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

Source Code for Package pyamf

   1  # Copyright (c) 2007-2009 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  @copyright: Copyright (c) 2007-2009 The PyAMF Project. All Rights Reserved. 
  11  @contact: U{dev@pyamf.org<mailto:dev@pyamf.org>} 
  12  @see: U{http://pyamf.org} 
  13   
  14  @since: October 2007 
  15  @version: 0.4.2 
  16  @status: Production/Stable 
  17  """ 
  18   
  19  import types 
  20   
  21  from pyamf import util 
  22  from pyamf.adapters import register_adapters 
  23   
  24  __all__ = [ 
  25      'register_class', 
  26      'register_class_loader', 
  27      'encode', 
  28      'decode', 
  29      '__version__' 
  30  ] 
  31   
  32  #: PyAMF version number. 
  33  __version__ = (0, 4, 2) 
  34   
  35  #: Class mapping support. 
  36  CLASS_CACHE = {} 
  37  #: Class loaders. 
  38  CLASS_LOADERS = [] 
  39  #: Custom type map. 
  40  TYPE_MAP = {} 
  41  #: Maps error classes to string codes. 
  42  ERROR_CLASS_MAP = {} 
  43  #: Alias mapping support 
  44  ALIAS_TYPES = {} 
  45   
  46  #: Specifies that objects are serialized using AMF for ActionScript 1.0 and 2.0. 
  47  AMF0 = 0 
  48  #: Specifies that objects are serialized using AMF for ActionScript 3.0. 
  49  AMF3 = 3 
  50  #: Supported AMF encoding types. 
  51  ENCODING_TYPES = (AMF0, AMF3) 
  52   
53 -class ClientTypes:
54 """ 55 Typecodes used to identify AMF clients and servers. 56 57 @see: U{Flash Player on WikiPedia (external) 58 <http://en.wikipedia.org/wiki/Flash_Player>} 59 @see: U{Flash Media Server on WikiPedia (external) 60 <http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server>} 61 """ 62 #: Specifies a Flash Player 6.0 - 8.0 client. 63 Flash6 = 0 64 #: Specifies a FlashCom / Flash Media Server client. 65 FlashCom = 1 66 #: Specifies a Flash Player 9.0 client. 67 Flash9 = 3
68 69 #: List of AMF client typecodes. 70 CLIENT_TYPES = [] 71 72 for x in ClientTypes.__dict__: 73 if not x.startswith('_'): 74 CLIENT_TYPES.append(ClientTypes.__dict__[x]) 75 del x 76
77 -class UndefinedType(object):
78 - def __repr__(self):
79 return 'pyamf.Undefined'
80 81 #: Represents the C{undefined} value in a Flash client. 82 Undefined = UndefinedType() 83
84 -class BaseError(Exception):
85 """ 86 Base AMF Error. 87 88 All AMF related errors should be subclassed from this class. 89 """
90
91 -class DecodeError(BaseError):
92 """ 93 Raised if there is an error in decoding an AMF data stream. 94 """
95
96 -class EOStream(BaseError):
97 """ 98 Raised if the data stream has come to a natural end. 99 """
100
101 -class ReferenceError(BaseError):
102 """ 103 Raised if an AMF data stream refers to a non-existent object 104 or string reference. 105 """
106
107 -class EncodeError(BaseError):
108 """ 109 Raised if the element could not be encoded to the stream. 110 111 @bug: See U{Docuverse blog (external) 112 <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>} 113 for more info about the empty key string array bug. 114 """
115
116 -class UnknownClassAlias(BaseError):
117 """ 118 Raised if the AMF stream specifies a class that does not 119 have an alias. 120 121 @see: L{register_class} 122 """
123
124 -class BaseContext(object):
125 """ 126 I hold the AMF context for en/decoding streams. 127 """ 128
129 - def __init__(self):
130 self.objects = util.IndexedCollection() 131 self.clear()
132
133 - def clear(self):
134 """ 135 Completely clears the context. 136 """ 137 self.objects.clear() 138 self.class_aliases = {}
139
140 - def reset(self):
141 """ 142 Resets the context. This is subtly different to the 143 L{BaseContext.clear} method, which is a hard delete of the context. 144 This method is mainly used by the L{remoting api<pyamf.remoting>} to 145 handle context clearing between requests. 146 """ 147 self.objects.clear() 148 self.class_aliases = {}
149
150 - def getObject(self, ref):
151 """ 152 Gets an object based on a reference. 153 154 @raise ReferenceError: Unknown object reference. 155 """ 156 try: 157 return self.objects.getByReference(ref) 158 except ReferenceError: 159 raise ReferenceError("Unknown object reference %r" % (ref,))
160
161 - def getObjectReference(self, obj):
162 """ 163 Gets a reference for an object. 164 165 @raise ReferenceError: Object not a valid reference, 166 """ 167 try: 168 return self.objects.getReferenceTo(obj) 169 except ReferenceError: 170 raise ReferenceError("Object %r not a valid reference" % (obj,))
171
172 - def addObject(self, obj):
173 """ 174 Adds a reference to C{obj}. 175 176 @type obj: C{mixed} 177 @param obj: The object to add to the context. 178 @rtype: C{int} 179 @return: Reference to C{obj}. 180 """ 181 return self.objects.append(obj)
182
183 - def getClassAlias(self, klass):
184 """ 185 Gets a class alias based on the supplied C{klass}. 186 """ 187 if klass not in self.class_aliases.keys(): 188 try: 189 self.class_aliases[klass] = get_class_alias(klass) 190 except UnknownClassAlias: 191 # no alias has been found yet .. check subclasses 192 alias = util.get_class_alias(klass) 193 194 if alias is not None: 195 self.class_aliases[klass] = alias(klass, None) 196 else: 197 self.class_aliases[klass] = None 198 199 return self.class_aliases[klass]
200
201 - def __copy__(self):
202 raise NotImplementedError
203
204 -class ASObject(dict):
205 """ 206 This class represents a Flash Actionscript Object (typed or untyped). 207 208 I supply a C{__builtin__.dict} interface to support C{get}/C{setattr} 209 calls. 210 211 @raise AttributeError: Unknown attribute. 212 """ 213
214 - def __init__(self, *args, **kwargs):
215 dict.__init__(self, *args, **kwargs)
216
217 - def __getattr__(self, k):
218 try: 219 return self[k] 220 except KeyError: 221 raise AttributeError('Unknown attribute \'%s\'' % (k,))
222
223 - def __setattr__(self, k, v):
224 self[k] = v
225
226 - def __repr__(self):
227 return dict.__repr__(self)
228
229 - def __hash__(self):
230 return id(self)
231
232 -class MixedArray(dict):
233 """ 234 Used to be able to specify the C{mixedarray} type. 235 """
236
237 -class ClassMetaData(list):
238 """ 239 I hold a list of tags relating to the class. The idea behind this is 240 to emulate the metadata tags you can supply to ActionScript, 241 e.g. C{static}/C{dynamic}. 242 """ 243 244 _allowed_tags = ( 245 ('static', 'dynamic', 'external'), 246 ('amf3', 'amf0'), 247 ('anonymous',), 248 ) 249
250 - def __init__(self, *args):
251 if len(args) == 1 and hasattr(args[0], '__iter__'): 252 for x in args[0]: 253 self.append(x) 254 else: 255 for x in args: 256 self.append(x)
257
258 - def _is_tag_allowed(self, x):
259 for y in self._allowed_tags: 260 if isinstance(y, (types.ListType, types.TupleType)): 261 if x in y: 262 return (True, y) 263 else: 264 if x == y: 265 return (True, None) 266 267 return (False, None)
268
269 - def append(self, x):
270 """ 271 Adds a tag to the metadata. 272 273 @param x: Tag. 274 @raise ValueError: Unknown tag. 275 """ 276 x = str(x).lower() 277 278 allowed, tags = self._is_tag_allowed(x) 279 280 if not allowed: 281 raise ValueError("Unknown tag %s" % (x,)) 282 283 if tags is not None: 284 # check to see if a tag in the list is about to be clobbered if so, 285 # raise a warning 286 for y in tags: 287 if y not in self: 288 continue 289 290 if x != y: 291 import warnings 292 293 warnings.warn( 294 "Previously defined tag %s superceded by %s" % (y, x)) 295 296 list.pop(self, self.index(y)) 297 break 298 299 list.append(self, x)
300
301 - def __contains__(self, other):
302 return list.__contains__(self, str(other).lower())
303 304 # TODO nick: deal with slices 305
306 -class ClassAlias(object):
307 """ 308 Class alias. 309 310 All classes are initially set to a dynamic state. 311 312 @ivar attrs: A list of attributes to encode for this class. 313 @type attrs: C{list} 314 @ivar metadata: A list of metadata tags similar to ActionScript tags. 315 @type metadata: C{list} 316 """ 317
318 - def __init__(self, klass, alias, attrs=None, attr_func=None, metadata=[]):
319 """ 320 @type klass: C{class} 321 @param klass: The class to alias. 322 @type alias: C{str} 323 @param alias: The alias to the class e.g. C{org.example.Person}. If the 324 value of this is C{None}, then it is worked out based on the C{klass}. 325 The anonymous tag is also added to the class. 326 @type attrs: A list of attributes to encode for this class. 327 @param attrs: C{list} 328 @type metadata: A list of metadata tags similar to ActionScript tags. 329 @param metadata: C{list} 330 331 @raise TypeError: The C{klass} must be a class type. 332 @raise TypeError: The C{attr_func} must be callable. 333 @raise TypeError: C{__readamf__} must be callable. 334 @raise TypeError: C{__writeamf__} must be callable. 335 @raise AttributeError: An externalised class was specified, but no 336 C{__readamf__} attribute was found. 337 @raise AttributeError: An externalised class was specified, but no 338 C{__writeamf__} attribute was found. 339 @raise ValueError: The C{attrs} keyword must be specified for static 340 classes. 341 """ 342 if not isinstance(klass, (type, types.ClassType)): 343 raise TypeError("klass must be a class type") 344 345 self.checkClass(klass) 346 347 self.metadata = ClassMetaData(metadata) 348 349 if alias is None: 350 self.metadata.append('anonymous') 351 alias = "%s.%s" % (klass.__module__, klass.__name__,) 352 353 self.klass = klass 354 self.alias = alias 355 self.attr_func = attr_func 356 self.attrs = attrs 357 358 if 'external' in self.metadata: 359 # class is declared as external, lets check 360 if not hasattr(klass, '__readamf__'): 361 raise AttributeError("An externalised class was specified, but" 362 " no __readamf__ attribute was found for class %s" % ( 363 klass.__name__)) 364 365 if not hasattr(klass, '__writeamf__'): 366 raise AttributeError("An externalised class was specified, but" 367 " no __writeamf__ attribute was found for class %s" % ( 368 klass.__name__)) 369 370 if not isinstance(klass.__readamf__, types.UnboundMethodType): 371 raise TypeError("%s.__readamf__ must be callable" % ( 372 klass.__name__)) 373 374 if not isinstance(klass.__writeamf__, types.UnboundMethodType): 375 raise TypeError("%s.__writeamf__ must be callable" % ( 376 klass.__name__)) 377 378 if 'dynamic' in self.metadata: 379 if attr_func is not None and not callable(attr_func): 380 raise TypeError("attr_func must be callable") 381 382 if 'static' in self.metadata: 383 if attrs is None: 384 raise ValueError("attrs keyword must be specified for static classes")
385
386 - def __str__(self):
387 return self.alias
388
389 - def __repr__(self):
390 return '<ClassAlias alias=%s klass=%s @ %s>' % ( 391 self.alias, self.klass, id(self))
392
393 - def __eq__(self, other):
394 if isinstance(other, basestring): 395 return self.alias == other 396 elif isinstance(other, self.__class__): 397 return self.klass == other.klass 398 elif isinstance(other, (type, types.ClassType)): 399 return self.klass == other 400 else: 401 return False
402
403 - def __hash__(self):
404 return id(self)
405
406 - def checkClass(kls, klass):
407 """ 408 This function is used to check the class being aliased to fits certain 409 criteria. The default is to check that the __init__ constructor does 410 not pass in arguments. 411 412 @since: 0.4 413 @raise TypeError: C{__init__} doesn't support additional arguments 414 """ 415 # Check that the constructor of the class doesn't require any additonal 416 # arguments. 417 if not (hasattr(klass, '__init__') and hasattr(klass.__init__, 'im_func')): 418 return 419 420 klass_func = klass.__init__.im_func 421 422 # built-in classes don't have func_code 423 if hasattr(klass_func, 'func_code') and ( 424 klass_func.func_code.co_argcount - len(klass_func.func_defaults or []) > 1): 425 args = list(klass_func.func_code.co_varnames) 426 values = list(klass_func.func_defaults or []) 427 428 if not values: 429 sign = "%s.__init__(%s)" % (klass.__name__, ", ".join(args)) 430 else: 431 named_args = zip(args[len(args) - len(values):], values) 432 sign = "%s.__init__(%s, %s)" % ( 433 klass.__name__, 434 ", ".join(args[:0-len(values)]), 435 ", ".join(map(lambda x: "%s=%s" % (x,), named_args))) 436 437 raise TypeError("__init__ doesn't support additional arguments: %s" 438 % sign)
439 440 checkClass = classmethod(checkClass) 441
442 - def _getAttrs(self, obj, static_attrs=None, dynamic_attrs=None, traverse=True):
443 if static_attrs is None: 444 static_attrs = [] 445 446 if dynamic_attrs is None: 447 dynamic_attrs = [] 448 449 modified_attrs = False 450 451 if self.attrs is not None: 452 modified_attrs = True 453 static_attrs.extend(self.attrs) 454 elif traverse is True and hasattr(obj, '__slots__'): 455 modified_attrs = True 456 static_attrs.extend(obj.__slots__) 457 458 if self.attr_func is not None: 459 modified_attrs = True 460 extra_attrs = self.attr_func(obj) 461 462 dynamic_attrs.extend([key for key in extra_attrs if key not in static_attrs]) 463 464 if traverse is True: 465 for base in util.get_mro(obj.__class__): 466 try: 467 alias = get_class_alias(base) 468 except UnknownClassAlias: 469 continue 470 471 x, y = alias._getAttrs(obj, static_attrs, dynamic_attrs, False) 472 473 if x is not None: 474 static_attrs.extend(x) 475 modified_attrs = True 476 477 if y is not None: 478 dynamic_attrs.extend(y) 479 modified_attrs = True 480 481 if modified_attrs is False: 482 return None, None 483 484 sa = [] 485 da = [] 486 487 for x in static_attrs: 488 if x not in sa: 489 sa.append(x) 490 491 for x in dynamic_attrs: 492 if x not in da: 493 da.append(x) 494 495 return (sa, da)
496
497 - def getAttrs(self, obj, codec=None):
498 """ 499 Returns a tuple of lists, static and dynamic attrs to encode. 500 501 @param codec: An optional argument that will contain the en/decoder 502 instance calling this function. 503 """ 504 return self._getAttrs(obj)
505
506 - def getAttributes(self, obj, codec=None):
507 """ 508 Returns a collection of attributes for an object. 509 Returns a C{tuple} containing a dict of static and dynamic attributes 510 511 @param codec: An optional argument that will contain the en/decoder 512 instance calling this function. 513 """ 514 dynamic_attrs = {} 515 static_attrs = {} 516 static_attr_names, dynamic_attr_names = self.getAttrs(obj, codec=codec) 517 518 if static_attr_names is None and dynamic_attr_names is None: 519 dynamic_attrs = util.get_attrs(obj) 520 521 if static_attr_names is not None: 522 for attr in static_attr_names: 523 if hasattr(obj, attr): 524 static_attrs[attr] = getattr(obj, attr) 525 else: 526 static_attrs[attr] = Undefined 527 528 if dynamic_attr_names is not None: 529 for attr in dynamic_attr_names: 530 if attr in static_attrs: 531 continue 532 533 if hasattr(obj, attr): 534 dynamic_attrs[attr] = getattr(obj, attr) 535 536 return (static_attrs, dynamic_attrs)
537
538 - def applyAttributes(self, obj, attrs, codec=None):
539 """ 540 Applies the collection of attributes C{attrs} to aliased object C{obj}. 541 It is mainly used when reading aliased objects from an AMF byte stream. 542 543 @param codec: An optional argument that will contain the en/decoder 544 instance calling this function. 545 """ 546 if 'static' in self.metadata: 547 s, d = self.getAttrs(obj, codec=codec) 548 549 if s is not None: 550 for k in attrs.keys(): 551 if k not in s: 552 del attrs[k] 553 554 util.set_attrs(obj, attrs)
555
556 - def createInstance(self, codec=None, *args, **kwargs):
557 """ 558 Creates an instance of the klass. 559 560 @return: Instance of C{self.klass}. 561 """ 562 return self.klass(*args, **kwargs)
563
564 -class TypedObject(dict):
565 """ 566 This class is used when a strongly typed object is decoded but there is no 567 registered class to apply it to. 568 569 This object can only be used for 'simple' streams - i.e. not externalized 570 data. If encountered, a L{DecodeError} will be raised. 571 572 @ivar alias: The alias of the typed object. 573 @type alias: C{unicode} 574 575 @since: 0.4 576 """ 577
578 - def __init__(self, alias):
579 dict.__init__(self) 580 581 self.alias = alias
582
583 - def __readamf__(self, o):
584 raise DecodeError('Unable to decode an externalised stream.\n\nThe ' 585 'class alias \'%s\' was found and because strict mode is False an' 586 ' attempt was made to decode the object automatically. To decode ' 587 'this stream, a registered class with the alias and a ' 588 'corresponding __readamf__ method will be required.' % ( 589 self.alias,))
590
591 - def __writeamf__(self, o):
592 raise EncodeError('Unable to encode an externalised stream.\n\nThe ' 593 'class alias \'%s\' was found and because strict mode is False an' 594 'attempt was made to encode the object automatically. To encode ' 595 'this stream, a registered class with the alias and a ' 596 'corresponding __readamf__ method will be required.' % ( 597 self.alias,))
598
599 -class TypedObjectClassAlias(ClassAlias):
600 """ 601 @since: 0.4 602 """ 603
604 - def createInstance(self, codec=None):
605 return TypedObject(self.alias)
606
607 - def checkClass(kls, klass):
608 pass
609
610 -class BaseDecoder(object):
611 """ 612 Base AMF decoder. 613 614 @ivar context_class: The context for the decoding. 615 @type context_class: An instance of C{BaseDecoder.context_class} 616 @ivar type_map: 617 @type type_map: C{list} 618 @ivar stream: The underlying data stream. 619 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 620 @ivar strict: Defines how strict the decoding should be. For the time 621 being this relates to typed objects in the stream that do not have a 622 registered alias. Introduced in 0.4. 623 @type strict: C{bool} 624 """ 625 626 context_class = BaseContext 627 type_map = {} 628
629 - def __init__(self, data=None, context=None, strict=False):
630 """ 631 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 632 @param data: Data stream. 633 @type context: L{Context<pyamf.amf0.Context>} 634 @param context: Context. 635 @raise TypeError: The C{context} parameter must be of 636 type L{Context<pyamf.amf0.Context>}. 637 """ 638 # coerce data to BufferedByteStream 639 if isinstance(data, util.BufferedByteStream): 640 self.stream = data 641 else: 642 self.stream = util.BufferedByteStream(data) 643 644 if context == None: 645 self.context = self.context_class() 646 elif isinstance(context, self.context_class): 647 self.context = context 648 else: 649 raise TypeError("context must be of type %s.%s" % ( 650 self.context_class.__module__, self.context_class.__name__)) 651 652 self.strict = strict
653
654 - def readType(self):
655 """ 656 @raise NotImplementedError: Override in a subclass. 657 """ 658 raise NotImplementedError
659
660 - def readElement(self):
661 """ 662 Reads an AMF3 element from the data stream. 663 664 @raise DecodeError: The ActionScript type is unsupported. 665 @raise EOStream: No more data left to decode. 666 """ 667 try: 668 type = self.readType() 669 except EOFError: 670 raise EOStream 671 672 try: 673 func = getattr(self, self.type_map[type]) 674 except KeyError: 675 raise DecodeError("Unsupported ActionScript type 0x%02x" % (type,)) 676 677 return func()
678
679 - def __iter__(self):
680 """ 681 @raise StopIteration: 682 """ 683 try: 684 while 1: 685 yield self.readElement() 686 except EOFError: 687 raise StopIteration
688
689 -class CustomTypeFunc(object):
690 """ 691 Custom type mappings. 692 """
693 - def __init__(self, encoder, func):
694 self.encoder = encoder 695 self.func = func
696
697 - def __call__(self, data):
698 self.encoder.writeElement(self.func(data, encoder=self.encoder))
699
700 -class BaseEncoder(object):
701 """ 702 Base AMF encoder. 703 704 @ivar type_map: A list of types -> functions. The types is a list of 705 possible instances or functions to call (that return a C{bool}) to 706 determine the correct function to call to encode the data. 707 @type type_map: C{list} 708 @ivar context_class: Holds the class that will create context objects for 709 the implementing C{Encoder}. 710 @type context_class: C{type} or C{types.ClassType} 711 @ivar stream: The underlying data stream. 712 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 713 @ivar context: The context for the encoding. 714 @type context: An instance of C{BaseEncoder.context_class} 715 @ivar strict: Whether the encoder should operate in 'strict' mode. Nothing 716 is really affected by this for the time being - its just here for 717 flexibility. 718 @type strict: C{bool}, default is False. 719 """ 720 context_class = BaseContext 721 type_map = [] 722
723 - def __init__(self, data=None, context=None, strict=False):
724 """ 725 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 726 @param data: Data stream. 727 @type context: L{Context<pyamf.amf0.Context>} 728 @param context: Context. 729 @raise TypeError: The C{context} parameter must be of type 730 L{Context<pyamf.amf0.Context>}. 731 """ 732 # coerce data to BufferedByteStream 733 if isinstance(data, util.BufferedByteStream): 734 self.stream = data 735 else: 736 self.stream = util.BufferedByteStream(data) 737 738 if context == None: 739 self.context = self.context_class() 740 elif isinstance(context, self.context_class): 741 self.context = context 742 else: 743 raise TypeError("context must be of type %s.%s" % ( 744 self.context_class.__module__, self.context_class.__name__)) 745 746 self._write_elem_func_cache = {} 747 self.strict = strict
748
749 - def writeFunc(self, obj):
750 """ 751 Not possible to encode functions. 752 753 @raise EncodeError: Unable to encode function/methods. 754 """ 755 raise EncodeError("Unable to encode function/methods")
756
757 - def _getWriteElementFunc(self, data):
758 """ 759 Gets a function used to encode the data. 760 761 @type data: C{mixed} 762 @param data: Python data. 763 @rtype: callable or C{None}. 764 @return: The function used to encode data to the stream. 765 """ 766 for type_, func in TYPE_MAP.iteritems(): 767 try: 768 if isinstance(data, type_): 769 return CustomTypeFunc(self, func) 770 except TypeError: 771 if callable(type_) and type_(data): 772 return CustomTypeFunc(self, func) 773 774 for tlist, method in self.type_map: 775 for t in tlist: 776 try: 777 if isinstance(data, t): 778 return getattr(self, method) 779 except TypeError: 780 if callable(t) and t(data): 781 return getattr(self, method) 782 783 return None
784
785 - def _writeElementFunc(self, data):
786 """ 787 Gets a function used to encode the data. 788 789 @type data: C{mixed} 790 @param data: Python data. 791 @rtype: callable or C{None}. 792 @return: The function used to encode data to the stream. 793 """ 794 try: 795 key = data.__class__ 796 except AttributeError: 797 return self._getWriteElementFunc(data) 798 799 if key not in self._write_elem_func_cache: 800 self._write_elem_func_cache[key] = self._getWriteElementFunc(data) 801 802 return self._write_elem_func_cache[key]
803
804 - def writeElement(self, data):
805 """ 806 Writes the data. Overridden in subclass. 807 808 @type data: C{mixed} 809 @param data: The data to be encoded to the data stream. 810 """ 811 raise NotImplementedError
812
813 -def register_class(klass, alias=None, attrs=None, attr_func=None, metadata=[]):
814 """ 815 Registers a class to be used in the data streaming. 816 817 @type alias: C{str} 818 @param alias: The alias of klass, i.e. C{org.example.Person}. 819 @param attrs: A list of attributes that will be encoded for the class. 820 @type attrs: C{list} or C{None} 821 @type attr_func: 822 @param attr_func: 823 @type metadata: 824 @param metadata: 825 @raise TypeError: PyAMF doesn't support required init arguments. 826 @raise TypeError: The C{klass} is not callable. 827 @raise ValueError: The C{klass} or C{alias} is already registered. 828 @return: The registered L{ClassAlias}. 829 """ 830 if not callable(klass): 831 raise TypeError("klass must be callable") 832 833 if klass in CLASS_CACHE: 834 raise ValueError("klass %s already registered" % (klass,)) 835 836 if alias is not None and alias in CLASS_CACHE.keys(): 837 raise ValueError("alias '%s' already registered" % (alias,)) 838 839 alias_klass = util.get_class_alias(klass) 840 841 if alias_klass is None: 842 alias_klass = ClassAlias 843 844 x = alias_klass(klass, alias, attr_func=attr_func, 845 attrs=attrs, metadata=metadata) 846 847 if alias is None: 848 alias = "%s.%s" % (klass.__module__, klass.__name__,) 849 850 CLASS_CACHE[alias] = x 851 852 return x
853
854 -def unregister_class(alias):
855 """ 856 Deletes a class from the cache. 857 858 If C{alias} is a class, the matching alias is found. 859 860 @type alias: C{class} or C{str} 861 @param alias: Alias for class to delete. 862 @raise UnknownClassAlias: Unknown alias. 863 """ 864 if isinstance(alias, (type, types.ClassType)): 865 for s, a in CLASS_CACHE.iteritems(): 866 if a.klass == alias: 867 alias = s 868 break 869 try: 870 del CLASS_CACHE[alias] 871 except KeyError: 872 raise UnknownClassAlias("Unknown alias %s" % (alias,))
873
874 -def register_class_loader(loader):
875 """ 876 Registers a loader that is called to provide the C{Class} for a specific 877 alias. 878 879 The L{loader} is provided with one argument, the C{Class} alias. If the 880 loader succeeds in finding a suitable class then it should return that 881 class, otherwise it should return C{None}. 882 883 @type loader: C{callable} 884 @raise TypeError: The C{loader} is not callable. 885 @raise ValueError: The C{loader} is already registered. 886 """ 887 if not callable(loader): 888 raise TypeError("loader must be callable") 889 890 if loader in CLASS_LOADERS: 891 raise ValueError("loader has already been registered") 892 893 CLASS_LOADERS.append(loader)
894
895 -def unregister_class_loader(loader):
896 """ 897 Unregisters a class loader. 898 899 @type loader: C{callable} 900 @param loader: The object to be unregistered 901 902 @raise LookupError: The C{loader} was not registered. 903 """ 904 if loader not in CLASS_LOADERS: 905 raise LookupError("loader not found") 906 907 del CLASS_LOADERS[CLASS_LOADERS.index(loader)]
908
909 -def get_module(mod_name):
910 """ 911 Load a module based on C{mod_name}. 912 913 @type mod_name: C{str} 914 @param mod_name: The module name. 915 @return: Module. 916 917 @raise ImportError: Unable to import an empty module. 918 """ 919 if mod_name is '': 920 raise ImportError("Unable to import empty module") 921 922 mod = __import__(mod_name) 923 components = mod_name.split('.') 924 925 for comp in components[1:]: 926 mod = getattr(mod, comp) 927 928 return mod
929
930 -def load_class(alias):
931 """ 932 Finds the class registered to the alias. 933 934 The search is done in order: 935 1. Checks if the class name has been registered via L{register_class}. 936 2. Checks all functions registered via L{register_class_loader}. 937 3. Attempts to load the class via standard module loading techniques. 938 939 @type alias: C{str} 940 @param alias: The class name. 941 @raise UnknownClassAlias: The C{alias} was not found. 942 @raise TypeError: Expecting class type or L{ClassAlias} from loader. 943 @return: Class registered to the alias. 944 """ 945 alias = str(alias) 946 947 # Try the CLASS_CACHE first 948 try: 949 return CLASS_CACHE[alias] 950 except KeyError: 951 pass 952 953 # Check each CLASS_LOADERS in turn 954 for loader in CLASS_LOADERS: 955 klass = loader(alias) 956 957 if klass is None: 958 continue 959 960 if isinstance(klass, (type, types.ClassType)): 961 return register_class(klass, alias) 962 elif isinstance(klass, ClassAlias): 963 CLASS_CACHE[str(alias)] = klass 964 else: 965 raise TypeError("Expecting class type or ClassAlias from loader") 966 967 return klass 968 969 # XXX nick: Are there security concerns for loading classes this way? 970 mod_class = alias.split('.') 971 972 if mod_class: 973 module = '.'.join(mod_class[:-1]) 974 klass = mod_class[-1] 975 976 try: 977 module = get_module(module) 978 except ImportError, AttributeError: 979 # XXX What to do here? 980 pass 981 else: 982 klass = getattr(module, klass) 983 984 if callable(klass): 985 CLASS_CACHE[alias] = klass 986 987 return klass 988 989 # All available methods for finding the class have been exhausted 990 raise UnknownClassAlias("Unknown alias %s" % (alias,))
991
992 -def get_class_alias(klass):
993 """ 994 Finds the alias registered to the class. 995 996 @type klass: C{object} or class 997 @rtype: L{ClassAlias} 998 @return: The class alias linked to the C{klass}. 999 @raise UnknownClassAlias: Class not found. 1000 @raise TypeError: Expecting C{string} or C{class} type. 1001 """ 1002 if not isinstance(klass, (type, types.ClassType, basestring)): 1003 if isinstance(klass, types.InstanceType): 1004 klass = klass.__class__ 1005 elif isinstance(klass, types.ObjectType): 1006 klass = type(klass) 1007 1008 if isinstance(klass, basestring): 1009 for a, k in CLASS_CACHE.iteritems(): 1010 if klass == a: 1011 return k 1012 else: 1013 for a, k in CLASS_CACHE.iteritems(): 1014 if klass == k.klass: 1015 return k 1016 1017 if isinstance(klass, basestring): 1018 return load_class(klass) 1019 1020 raise UnknownClassAlias("Unknown alias %s" % (klass,))
1021
1022 -def has_alias(obj):
1023 """ 1024 @rtype: C{bool} 1025 @return: Alias is available. 1026 """ 1027 try: 1028 alias = get_class_alias(obj) 1029 return True 1030 except UnknownClassAlias: 1031 return False
1032
1033 -def decode(stream, encoding=AMF0, context=None, strict=False):
1034 """ 1035 A generator function to decode a datastream. 1036 1037 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 1038 @param stream: AMF data. 1039 @type encoding: C{int} 1040 @param encoding: AMF encoding type. 1041 @type context: L{AMF0 Context<pyamf.amf0.Context>} or 1042 L{AMF3 Context<pyamf.amf3.Context>} 1043 @param context: Context. 1044 @return: Each element in the stream. 1045 """ 1046 decoder = _get_decoder_class(encoding)(stream, context, strict) 1047 1048 while 1: 1049 try: 1050 yield decoder.readElement() 1051 except EOStream: 1052 break
1053
1054 -def encode(*args, **kwargs):
1055 """ 1056 A helper function to encode an element. 1057 1058 @type element: C{mixed} 1059 @keyword element: Python data. 1060 @type encoding: C{int} 1061 @keyword encoding: AMF encoding type. 1062 @type context: L{amf0.Context<pyamf.amf0.Context>} or 1063 L{amf3.Context<pyamf.amf3.Context>} 1064 @keyword context: Context. 1065 1066 @rtype: C{StringIO} 1067 @return: File-like object. 1068 """ 1069 encoding = kwargs.get('encoding', AMF0) 1070 context = kwargs.get('context', None) 1071 strict = kwargs.get('strict', False) 1072 1073 stream = util.BufferedByteStream() 1074 encoder = _get_encoder_class(encoding)(stream, context, strict) 1075 1076 for el in args: 1077 encoder.writeElement(el) 1078 1079 stream.seek(0) 1080 1081 return stream
1082
1083 -def get_decoder(encoding, data=None, context=None, strict=False):
1084 return _get_decoder_class(encoding)(data=data, context=context, strict=strict)
1085
1086 -def _get_decoder_class(encoding):
1087 """ 1088 Get compatible decoder. 1089 1090 @type encoding: C{int} 1091 @param encoding: AMF encoding version. 1092 @raise ValueError: AMF encoding version is unknown. 1093 1094 @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or 1095 L{amf3.Decoder<pyamf.amf3.Decoder>} 1096 @return: AMF0 or AMF3 decoder. 1097 """ 1098 if encoding == AMF0: 1099 from pyamf import amf0 1100 1101 return amf0.Decoder 1102 elif encoding == AMF3: 1103 from pyamf import amf3 1104 1105 return amf3.Decoder 1106 1107 raise ValueError("Unknown encoding %s" % (encoding,))
1108
1109 -def get_encoder(encoding, data=None, context=None, strict=False):
1110 """ 1111 Returns a subclassed instance of L{pyamf.BaseEncoder}, based on C{encoding} 1112 """ 1113 return _get_encoder_class(encoding)(data=data, context=context, 1114 strict=strict)
1115
1116 -def _get_encoder_class(encoding):
1117 """ 1118 Get compatible encoder. 1119 1120 @type encoding: C{int} 1121 @param encoding: AMF encoding version. 1122 @raise ValueError: AMF encoding version is unknown. 1123 1124 @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or 1125 L{amf3.Encoder<pyamf.amf3.Encoder>} 1126 @return: AMF0 or AMF3 encoder. 1127 """ 1128 if encoding == AMF0: 1129 from pyamf import amf0 1130 1131 return amf0.Encoder 1132 elif encoding == AMF3: 1133 from pyamf import amf3 1134 1135 return amf3.Encoder 1136 1137 raise ValueError("Unknown encoding %s" % (encoding,))
1138
1139 -def get_context(encoding):
1140 return _get_context_class(encoding)()
1141
1142 -def _get_context_class(encoding):
1143 """ 1144 Gets a compatible context class. 1145 1146 @type encoding: C{int} 1147 @param encoding: AMF encoding version. 1148 @raise ValueError: AMF encoding version is unknown. 1149 1150 @rtype: L{amf0.Context<pyamf.amf0.Context>} or 1151 L{amf3.Context<pyamf.amf3.Context>} 1152 @return: AMF0 or AMF3 context class. 1153 """ 1154 if encoding == AMF0: 1155 from pyamf import amf0 1156 1157 return amf0.Context 1158 elif encoding == AMF3: 1159 from pyamf import amf3 1160 1161 return amf3.Context 1162 1163 raise ValueError("Unknown encoding %s" % (encoding,))
1164
1165 -def flex_loader(alias):
1166 """ 1167 Loader for L{Flex<pyamf.flex>} framework compatibility classes. 1168 1169 @raise UnknownClassAlias: Trying to load unknown Flex compatibility class. 1170 """ 1171 if not alias.startswith('flex.'): 1172 return 1173 1174 try: 1175 if alias.startswith('flex.messaging.messages'): 1176 import pyamf.flex.messaging 1177 elif alias.startswith('flex.messaging.io'): 1178 import pyamf.flex 1179 elif alias.startswith('flex.data.messages'): 1180 import pyamf.flex.data 1181 1182 return CLASS_CACHE[alias] 1183 except KeyError: 1184 raise UnknownClassAlias(alias)
1185
1186 -def add_type(type_, func=None):
1187 """ 1188 Adds a custom type to L{TYPE_MAP}. A custom type allows fine grain control 1189 of what to encode to an AMF data stream. 1190 1191 @raise TypeError: Unable to add as a custom type (expected a class or callable). 1192 @raise KeyError: Type already exists. 1193 """ 1194 def _check_type(type_): 1195 if not (isinstance(type_, (type, types.ClassType)) or callable(type_)): 1196 raise TypeError('Unable to add \'%r\' as a custom type (expected a class or callable)' % (type_,))
1197 1198 if isinstance(type_, list): 1199 type_ = tuple(type_) 1200 1201 if type_ in TYPE_MAP: 1202 raise KeyError('Type %r already exists' % (type_,)) 1203 1204 if isinstance(type_, types.TupleType): 1205 for x in type_: 1206 _check_type(x) 1207 else: 1208 _check_type(type_) 1209 1210 TYPE_MAP[type_] = func 1211
1212 -def get_type(type_):
1213 """ 1214 Gets the declaration for the corresponding custom type. 1215 1216 @raise KeyError: Unknown type. 1217 """ 1218 if isinstance(type_, list): 1219 type_ = tuple(type_) 1220 1221 for (k, v) in TYPE_MAP.iteritems(): 1222 if k == type_: 1223 return v 1224 1225 raise KeyError("Unknown type %r" % (type_,))
1226
1227 -def remove_type(type_):
1228 """ 1229 Removes the custom type declaration. 1230 1231 @return: Custom type declaration. 1232 """ 1233 declaration = get_type(type_) 1234 1235 del TYPE_MAP[type_] 1236 1237 return declaration
1238
1239 -def add_error_class(klass, code):
1240 """ 1241 Maps an exception class to a string code. Used to map remoting C{onStatus} 1242 objects to an exception class so that an exception can be built to 1243 represent that error:: 1244 1245 class AuthenticationError(Exception): 1246 pass 1247 1248 An example: C{add_error_class(AuthenticationError, 'Auth.Failed')} 1249 1250 @type code: C{str} 1251 1252 @raise TypeError: C{klass} must be a C{class} type. 1253 @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class. 1254 @raise ValueError: Code is already registered. 1255 """ 1256 if not isinstance(code, basestring): 1257 code = str(code) 1258 1259 if not isinstance(klass, (type, types.ClassType)): 1260 raise TypeError("klass must be a class type") 1261 1262 mro = util.get_mro(klass) 1263 1264 if not Exception in mro: 1265 raise TypeError('Error classes must subclass the __builtin__.Exception class') 1266 1267 if code in ERROR_CLASS_MAP.keys(): 1268 raise ValueError('Code %s is already registered' % (code,)) 1269 1270 ERROR_CLASS_MAP[code] = klass
1271
1272 -def remove_error_class(klass):
1273 """ 1274 Removes a class from C{ERROR_CLASS_MAP}. 1275 1276 @raise ValueError: Code is not registered. 1277 @raise ValueError: Class is not registered. 1278 @raise TypeError: Invalid type, expected C{class} or C{string}. 1279 """ 1280 if isinstance(klass, basestring): 1281 if not klass in ERROR_CLASS_MAP.keys(): 1282 raise ValueError('Code %s is not registered' % (klass,)) 1283 elif isinstance(klass, (type, types.ClassType)): 1284 classes = ERROR_CLASS_MAP.values() 1285 if not klass in classes: 1286 raise ValueError('Class %s is not registered' % (klass,)) 1287 1288 klass = ERROR_CLASS_MAP.keys()[classes.index(klass)] 1289 else: 1290 raise TypeError("Invalid type, expected class or string") 1291 1292 del ERROR_CLASS_MAP[klass]
1293
1294 -def register_alias_type(klass, *args):
1295 """ 1296 This function allows you to map subclasses of L{ClassAlias} to classes 1297 listed in C{args}. 1298 1299 When an object is read/written from/to the AMF stream, a paired 1300 L{ClassAlias} instance is created (or reused), based on the Python class 1301 of that object. L{ClassAlias} provides important metadata for the class 1302 and can also control how the equivalent Python object is created, how the 1303 attributes are applied etc. 1304 1305 Use this function if you need to do something non-standard. 1306 1307 @see: L{pyamf.adapters._google_appengine_ext_db.DataStoreClassAlias} for a 1308 good example. 1309 @since: 0.4 1310 @raise RuntimeError: Type is already registered. 1311 @raise TypeError: C{klass} must be a class. 1312 @raise ValueError: New aliases must subclass L{pyamf.ClassAlias}. 1313 @raise ValueError: At least one type must be supplied. 1314 """ 1315 def check_type_registered(arg): 1316 # FIXME: Create a reverse index of registered types and do a quicker lookup 1317 for k, v in ALIAS_TYPES.iteritems(): 1318 for kl in v: 1319 if arg is kl: 1320 raise RuntimeError('%r is already registered under %r' % (arg, k))
1321 1322 if not isinstance(klass, (type, types.ClassType)): 1323 raise TypeError('klass must be class') 1324 1325 if not issubclass(klass, ClassAlias): 1326 raise ValueError('New aliases must subclass pyamf.ClassAlias') 1327 1328 if len(args) == 0: 1329 raise ValueError('At least one type must be supplied') 1330 1331 if len(args) == 1 and callable(args[0]): 1332 c = args[0] 1333 1334 check_type_registered(c) 1335 else: 1336 for arg in args: 1337 if not isinstance(arg, (type, types.ClassType)): 1338 raise TypeError('%r must be class' % (arg,)) 1339 1340 check_type_registered(arg) 1341 1342 ALIAS_TYPES[klass] = args 1343 1344 register_class_loader(flex_loader) 1345 register_adapters() 1346 1347 register_alias_type(TypedObjectClassAlias, TypedObject) 1348