Package svnmailer :: Module typedstruct

[hide private]

# Source Code for Module svnmailer.typedstruct

      1  # -*- coding: utf-8 -*-
      2  # pylint: disable-msg = W0613
      3  #
      4  # Copyright 2004-2006 André Malo or his licensors, as applicable
      5  #
      6  # Licensed under the Apache License, Version 2.0 (the "License");
      7  # you may not use this file except in compliance with the License.
      8  # You may obtain a copy of the License at
      9  #
     10  #     http://www.apache.org/licenses/LICENSE-2.0
     11  #
     12  # Unless required by applicable law or agreed to in writing, software
     13  # distributed under the License is distributed on an "AS IS" BASIS,
     14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  # See the License for the specific language governing permissions and
     16  # limitations under the License.
     17  """
     18  Typed Data Structures
     19  =====================
     20
     21  This module provides helpers for creating typed data structures.
     22
     23  Basic Usage
     24  -----------
     25  In order to create a new data structure, you inherit from C{Struct} and
     26  define the members like so (the booleans are explained later)::
     27
     28      class MyStruct(typedstruct.Struct):
     29          __slots__ = typedstruct.members(locals(), {
     30              'name1': None,
     31              # ... and/or ...
     32              'name2': <type>,
     33              # ... and/or ...
     34              'name3': (<type>, <param>),
     35          })
     36
     37  If there are no fixed types at all (always C{None}, you can still benefit
     38  from the features of the L{Struct} class and further write it a bit simpler::
     39
     40      class MyStruct(typedstruct.Struct):
     41          __slots__ = typedstruct.members(locals(), (
     42              'name1', 'name2', ...
     43          ))
     44
     45  Well, the main reason for using the Struct class is to get some level of type
     46  safety and automatic conversion without a complex written definition of
     47  C{property} for each and every member (it uses some property like descriptors
     48  internally, however). This encapsulates a lot of ugly logic and error handling
     49  (more or less) into a single piece of code and makes the member definitions
     50  I{much} easier to read and maintain. For example, you can create a struct
     51  member of type C{regex}. Now you assign a string to this member and it is
     52  automatically compiled to a regex, which you get, if you retrieve the
     53  value later. As you'll see, the C{regex} type needs to be defined as a class
     54  which should be inherited from the L{MemberDescriptor} class and assigned
     55  to the C{regex} type name via a type mapping dict::
     56
     57      class RegexMember(typedstruct.MemberDescriptor):
     58          def transform(self, value, arg):
     59              import re
     60              return re.compile(value)
     61      # ...
     62      typemap = {'regex': RegexMember}
     63      # ...
     64      class MyStruct(typedstruct.Struct):
     65          __slots__ = typedstruct.members(locals(), {
     66              'checker': 'regex',
     67          }, typemap = typemap)
     68      # ...
     69      store = MyStruct()
     70      store.checker = r'[a-zA-Z]$'
     71      # ...
     72      if store.checker.match(stringtocheck):
     73          # do something
     74
     75  Constraints
     76  -----------
     77  Member names must be valid python identifiers. Further all names starting
     78  I{and} ending with underscores are reserved for L{Struct}'s or python's
     79  own purposes.
     80  """
     81  __author__    = "André Malo"
     82  __docformat__ = "epytext en"
     83  __all__       = ['members', 'Struct', 'MemberDescriptor']
     84
     85  # global imports
     86  from svnmailer import util
     87
     88
     89 -class MemberDescriptor(object):
     90      """ Base class for members descriptors
     91
     92          @ivar name: The name of the member
     93          @type name: C{str}
     94
     95          @ivar param: The descriptor parameter
     96          @type param: any
     97
     98          @ivar __private: The reference to the private container
     99          @type __private: C{StructPrivate}
    100      """
    101 -    def __init__(self, name, private, param = None):
    102          """ Initialization """
    103          self.name = name
    104          self.param = param
    105          self.__private = private
    106
    107
    108 -    def __get__(self, instance, owner):
    109          """ Gets the member value """
    110          if instance is None:
    111              return None
    112
    113          priv = self.__private
    114          arg = priv.getArg(instance)
    115          mapper = priv.getMaps(instance).get(self.name)
    116
    117          value = self.substitute(
    118              priv.getValues(instance).get(self.name),
    119              util.ReadOnlyDict(priv.getSubst(instance)),
    120              arg
    121          )
    122          if mapper is not None:
    123              value = self.postmap(value, mapper, arg)
    124
    125          return value
    126
    127
    128 -    def __set__(self, instance, value):
    129          """ Sets the members value """
    130          priv = self.__private
    131          arg = priv.getArg(instance)
    132          mapper = priv.getMaps(instance).get(self.name)
    133
    134          if mapper is not None:
    135              value = self.premap(value, mapper, arg)
    136
    137          priv.getValues(instance)[self.name] = self.transform(value, arg)
    138
    139
    140 -    def __delete__(self, instance):
    141          """ Raises an AttributeError """
    142          raise AttributeError(
    143              "member '%s' cannot be deleted" % self.name
    144          )
    145
    146
    147 -    def premap(self, value, mapper, arg):
    148          """ Premapper - passes through by default
    149
    150              The premapper is called if the value is set before doing
    151              anything else.
    152
    153              @note: It is not called if no mapper function is defined (or it
    154                  is C{None}).
    155
    156              @param value: The value to premap
    157              @type value: any
    158
    159              @param mapper: The mapping argument
    160              @type mapper: any
    161
    162              @param arg: The argument used for struct initialization
    163              @type arg: any
    164          """
    165          return value
    166
    167
    168 -    def transform(self, value, arg):
    169          """ Transformer - passes through by default
    170
    171              Override this method in order to do any value transformation,
    172              e.g. compile the input string as regex or split it into a list.
    173
    174              The C{transform} method is called with the value returned from
    175              the L{premap} method. The result is stored as final member value.
    176
    177              @param value: The value to tranform
    178              @type value: any
    179
    180              @param arg: The argument used for struct initialization
    181              @type arg: any
    182
    183              @return: The transformed value
    184              @rtype: any
    185          """
    186          return value
    187
    188
    189 -    def substitute(self, value, subst, arg):
    190          """ Substituter - passes through by default
    191
    192              Use this method to do any dynamic processing on
    193              the retrieved value before it's being L{postmap}ped.
    194
    195              @param value: The value to substitute
    196              @type value: any
    197
    198              @param subst: The substitution dictionary
    199              @type subst: C{dict}
    200
    201              @param arg: The argument used for struct initialization
    202              @type arg: any
    203
    204              @return: The substituted value
    205              @rtype: any
    206          """
    207          return value
    208
    209
    210 -    def postmap(self, value, mapper, arg):
    211          """ Postmapper - passes through by default
    212
    213              The postmapper is called before the value is finally returned
    214              to the caller (after being substituted).
    215
    216              @note: The postmapper is not called if no mapper function
    217                  is defined (or it is C{None}).
    218
    219              @param value: The value to postmap
    220              @type value: any
    221
    222              @param mapper: The mapping argument
    223              @type mapper: any
    224
    225              @param arg: The argument used for struct initialization
    226              @type arg: any
    227          """
    228          return value
    229
    230
    231 -class Struct(object):
    232      """ General structure stub """
    233      _set_ = _members_ = None # satisfy pylint
    234
    235 -    def __new__(cls, _maps_ = None, _arg_ = None, **kwargs):
    236          """ Object creation
    237
    238              @param _maps_: The maps to use
    239              @type _maps_: C{dict}
    240
    241              @param _arg_: The opaque argument for custom descriptors
    242              @type _arg_: any
    243          """
    244          self = object.__new__(cls)
    245          self._set_.private.initInstance(self, _maps_, _arg_)
    246
    247          return self
    248
    249
    250 -    def __init__(self, _maps_ = None, _arg_ = None, **kwargs):
    251          """ Stub Initialization
    252
    253              @param _maps_: unused, but consistent to L{__new__}
    254              @type _maps_: C{dict}
    255
    256              @param _arg_: unused, but consistent to L{__new__}
    257              @type _arg_: any
    258          """
    259          for name, value in kwargs.items():
    260              self._set_(name, value)
    261
    262
    263 -    def __del__(self):
    264          """ Removes all references from private container """
    265          self._set_.private.removeInstance(self)
    266
    267
    268 -    def __repr__(self):
    269          """ Representation for debugging purposes
    270
    271              @return: A pythonic representation of the struct
    272              @rtype: C{str}
    273          """
    274          return "%s.%s(\n    %s\n)" % (
    275              self.__class__.__module__,
    276              self.__class__.__name__,
    277              ',\n    '.join([
    278                  "_maps_ = %r" % self._set_.private.getMaps(self),
    279                  "_arg_ = %r" % self._set_.private.getArg(self),
    280                  ] + [
    281                  "%s = %r" % (name, getattr(self, name))
    282                  for name in self._members_
    283                  if getattr(self, name) is not None
    284              ])
    285          )
    286
    287
    288 -def members(space, the_members, aliases = None, typemap = None):
    289      """ supply the member and slot entries
    290
    291          @param space: The namespace to pollute
    292          @type space: C{dict}
    293
    294          @param the_members: The member list / description
    295          @type the_members: C{tuple} or C{dict}
    296
    297          @param aliases: The member name aliases
    298          @type aliases: C{dict}
    299
    300          @param typemap: The type mapping table
    301          @type typemap: C{dict}
    302
    303          @return: The list of __slots__ to use.
    304          @rtype: C{list}
    305      """
    306      if type(the_members) in [tuple, list]:
    307          the_members = dict.fromkeys(the_members)
    308      names = the_members.keys()
    309
    310      private = StructPrivate(names, aliases or {})
    311      typemap = dict(typemap or {})
    312      typemap[None] = MemberDescriptor
    313
    314      for name, param in the_members.items():
    315          if name[:1] == '_' and name[-1:] == '_':
    316              raise AttributeError("%r is not allowed as member name" % name)
    317
    318          if type(param) in (tuple, list):
    319              space[name] = typemap[param[0]](name, private, param[1])
    320          else:
    321              space[name] = typemap[param](name, private)
    322
    323      space.update({
    324          '_set_'    : StructSetDescriptor(private),
    325          '_sub_'    : StructSubDescriptor(private),
    326          '_members_': StructMembersDescriptor(private),
    327          '_dict_'   : StructDictDescriptor(private),
    328      })
    329
    330      return names
    331
    332  # ====================================================================
    333  # PRIVATE STUFF
    334  # ====================================================================
    335
    336 -class StructPrivate(object):
    337      """ Private container class for Struct internals """
    338
    339 -    def __init__(self, names, aliases):
    340          """ Initialization
    341
    342              @param names: The member names to serve
    343              @type names: C{tuple}
    344
    345              @param aliases: The name mappings
    346              @type aliases: C{dict}
    347          """
    348          self.members = tuple(names)
    349          self.aliases = util.ReadOnlyDict(aliases)
    350          self.values  = {}
    351          self.subst   = {}
    352          self.maps    = {}
    353          self.args    = {}
    354
    355
    356 -    def initInstance(self, instance, maps, arg):
    357          """ Initializes the class for a particular instance
    358
    359              @param instance: The instance in question
    360              @type instance: C{object}
    361
    362              @param maps: The maps to use
    363              @type maps: C{dict}
    364
    365              @param arg: The initialization argument
    366              @type arg: any
    367          """
    368          this = id(instance)
    369
    370          # merge alias maps to real
    371          maps = dict(maps or {})
    372          for alias, real in self.aliases.items():
    373              if maps.has_key(alias):
    374                  maps[real] = maps[alias]
    375                  del maps[alias]
    376
    377          self.maps[this]   = util.ReadOnlyDict(maps)
    378          self.args[this]   = arg
    379          self.values[this] = {}
    380          self.subst[this]  = {}
    381
    382
    383 -    def removeInstance(self, instance):
    384          """ Removes all data, referring to a particular instance
    385
    386              @param instance: The instance in question
    387              @type instance: C{object}
    388          """
    389          this = id(instance)
    390
    391          try:
    392              del self.args[this]
    393              del self.maps[this]
    394              del self.values[this]
    395              del self.subst[this]
    396          except KeyError:
    397              raise RuntimeError("%r was not properly initialized" % self)
    398
    399
    400 -    def getValues(self, instance):
    401          """ Returns the value dict for the particular instance
    402
    403              @param instance: The instance in question
    404              @type instance: C{object}
    405
    406              @return: The value dictionary
    407              @rtype: C{dict}
    408          """
    409          this = id(instance)
    410          return self.values[this]
    411
    412
    413 -    def getSubst(self, instance):
    414          """ Returns the subst dict for the particular instance
    415
    416              @param instance: The instance in question
    417              @type instance: C{object}
    418
    419              @return: The substitution dict
    420              @rtype: C{dict}
    421          """
    422          this = id(instance)
    423          return self.subst[this]
    424
    425
    426 -    def getMaps(self, instance):
    427          """ Returns the map dict for the particular instance
    428
    429              @param instance: The instance in question
    430              @type instance: C{object}
    431
    432              @return: The map dictionary
    433              @rtype: C{dict}
    434          """
    435          this = id(instance)
    436          return self.maps[this]
    437
    438
    439 -    def getArg(self, instance):
    440          """ Returns the arg for the particular instance
    441
    442              @param instance: The instance in question
    443              @type instance: C{object}
    444
    445              @return: The arg used for initialization
    446              @rtype: any
    447          """
    448          this = id(instance)
    449          return self.args[this]
    450
    451
    452 -class StructDescriptor(object):
    453      """ Base class for struct descriptors
    454
    455          @ivar private: The private data container
    456          @type private: C{StructPrivate}
    457      """
    458 -    def __init__(self, private):
    459          """ Initialization
    460
    461              @param private: The private data container
    462              @type private: C{StructPrivate}
    463          """
    464          self.private = private
    465
    466
    467 -    def __set__(self, instance, value):
    468          """ setting is not allowed """
    469          raise AttributeError("attribute is read-only")
    470
    471
    472 -    def __delete__(self, instance):
    473          """ deleting is not allowed """
    474          raise AttributeError("attribute is read-only")
    475
    476
    477 -class StructSetDescriptor(StructDescriptor):
    478      """ _set_ descriptor """
    479
    480 -    def __get__(self, instance, owner):
    481          """ Returns an aliasing setter function """
    482          def aliassetter(name, value):
    483              """ Set the self.name = value
    484
    485                  @param name: Name of the struct member or an alias
    486                  @type name: C{str}
    487
    488                  @param value: Value of the struct member
    489                  @type value: any
    490
    491                  @exception AttributeError: The specified struct member doesn't
    492                      exist (nor is it an alias)
    493              """
    494              name = self.private.aliases.get(name, name)
    495              if name[:1] == '_' and name[-1:] == '_':
    496                  raise AttributeError("%r is not a struct member" % name)
    497
    498              setattr(instance or owner, name, value)
    499
    500          # starting point for the Struct class
    501          aliassetter.private = self.private
    502
    503          return aliassetter
    504
    505
    506 -class StructSubDescriptor(StructDescriptor):
    507      """ _sub_ descriptor """
    508
    509 -    def __get__(self, instance, owner):
    510          """ Returns an aliasing setter function """
    511
    512          def subsetter(name, value, default = False):
    513              """ Sets a key-value-pair for substitutions
    514
    515                  If C{default} is true and the name already exists,
    516                  it will not be overidden.
    517
    518                  @param name: The key
    519                  @type name: C{str}
    520
    521                  @param value: The value
    522                  @type value: C{str}
    523
    524                  @param default: Is the supplied value a default?
    525                  @type default: C{bool}
    526              """
    527              if instance is None:
    528                  raise AttributeError(
    529                      "%s._sub_ only works on instances" % owner.__name__
    530                  )
    531
    532              subst = self.private.getSubst(instance)
    533              if not default or not subst.has_key(name):
    534                  subst[name] = value
    535
    536          def getdict():
    537              """ Returns the current dict (read-only) """
    538              return util.ReadOnlyDict(self.private.getSubst(instance))
    539
    540          subsetter.dict = getdict
    541          return subsetter
    542
    543
    544 -class StructMembersDescriptor(StructDescriptor):
    545      """ _members_ descriptor """
    546 -    def __get__(self, instance, owner):
    547          """ Returns the struct members as tuple """
    548          return self.private.members
    549
    550
    551 -class StructDictDescriptor(StructDescriptor):
    552      """ _dict_ descriptor """
    553 -    def __get__(self, instance, owner):
    554          """ Returns the values dict read-only """
    555          if instance is None:
    556              return util.ReadOnlyDict.fromkeys(self.private.members)
    557
    558          return util.ReadOnlyDict(self.private.getValues(instance))
    559