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