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