Package pyamf
[hide private]
[frames] | no frames]

Source Code for Package pyamf

   1  # Copyright (c) 2007-2008 The PyAMF Project. 
   2  # See LICENSE for details. 
   3   
   4  """ 
   5  B{PyAMF} provides B{A}ction B{M}essage B{F}ormat 
   6  (U{AMF<http://en.wikipedia.org/wiki/Action_Message_Format>}) support for 
   7  Python that is compatible with the 
   8  U{Flash Player<http://en.wikipedia.org/wiki/Flash_Player>}. 
   9   
  10  @author: U{Arnar Birgisson<mailto:arnarbi@gmail.com>} 
  11  @author: U{Thijs Triemstra<mailto:info@collab.nl>} 
  12  @author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>} 
  13   
  14  @copyright: Copyright (c) 2007-2008 The PyAMF Project. All Rights Reserved. 
  15  @contact: U{dev@pyamf.org<mailto:dev@pyamf.org>} 
  16  @see: U{http://pyamf.org} 
  17   
  18  @since: October 2007 
  19  @status: Beta 
  20  @version: 0.3.1 
  21  """ 
  22   
  23  import types 
  24   
  25  from pyamf import util 
  26  from pyamf.adapters import register_adapters 
  27   
  28  __all__ = [ 
  29      'register_class', 
  30      'register_class_loader', 
  31      'encode', 
  32      'decode', 
  33      '__version__'] 
  34   
  35  #: PyAMF version number. 
  36  __version__ = (0, 3, 1) 
  37   
  38  #: Class mapping support. 
  39  CLASS_CACHE = {} 
  40  #: Class loaders. 
  41  CLASS_LOADERS = [] 
  42   
  43  #: Custom type map. 
  44  TYPE_MAP = {} 
  45   
  46  #: Maps error classes to string codes. 
  47  ERROR_CLASS_MAP = {} 
  48   
  49  #: Specifies that objects are serialized using AMF for ActionScript 1.0 and 2.0. 
  50  AMF0 = 0 
  51  #: Specifies that objects are serialized using AMF for ActionScript 3.0. 
  52  AMF3 = 3 
  53  #: Supported AMF encoding types. 
  54  ENCODING_TYPES = (AMF0, AMF3) 
  55   
