spoon.spoon

Package spoon :: Module spoon |
[hide private]
[frames] | no frames]

# Source Code for Module spoon.spoon

      1  #
      2  # Copyright (C) 2006, Matt Sullivan <matts@zarrf.com>
      3  #
      4  # Permission is hereby granted, free of charge, to any person obtaining
      5  # a copy of this software and associated documentation files (the
      6  # "Software"), to deal in the Software without restriction, including
      7  # without limitation the rights to use, copy, modify, merge, publish,
      8  # distribute, sublicense, and/or sell copies of the Software, and to
      9  # permit persons to whom the Software is furnished to do so, subject to
     10  # the following conditions:
     11  #
     12  # The above copyright notice and this permission notice shall be included
     13  # in all copies or substantial portions of the Software.
     14  #
     15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
     16  # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     17  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     18  # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     19  # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     20  # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     21  # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     22  #
     23
     24  import sys, StringIO, warnings, zlib
     25
     26  from threading import RLock, Semaphore
     27
     28  import ber
     29
     30  DICT_TAG = ber.Tag(ber.APPLICATION, 1, container=True)
     31  PYOBJ_TAG = ber.Tag(ber.APPLICATION, 2, container=True)
     32  PYOBJ_REF_TAG = ber.Tag(ber.APPLICATION, 3)
     33  LAZY_DICT_TAG = ber.Tag(ber.APPLICATION, 4, container=True)
     34  STRING_BUFF_TAG = ber.Tag(ber.APPLICATION, 5, container=True)
     35  PYOBJZ_TAG = ber.Tag(ber.APPLICATION, 6)
     36  # Sub package types
     37  SPOONLINKMSG_TAG = ber.Tag(ber.APPLICATION, 7, container=True)
     38  SPOONNETMSG_TAG = ber.Tag(ber.APPLICATION, 8, container=True)
     39
     40 -class serialprop(object):
     41      """
     42      When used like a property in a Serial class, it will act as
     43      a flag to the serializer that only properties that are of type C{serialprop} or C{lazyprop}
     44      are to be serialized.
     45
     46      Any subclass of this will be treated the same way, so if you need your property to behave like
     47      a customizable property, then subclass away.
     48      """
     49 -    def __init__(self, value=None):
     50          self.initVal = value
     51          self.valDict = {}
     52          self.lock = RLock()
     53
     54 -    def __get__(self, obj, Type=None):
     55          obj._spoon_meta.proplock.acquire()
     56          try:
     57              return obj._spoon_meta.propdict.setdefault(self, self.initVal) # Grr stupid name
     58          finally:
     59              obj._spoon_meta.proplock.release()
     60
     61 -    def __set__(self, obj, value):
     62          obj._spoon_meta.proplock.acquire()
     63          try:
     64              obj._spoon_meta.propdict[self] = value
     65          finally:
     66              obj._spoon_meta.proplock.release()
     67
     68 -    def __del__(self, obj):
     69          self.lock.acquire()
     70          try:
     71              del obj._spoon_meta.propdict[self]
     72          finally:
     73              self.lock.release()
     74
     75
     76 -class lazyprop(property):
     77      """
     78      When used like a property in a Serial class, it will act as
     79      a flag to the serializer that only properties that are of type C{serialprop} or C{lazyprop}
     80      are to be serialized.
     81
     82      Any subclass of this will be treated the same way, so if you need your property to behave like
     83      a customizable property, then subclass away.
     84
     85      lazyprop indicates that on decoding, the object will have to explicitly decode all of the
     86      properties marked with lazyprop.  lazyprop and serialprop may be mixed together in a class,
     87      and all serialprops will be decoded.
     88      """
     89 -    def __init__(self, value=None):
     90          self.initVal = value
     91          self.valDict = {}
     92 -    def __get__(self, obj, Type=None):
     93          lazydata = getattr(obj, "_spoon_lazydata", None)
     94          if (lazydata is not None) and (lazydata.closed != True):
     95              obj.decode_lazy()
     96          obj._spoon_meta.proplock.acquire()
     97          try:
     98              return obj._spoon_meta.propdict.setdefault(self, self.initVal) # Grr stupid name
     99          finally:
    100              obj._spoon_meta.proplock.release()
    101
    102 -    def __set__(self, obj, value):
    103          obj._spoon_meta.proplock.acquire()
    104          try:
    105              obj._spoon_meta.propdict[self] = value
    106          finally:
    107              obj._spoon_meta.proplock.release()
    108
    109 -    def __del__(self, obj):
    110          obj._spoon_meta.proplock.acquire()
    111          try:
    112              del obj._spoon_meta.propdict[self]
    113          finally:
    114              obj._spoon_meta.proplock.release()
    115
    116 -class SpoonData(object):
    117 -    def __init__(self):
    118          self.proplock = Semaphore()
    119          self.decodelock = Semaphore()
    120          self.propdict = {}
    121
    122 -class SerialMeta(type):
    123 -    def __init__(cls, name, bases, dict):
    124          super(SerialMeta, cls).__init__(name, bases, dict)
    125          tmpList = [k for k, v in dict.iteritems() if serialprop in type(v).__mro__]
    126          if len(tmpList) > 0:
    127              setattr(cls, "_spoon_attrs", tmpList)
    128          tmpList = [k for k, v in dict.iteritems() if lazyprop in type(v).__mro__]
    129          if len(tmpList) > 0:
    130              setattr(cls, "_spoon_lazyattrs", tmpList)
    131
    132 -class Serial (object):
    133      __metaclass__ = SerialMeta
    134
    135
    136 -    def __new__(cls, *args, **kwargs):
    137          newobj = super(Serial, cls).__new__(cls, *args, **kwargs)
    138          newobj._spoon_meta = SpoonData()
    139          return newobj
    140
    141 -    def post_deserialize(self):
    142          """
    143          Called after an object is received over the network, instead of the
    144          normal C{__init__} method.
    145          """
    146          pass
    147
    148 -    def pre_serialize(self):
    149          """
    150          Called right before this object's attributes are encoded into a stream
    151          for network travel.
    152          """
    153          pass
    154
    155 -    def decode_lazy(self):
    156          """
    157          Decode all of the attributes marked as lazy that have not yet been decoded.
    158          @return: Number of attributes decoded.
    159          @warning: Some sort of instance level locking should be done
    160          """
    161          if self._spoon_lazydata.closed:
    162              return 0
    163          self._spoon_meta.decodelock.acquire()
    164          try:
    165              b = ber.BERStream(self._spoon_lazydata)
    166              attrs = b.next()
    167              for k, v in attrs.iteritems():
    168                  setattr(self, k, v)
    169              # We can now happily get rid of this string buffer
    170              self._spoon_lazydata.close()
    171              return len(attrs)
    172          finally:
    173              self._spoon_meta.decodelock.release()
    174
    175 -class Memoizer (object):
    176      """
    177      Used as a substitute for the file object when serializing Serial objects,
    178      so reference cycles are caught and handled.
    179      """
    180
    181 -    def __init__(self, fd):
    182          self.fd = fd
    183          self.memo = {}
    184
    185 -    def write(self, data):
    186          self.fd.write(data)
    187
    188 -    def read(self, n):
    189          return self.fd.read(n)
    190
    191 -    def __getattr__(self, name):
    192          return getattr(self._fd, name)
    193
    194
    195 -class StringIOWrap(object):
    196      """
    197      Stupid wrapper around StringIO because it isn't a new style class. GRR!
    198
    199      I'm only implementing the methods that actually make sense for StringIOs and that we actually use.
    200      """
    201 -    def __init__(self, sio = None):
    202          if sio == None:
    203              self.sio = StringIO.StringIO()
    204          else:
    205              self.sio = sio
    206
    207 -    def __iter__(self):
    208          return self.sio.__iter__()
    209
    210 -    def next(self):
    211          return self.sio.next()
    212
    213 -    def getvalue(self):
    214          return self.sio.getvalue()
    215
    216 -    def write(self, data):
    217          return self.sio.write(data)
    218
    219 -    def read(self, cnt):
    220          return self.sio.read(cnt)
    221
    222 -    def close(self):
    223          return self.sio.close()
    224
    225 -    def seek(self, pos):
    226          return self.sio.seek(pos)
    227
    228 -    def tell(self):
    229          return self.sio.tell()
    230
    231      # property functions
    232 -    def get_len(self):
    233          return self.sio.len
    234
    235 -    def get_closed(self):
    236          return self.sio.closed
    237
    238      len = property(fget=get_len)
    239      closed = property(fget=get_closed)
    240
    241
    242 -class LazyDict(dict):
    243      pass
    244
    245  # Used to make sure that lazy attributes will be properly re-encoded if they aren't ever decoded.
    246  @ber.encoder(StringIOWrap)
    247 -def encode_stringio(fd, item):
    248      tmpfd = StringIO.StringIO()
    249      strio_pos = item.tell()
    250
    251      b = ber.BERStream(tmpfd)
    252      b.add(item.tell())
    253
    254      ber.Tag.from_tag(ber.BYTES_TYPE, item.len).write(tmpfd)
    255
    256      tmpfd.write(item.getvalue())
    257
    258      ber.Tag.from_tag(STRING_BUFF_TAG, tmpfd.len).write(fd)
    259      fd.write(tmpfd.getvalue())
    260
    261  @ber.decoder(STRING_BUFF_TAG)
    262 -def decode_stringio(fd, tag):
    263      out = StringIOWrap()
    264      b = ber.BERStream(fd)
    265      seek_pos = b.next()
    266      out.write(b.next())
    267      out.seek(seek_pos)
    268      return out
    269
    270
    271  @ber.encoder(LazyDict)
    272 -def encode_lazydict(fd, item):
    273      tmpfd = StringIO.StringIO()
    274      b = ber.BERStream(tmpfd)
    275      for key, value in item.iteritems():
    276          b.add((key, value))
    277
    278      ber.Tag.from_tag(LAZY_DICT_TAG, tmpfd.len).write(fd)
    279      fd.write(tmpfd.getvalue())
    280      tmpfd.close()
    281
    282
    283  @ber.decoder(LAZY_DICT_TAG)
    284 -def decode_lazydict(fd, tag):
    285      """
    286      Decoding a lazy dict will just result in returning the entire object as a string.
    287      """
    288      tmpfd = StringIOWrap()
    289      # Turn the tag in the encoded data to a normal dict, so it will actually return objects
    290      ber.Tag.from_tag(DICT_TAG, tag.size).write(tmpfd)
    291      count = 0
    292      while count < tag.size:
    293          data = fd.read(tag.size - count)
    294          count += len(data)
    295          tmpfd.write(data)
    296      tmpfd.seek(0)
    297      return tmpfd
    298
    299
    300  @ber.encoder(dict)
    301 -def encode_dict(fd, item):
    302      ber.Tag.from_tag(DICT_TAG, None).write(fd)
    303      b = ber.BERStream(fd)
    304      for key, value in item.iteritems():
    305          b.add((key, value))
    306      b._add_eof()
    307
    308
    309  @ber.decoder(DICT_TAG)
    310 -def decode_dict(fd, tag):
    311      out = {}
    312      b = ber.BERStream(fd, tag.size)
    313      while b.has_next():
    314          key, value = b.next()
    315          out[key] = value
    316      return out
    317
    318  @ber.zencoder(Serial)
    319 -def encode_pyobjz(fd, obj):
    320      tmpfd = StringIO.StringIO()
    321      encode_pyobj(tmpfd, obj, True)
    322      data = zlib.compress(tmpfd.getvalue())
    323      tmpfd.close()
    324      ber.Tag.from_tag(PYOBJZ_TAG, len(data)).write(fd)
    325      fd.write(data)
    326
    327  @ber.decoder(PYOBJZ_TAG)
    328 -def decode_pyobjz(fd, tag):
    329      data = zlib.decompress(fd.read(tag.size))
    330      tmpfd = StringIO.StringIO(data)
    331      return decode_pyobj(tmpfd, ber.Tag.from_tag(PYOBJ_TAG, len(data)))
    332
    333
    334  @ber.encoder(Serial)
    335 -def encode_pyobj(fd, obj, omittag = False):
    336      #  The format for an object is
    337      #  [ str(id), str(name), str(module), dict(attrs), lazydict(lazyattrs)]
    338      obj.pre_serialize()
    339      attrlist = getattr(obj, '_spoon_attrs', None)
    340      lazyattrlist = getattr(obj, '_spoon_lazyattrs', None)
    341
    342      if attrlist is None and lazyattrlist is None:
    343          #attrlist = dir(obj)
    344          # do not encode anything with a '__call__' attribute or that starts with '_'
    345          # No filtering if we're using the _spoon_attrs
    346          attrlist = [a for a in dir(obj) if ((a[0] != '_') and ('__call__' not in dir(getattr(obj, a, None))))]
    347
    348      objectlazydata = getattr(obj, "_spoon_lazydata", None)
    349
    350      attrdict = {}
    351      if attrlist is not None:
    352          for a in attrlist:
    353              if not ber.BERStream.can_encode(getattr(obj, a, None)):
    354                  warnings.warn("Can't encode attribute %s of %s(type: %s"%(a,
    355                      type(obj).__name__, repr(type(getattr(obj,a,None)))), RuntimeWarning)
    356                  continue
    357              attrdict[a] = getattr(obj, a, None)
    358
    359      # Special case for _spoon_lazydata, if it's present and not closed, we should encode it.
    360      if (objectlazydata is not None) and (not objectlazydata.closed):
    361          attrdict["_spoon_lazydata"] = objectlazydata
    362
    363      lazydict = LazyDict()
    364      if (lazyattrlist is not None) and (objectlazydata is None):
    365          for a in lazyattrlist:
    366              lazydict[a] = getattr(obj, a, None)
    367
    368      # fd will be a Memoizer already if we're being called recursively while
    369      # decoding another Serial object
    370      if getattr(fd, 'memo', None) is None:
    371          fd = Memoizer(fd)
    372
    373      ref = str(id(obj))
    374      if ref in fd.memo:
    375          # cyclic reference: just store a ref id
    376          ber.Tag.from_tag(PYOBJ_REF_TAG, len(ref)).write(fd)
    377          fd.write(ref)
    378          return
    379
    380      fd.memo[ref] = obj
    381
    382      if not omittag:
    383          ber.Tag.from_tag(PYOBJ_TAG, None).write(fd)
    384      b = ber.BERStream(fd)
    385      b.add(ref)
    386      b.add(type(obj).__name__)
    387      b.add(type(obj).__module__)
    388      b.add(attrdict)
    389      b.add(lazydict)
    390      b._add_eof()
    391
    392
    393  @ber.decoder(PYOBJ_TAG)
    394 -def decode_pyobj(fd, tag):
    395      if getattr(fd, 'memo', None) is None:
    396          fd = Memoizer(fd)
    397
    398      b = ber.BERStream(fd, tag.size)
    399      ref = b.next()
    400      typename = b.next()
    401      modulename = b.next()
    402
    403      # must build the obj first, and populate it in the memo dict, before
    404      # decoding attrs, cuz decoding attrs will potentially lead us into a
    405      # recursive spiral of decoding more objects!
    406
    407      # FIXME: to handle future extensibility, undecodable objects should be
    408      # stored in some other object class, like "UnknownObject", holding the
    409      # module name, class name, and attributes.  this is especially important
    410      # for supporting plugins.
    411
    412      # FIXME: for security purposes, the class must be verified to be some
    413      # kind of serializable class.
    414      module = sys.modules.get(modulename, None)
    415      if module is None:
    416          raise ber.BERException('Unable to find module %r' % (modulename,))
    417      cls = getattr(module, typename, None)
    418      if cls is None:
    419          raise ber.BERException('Unable to find class %r in module %r' % (typename, modulename))
    420
    421      obj = cls.__new__(cls)
    422      fd.memo[ref] = obj
    423
    424      # Now we need to potentially decode the rest of
    425
    426      setlazydata = False
    427      attrs = b.next()
    428      for k, v in attrs.iteritems():
    429          setattr(obj, k, v)
    430          if k == "_spoon_lazydata":
    431              setlazydata = True
    432
    433      lazydata = b.next()
    434      if setlazydata is False:
    435          setattr(obj, "_spoon_lazydata", lazydata)
    436      if b.has_next():
    437          pass
    438      obj.post_deserialize()
    439      return obj
    440
    441
    442  @ber.decoder(PYOBJ_REF_TAG)
    443 -def decode_pyobj_ref(fd, tag):
    444      print 'decode-ref'
    445      if getattr(fd, 'memo', None) is None:
    446          raise ber.BERException('Python object reference outside of object cycle == impossible?')
    447      ref = fd.read(tag.size)
    448      obj = fd.memo.get(ref, None)
    449      if obj is None:
    450          raise ber.BERException('Python object reference to nonexistent entity')
    451      return obj
    452