56 -class ClientTypes:
57 """ 58 Typecodes used to identify AMF clients and servers. 59 60 @see: U{Flash Player on WikiPedia (external) 61 <http://en.wikipedia.org/wiki/Flash_Player>} 62 @see: U{Flash Media Server on WikiPedia (external) 63 <http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server>} 64 """ 65 #: Specifies a Flash Player 6.0 - 8.0 client. 66 Flash6 = 0 67 #: Specifies a FlashCom / Flash Media Server client. 68 FlashCom = 1 69 #: Specifies a Flash Player 9.0 client. 70 Flash9 = 3
71 72 #: List of AMF client typecodes. 73 CLIENT_TYPES = [] 74 75 for x in ClientTypes.__dict__: 76 if not x.startswith('_'): 77 CLIENT_TYPES.append(ClientTypes.__dict__[x]) 78 del x 79 80 #: Represents the C{undefined} value in a Flash client. 81 Undefined = object() 82
83 -class BaseError(Exception):
84 """ 85 Base AMF Error. 86 87 All AMF related errors should be subclassed from this class. 88 """
89
90 -class DecodeError(BaseError):
91 """ 92 Raised if there is an error in decoding an AMF data stream. 93 """
94
95 -class EOStream(BaseError):
96 """ 97 Raised if the data stream has come to a natural end. 98 """
99
100 -class ReferenceError(BaseError):
101 """ 102 Raised if an AMF data stream refers to a non-existent object 103 or string reference. 104 """
105
106 -class EncodeError(BaseError):
107 """ 108 Raised if the element could not be encoded to the stream. 109 110 @bug: See U{Docuverse blog (external) 111 <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>} 112 for more info about the empty key string array bug. 113 """
114
115 -class UnknownClassAlias(BaseError):
116 """ 117 Raised if the AMF stream specifies a class that does not 118 have an alias. 119 120 @see: L{register_class} 121 """
122
123 -class BaseContext(object):
124 """ 125 I hold the AMF context for en/decoding streams. 126 """
127 - def __init__(self):
128 self.clear()
129
130 - def clear(self):
131 self.objects = [] 132 self.rev_objects = {} 133 134 self.class_aliases = {}
135
136 - def getObject(self, ref):
137 """ 138 Gets an object based on a reference. 139 140 @raise TypeError: Bad reference type. 141 """ 142 if not isinstance(ref, (int, long)): 143 raise TypeError, "Bad reference type" 144 145 try: 146 return self.objects[ref] 147 except IndexError: 148 raise ReferenceError
149
150 - def getObjectReference(self, obj):
151 """ 152 Gets a reference for an object. 153 """ 154 try: 155 return self.rev_objects[id(obj)] 156 except KeyError: 157 raise ReferenceError
158
159 - def addObject(self, obj):
160 """ 161 Adds a reference to C{obj}. 162 163 @type obj: C{mixed} 164 @param obj: The object to add to the context. 165 166 @rtype: C{int} 167 @return: Reference to C{obj}. 168 """ 169 self.objects.append(obj) 170 idx = len(self.objects) - 1 171 self.rev_objects[id(obj)] = idx 172 173 return idx
174
175 - def getClassAlias(self, klass):
176 """ 177 Gets a class alias based on the supplied C{klass}. 178 """ 179 if klass not in self.class_aliases: 180 try: 181 self.class_aliases[klass] = get_class_alias(klass) 182 except UnknownClassAlias: 183 self.class_aliases[klass] = None 184 185 return self.class_aliases[klass]
186
187 - def __copy__(self):
188 raise NotImplementedError
189
190 -class ASObject(dict):
191 """ 192 This class represents a Flash Actionscript Object (typed or untyped). 193 194 I supply a C{__builtin__.dict} interface to support get/setattr calls. 195 """
196 - def __init__(self, *args, **kwargs):
197 dict.__init__(self, *args, **kwargs)
198
199 - def __getattr__(self, k):
200 try: 201 return self[k] 202 except KeyError: 203 raise AttributeError, 'unknown attribute \'%s\'' % k
204
205 - def __setattr__(self, k, v):
206 self[k] = v
207
208 - def __repr__(self):
209 return dict.__repr__(self)
210
211 -class MixedArray(dict):
212 """ 213 Used to be able to specify the C{mixedarray} type. 214 """
215
216 -class ClassMetaData(list):
217 """ 218 I hold a list of tags relating to the class. The idea behind this is 219 to emulate the metadata tags you can supply to ActionScript, 220 e.g. static/dynamic. 221 """ 222 _allowed_tags = ( 223 ('static', 'dynamic', 'external'), 224 ('amf3', 'amf0'), 225 ('anonymous',), 226 ) 227
228 - def __init__(self, *args):
229 if len(args) == 1 and hasattr(args[0], '__iter__'): 230 for x in args[0]: 231 self.append(x) 232 else: 233 for x in args: 234 self.append(x)
235
236 - def _is_tag_allowed(self, x):
237 for y in self._allowed_tags: 238 if isinstance(y, (types.ListType, types.TupleType)): 239 if x in y: 240 return (True, y) 241 else: 242 if x == y: 243 return (True, None) 244 245 return (False, None)
246
247 - def append(self, x):
248 """ 249 Adds a tag to the metadata. 250 251 @param x: Tag. 252 253 @raise ValueError: Unknown tag. 254 """ 255 x = str(x).lower() 256 257 allowed, tags = self._is_tag_allowed(x) 258 259 if not allowed: 260 raise ValueError("Unknown tag %s" % x) 261 262 if tags is not None: 263 # check to see if a tag in the list is about to be clobbered if so, 264 # raise a warning 265 for y in tags: 266 if y in self: 267 if x != y: 268 import warnings 269 270 warnings.warn( 271 "Previously defined tag %s superceded by %s" % ( 272 y, x)) 273 274 list.pop(self, self.index(y)) 275 break 276 277 list.append(self, x)
278
279 - def __contains__(self, other):
280 return list.__contains__(self, str(other).lower())
281 282 # TODO nick: deal with slices 283
284 -class ClassAlias(object):
285 """ 286 Class alias. 287 288 All classes are initially set to a dynamic state. 289 290 @ivar attrs: A list of attributes to encode for this class. 291 @type attrs: C{list} 292 @ivar metadata: A list of metadata tags similar to ActionScript tags. 293 @type metadata: C{list} 294 """
295 - def __init__(self, klass, alias, attrs=None, attr_func=None, metadata=[]):
296 """ 297 @type klass: C{class} 298 @param klass: The class to alias. 299 @type alias: C{str} 300 @param alias: The alias to the class e.g. C{org.example.Person}. If the 301 value of this is C{None}, then it is worked out based on the C{klass}. 302 The anonymous tag is also added to the class. 303 @type attrs: 304 @param attrs: 305 @type metadata: 306 @param metadata: 307 308 @raise TypeError: The C{klass} must be a class type. 309 @raise TypeError: The C{read_func} must be callable. 310 @raise TypeError: The C{write_func} must be callable. 311 """ 312 if not isinstance(klass, (type, types.ClassType)): 313 raise TypeError, "klass must be a class type" 314 315 self.metadata = ClassMetaData(metadata) 316 317 if alias is None: 318 self.metadata.append('anonymous') 319 alias = "%s.%s" % (klass.__module__, klass.__name__,) 320 321 self.klass = klass 322 self.alias = alias 323 self.attr_func = attr_func 324 self.attrs = attrs 325 326 if 'external' in self.metadata: 327 # class is declared as external, lets check 328 if not hasattr(klass, '__readamf__'): 329 raise AttributeError("An externalised class was specified, but" 330 " no __readamf__ attribute was found for class %s" % ( 331 klass.__name__)) 332 333 if not hasattr(klass, '__writeamf__'): 334 raise AttributeError("An externalised class was specified, but" 335 " no __writeamf__ attribute was found for class %s" % ( 336 klass.__name__)) 337 338 if not isinstance(klass.__readamf__, types.UnboundMethodType): 339 raise TypeError, "%s.__readamf__ must be callable" % ( 340 klass.__name__) 341 342 if not isinstance(klass.__writeamf__, types.UnboundMethodType): 343 raise TypeError, "%s.__writeamf__ must be callable" % ( 344 klass.__name__) 345 346 if 'dynamic' in self.metadata: 347 if attr_func is not None and not callable(attr_func): 348 raise TypeError, "attr_func must be callable" 349 350 if 'static' in self.metadata: 351 if attrs is None: 352 raise ValueError, "attrs keyword must be specified for static classes"
353
354 - def __call__(self, *args, **kwargs):
355 """ 356 Creates an instance of the klass. 357 358 @return: Instance of C{self.klass}. 359 """ 360 if hasattr(self.klass, '__setstate__') or hasattr(self.klass, '__getstate__'): 361 if type(self.klass) is types.TypeType: # new-style class 362 return self.klass.__new__(self.klass) 363 elif type(self.klass) is types.ClassType: # classic class 364 return util.make_classic_instance(self.klass) 365 366 raise TypeError, 'invalid class type %r' % self.klass 367 368 return self.klass(*args, **kwargs)
369
370 - def __str__(self):
371 return self.alias
372
373 - def __repr__(self):
374 return '<ClassAlias alias=%s klass=%s @ %s>' % ( 375 self.alias, self.klass, id(self))
376
377 - def __eq__(self, other):
378 if isinstance(other, basestring): 379 return self.alias == other 380 elif isinstance(other, self.__class__): 381 return self.klass == other.klass 382 elif isinstance(other, (type, types.ClassType)): 383 return self.klass == other 384 else: 385 return False
386
387 - def __hash__(self):
388 return id(self)
389
390 - def getAttrs(self, obj, attrs=None, traverse=True):
391 if attrs is None: 392 attrs = [] 393 394 did_something = False 395 396 if self.attrs is not None: 397 did_something = True 398 attrs += self.attrs 399 400 if 'dynamic' in self.metadata and self.attr_func is not None: 401 did_something = True 402 extra_attrs = self.attr_func(obj) 403 404 attrs += [key for key in extra_attrs if key not in attrs] 405 406 if traverse is True: 407 for base in util.get_mro(obj.__class__): 408 try: 409 alias = get_class_alias(base) 410 except UnknownClassAlias: 411 continue 412 413 x = alias.getAttrs(obj, attrs, False) 414 415 if x is not None: 416 attrs += x 417 did_something = True 418 419 if did_something is False: 420 return None 421 422 a = [] 423 424 for x in attrs: 425 if x not in a: 426 a.append(x) 427 428 return a
429
430 -class BaseDecoder(object):
431 """ 432 Base AMF decoder. 433 434 @ivar context_class: The context for the decoding. 435 @type context_class: An instance of C{BaseDecoder.context_class} 436 @ivar type_map: 437 @type type_map: C{list} 438 @ivar stream: The underlying data stream. 439 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 440 """ 441 context_class = BaseContext 442 type_map = {} 443
444 - def __init__(self, data=None, context=None):
445 """ 446 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 447 @param data: Data stream. 448 @type context: L{Context<pyamf.amf0.Context>} 449 @param context: Context. 450 @raise TypeError: The C{context} parameter must be of 451 type L{Context<pyamf.amf0.Context>}. 452 """ 453 # coerce data to BufferedByteStream 454 if isinstance(data, util.BufferedByteStream): 455 self.stream = d