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

Source Code for Package pyamf

   1  # Copyright (c) 2007-2009 The PyAMF Project. 
   2  # See LICENSE.txt 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 Adobe 
   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{users@pyamf.org<mailto:users@pyamf.org>} 
  12  @see: U{http://pyamf.org} 
  13   
  14  @since: October 2007 
  15  @version: 0.5.1 
  16  @status: Production/Stable 
  17  """ 
  18   
  19  import types 
  20  import inspect 
  21   
  22  from pyamf import util 
  23  from pyamf.adapters import register_adapters 
  24   
  25   
  26  try: 
  27      set 
  28  except NameError: 
  29      from sets import Set as set 
  30   
  31   
  32  __all__ = [ 
  33      'register_class', 
  34      'register_class_loader', 
  35      'encode', 
  36      'decode', 
  37      '__version__' 
  38  ] 
  39   
  40  #: PyAMF version number. 
  41  __version__ = (0, 5, 1) 
  42   
  43  #: Class mapping support. 
  44  CLASS_CACHE = {} 
  45  #: Class loaders. 
  46  CLASS_LOADERS = [] 
  47  #: Custom type map. 
  48  TYPE_MAP = {} 
  49  #: Maps error classes to string codes. 
  50  ERROR_CLASS_MAP = {} 
  51  #: Alias mapping support 
  52  ALIAS_TYPES = {} 
  53   
  54  #: Specifies that objects are serialized using AMF for ActionScript 1.0 
  55  #: and 2.0 that were introduced in the Adobe Flash Player 6. 
  56  AMF0 = 0 
  57  #: Specifies that objects are serialized using AMF for ActionScript 3.0 
  58  #: that was introduced in the Adobe Flash Player 9. 
  59  AMF3 = 3 
  60  #: Supported AMF encoding types. 
  61  ENCODING_TYPES = (AMF0, AMF3) 
  62   
  63  #: Default encoding 
  64  DEFAULT_ENCODING = AMF0 
  65   
  66   
67 -class ClientTypes:
68 """ 69 Typecodes used to identify AMF clients and servers. 70 71 @see: U{Adobe Flash Player on WikiPedia (external) 72 <http://en.wikipedia.org/wiki/Flash_Player>} 73 @see: U{Adobe Flash Media Server on WikiPedia (external) 74 <http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server>} 75 """ 76 #: Specifies a Adobe Flash Player 6.0 - 8.0 client. 77 Flash6 = 0 78 #: Specifies a Adobe FlashCom / Flash Media Server client. 79 FlashCom = 1 80 #: Specifies a Adobe Flash Player 9.0 client or newer. 81 Flash9 = 3
82 83 84 #: List of AMF client typecodes. 85 CLIENT_TYPES = [] 86 87 for x in ClientTypes.__dict__: 88 if not x.startswith('_'): 89 CLIENT_TYPES.append(ClientTypes.__dict__[x]) 90 del x 91 92
93 -class UndefinedType(object):
94
95 - def __repr__(self):
96 return 'pyamf.Undefined'
97 98 #: Represents the C{undefined} value in a Adobe Flash Player client. 99 Undefined = UndefinedType() 100 101
102 -class BaseError(Exception):
103 """ 104 Base AMF Error. 105 106 All AMF related errors should be subclassed from this class. 107 """
108 109
110 -class DecodeError(BaseError):
111 """ 112 Raised if there is an error in decoding an AMF data stream. 113 """
114 115
116 -class EOStream(BaseError):
117 """ 118 Raised if the data stream has come to a natural end. 119 """
120 121
122 -class ReferenceError(BaseError):
123 """ 124 Raised if an AMF data stream refers to a non-existent object 125 or string reference. 126 """
127 128
129 -class EncodeError(BaseError):
130 """ 131 Raised if the element could not be encoded to the stream. 132 133 @bug: See U{Docuverse blog (external) 134 <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>} 135 for more info about the empty key string array bug. 136 """
137 138
139 -class ClassAliasError(BaseError):
140 """ 141 Generic error for anything class alias related. 142 """
143 144
145 -class UnknownClassAlias(ClassAliasError):
146 """ 147 Raised if the AMF stream specifies an Actionscript class that does not 148 have a Python class alias. 149 150 @see: L{register_class} 151 """
152 153
154 -class BaseContext(object):
155 """ 156 I hold the AMF context for en/decoding streams. 157 158 @ivar objects: An indexed collection of referencable objects encountered 159 during en/decoding. 160 @type objects: L{util.IndexedCollection} 161 @ivar class_aliases: A L{dict} of C{class} to L{ClassAlias} 162 @ivar exceptions: If C{True} then reference errors will be propagated. 163 @type exceptions: C{bool} 164 """ 165
166 - def __init__(self, exceptions=True):
167 self.objects = util.IndexedCollection(exceptions=False) 168 self.clear() 169 170 self.exceptions = exceptions
171
172 - def clear(self):
173 """ 174 Completely clears the context. 175 """ 176 self.objects.clear() 177 self.class_aliases = {}
178
179 - def getObject(self, ref):
180 """ 181 Gets an object based on a reference. 182 183 @raise ReferenceError: Unknown object reference, if L{exceptions} is 184 C{True}, otherwise C{None} will be returned. 185 """ 186 o = self.objects.getByReference(ref) 187 188 if o is None and self.exceptions: 189 raise ReferenceError("Unknown object reference %r" % (ref,)) 190 191 return o
192
193 - def getObjectReference(self, obj):
194 """ 195 Gets a reference for an object. 196 197 @raise ReferenceError: Object not a valid reference, 198 """ 199 o = self.objects.getReferenceTo(obj) 200 201 if o is None and self.exceptions: 202 raise ReferenceError("Object %r not a valid reference" % (obj,)) 203 204 return o
205
206 - def addObject(self, obj):
207 """ 208 Adds a reference to C{obj}. 209 210 @type obj: C{mixed} 211 @param obj: The object to add to the context. 212 @rtype: C{int} 213 @return: Reference to C{obj}. 214 """ 215 return self.objects.append(obj)
216
217 - def getClassAlias(self, klass):
218 """ 219 Gets a class alias based on the supplied C{klass}. 220 """ 221 try: 222 return self.class_aliases[klass] 223 except KeyError: 224 pass 225 226 try: 227 self.class_aliases[klass] = get_class_alias(klass) 228 except UnknownClassAlias: 229 # no alias has been found yet .. check subclasses 230 alias = util.get_class_alias(klass) 231 232 self.class_aliases[klass] = alias(klass) 233 234 return self.class_aliases[klass]
235
236 - def __copy__(self):
237 raise NotImplementedError
238 239
240 -class ASObject(dict):
241 """ 242 This class represents a Flash Actionscript Object (typed or untyped). 243 244 I supply a C{__builtin__.dict} interface to support C{get}/C{setattr} 245 calls. 246 247 @raise AttributeError: Unknown attribute. 248 """ 249
250 - class __amf__:
251 dynamic = True
252
253 - def __init__(self, *args, **kwargs):
254 dict.__init__(self, *args, **kwargs)
255
256 - def __getattr__(self, k):
257 try: 258 return self[k] 259 except KeyError: 260 raise AttributeError('Unknown attribute \'%s\'' % (k,))
261
262 - def __setattr__(self, k, v):
263 self[k] = v
264
265 - def __repr__(self):
266 return dict.__repr__(self)
267
268 - def __hash__(self):
269 return id(self)
270 271
272 -class MixedArray(dict):
273 """ 274 Used to be able to specify the C{mixedarray} type. 275 """
276 277
278 -class ClassAlias(object):
279 """ 280 Class alias. Provides class/instance meta data to the En/Decoder to allow 281 fine grain control and some performance increases. 282 """ 283
284 - def __init__(self, klass, alias=None, **kwargs):
285 if not isinstance(klass, (type, types.ClassType)): 286 raise TypeError('klass must be a class type, got %r' % type(klass)) 287 288 self.checkClass(klass) 289 290 self.klass = klass 291 self.alias = alias 292 293 self.static_attrs = kwargs.get('static_attrs', None) 294 self.exclude_attrs = kwargs.get('exclude_attrs', None) 295 self.readonly_attrs = kwargs.get('readonly_attrs', None) 296 self.proxy_attrs = kwargs.get('proxy_attrs', None) 297 self.amf3 = kwargs.get('amf3', None) 298 self.external = kwargs.get('external', None) 299 self.dynamic = kwargs.get('dynamic', None) 300 301 self._compiled = False 302 self.anonymous = False 303 self.sealed = None 304 305 if self.alias is None: 306 self.anonymous = True 307 # we don't set this to None because AMF3 untyped objects have a 308 # class name of '' 309 self.alias = '' 310 else: 311 if self.alias == '': 312 raise ValueError('Cannot set class alias as \'\'') 313 314 if not kwargs.get('defer', False): 315 self.compile()
316
317 - def _checkExternal(self):
318 if not hasattr(self.klass, '__readamf__'): 319 raise AttributeError("An externalised class was specified, but" 320 " no __readamf__ attribute was found for %r" % (self.klass,)) 321 322 if not hasattr(self.klass, '__writeamf__'): 323 raise AttributeError("An externalised class was specified, but" 324 " no __writeamf__ attribute was found for %r" % (self.klass,)) 325 326 if not isinstance(self.klass.__readamf__, types.UnboundMethodType): 327 raise TypeError("%s.__readamf__ must be callable" % ( 328 self.klass.__name__,)) 329 330 if not isinstance(self.klass.__writeamf__, types.UnboundMethodType): 331 raise TypeError("%s.__writeamf__ must be callable" % ( 332 self.klass.__name__,))
333
334 - def compile(self):
335 """ 336 This compiles the alias into a form that can be of most benefit to the 337 en/decoder. 338 """ 339 if self._compiled: 340 return 341 342 self.decodable_properties = set() 343 self.encodable_properties = set() 344 self.inherited_dynamic = None 345 self.inherited_sealed = None 346 347 self.exclude_attrs = set(self.exclude_attrs or []) 348 self.readonly_attrs = set(self.readonly_attrs or []) 349 self.static_attrs = set(self.static_attrs or []) 350 self.proxy_attrs = set(self.proxy_attrs or []) 351 352 if self.external: 353 self._checkExternal() 354 self._finalise_compile() 355 356 # this class is external so no more compiling is necessary 357 return 358 359 self.sealed = util.is_class_sealed(self.klass) 360 361 if hasattr(self.klass, '__slots__'): 362 self.decodable_properties.update(self.klass.__slots__) 363 self.encodable_properties.update(self.klass.__slots__) 364 365 for k, v in self.klass.__dict__.iteritems(): 366 if not isinstance(v, property): 367 continue 368 369 if v.fget: 370 self.encodable_properties.update([k]) 371 372 if v.fset: 373 self.decodable_properties.update([k]) 374 else: 375 self.readonly_attrs.update([k]) 376 377 mro = inspect.getmro(self.klass)[1:] 378 379 try: 380 self._compile_base_class(mro[0]) 381 except IndexError: 382 pass 383 384 self.getCustomProperties() 385 386 self._finalise_compile()
387
388 - def _compile_base_class(self, klass):
389 if klass is object: 390 return 391 392 try: 393 alias = get_class_alias(klass) 394 except UnknownClassAlias: 395 alias = register_class(klass) 396 397 alias.compile() 398 399 if alias.exclude_attrs: 400 self.exclude_attrs.update(alias.exclude_attrs) 401 402 if alias.readonly_attrs: 403 self.readonly_attrs.update(alias.readonly_attrs) 404 405 if alias.static_attrs: 406 self.static_attrs.update(alias.static_attrs) 407 408 if alias.proxy_attrs: 409 self.proxy_attrs.update(alias.proxy_attrs) 410 411 if alias.encodable_properties: 412 self.encodable_properties.update(alias.encodable_properties) 413 414 if alias.decodable_properties: 415 self.decodable_properties.update(alias.decodable_properties) 416 417 if self.amf3 is None and alias.amf3: 418 self.amf3 = alias.amf3 419 420 if self.dynamic is None and alias.dynamic is not None: 421 self.inherited_dynamic = alias.dynamic 422 423 if alias.sealed is not None: 424 self.inherited_sealed = alias.sealed
425
426 - def _finalise_compile(self):
427 if self.dynamic is None: 428 self.dynamic = True 429 430 if self.inherited_dynamic is not None: 431 if self.inherited_dynamic is False and not self.sealed and self.inherited_sealed: 432 self.dynamic = True 433 else: 434 self.dynamic = self.inherited_dynamic 435 436 if self.sealed: 437 self.dynamic = False 438 439 if self.amf3 is None: 440 self.amf3 = False 441 442 if self.external is None: 443 self.external = False 444 445 if not self.static_attrs: 446 self.static_attrs = None 447 else: 448 self.encodable_properties.update(self.static_attrs) 449 self.decodable_properties.update(self.static_attrs) 450 451 if self.static_attrs is not None: 452 if self.exclude_attrs: 453 self.static_attrs.difference_update(self.exclude_attrs) 454 455 self.static_attrs = list(self.static_attrs) 456 self.static_attrs.sort() 457 458 if not self.exclude_attrs: 459 self.exclude_attrs = None 460 else: 461 self.encodable_properties.difference_update(self.exclude_attrs) 462 self.decodable_properties.difference_update(self.exclude_attrs) 463 464 if self.exclude_attrs is not None: 465 self.exclude_attrs = list(self.exclude_attrs) 466 self.exclude_attrs.sort() 467 468 if not self.readonly_attrs: 469 self.readonly_attrs = None 470 else: 471 self.decodable_properties.difference_update(self.readonly_attrs) 472 473 if self.readonly_attrs is not None: 474 self.readonly_attrs = list(self.readonly_attrs) 475 self.readonly_attrs.sort() 476 477 if not self.proxy_attrs: 478 self.proxy_attrs = None 479 else: 480 if not self.amf3: 481 raise ClassAliasError('amf3 = True must be specified for ' 482 'classes with proxied attributes. Attribute = %r, ' 483 'Class = %r' % (self.proxy_attrs, self.klass,)) 484 485 self.proxy_attrs = list(self.proxy_attrs) 486 self.proxy_attrs.sort() 487 488 if len(self.decodable_properties) == 0: 489 self.decodable_properties = None 490 else: 491 self.decodable_properties = list(self.decodable_properties) 492 self.decodable_properties.sort() 493 494 if len(self.encodable_properties) == 0: 495 self.encodable_properties = None 496 else: 497 self.encodable_properties = list(self.encodable_properties) 498 self.encodable_properties.sort() 499 500 self.non_static_encodable_properties = None 501 502 if self.encodable_properties: 503 self.non_static_encodable_properties = set(self.encodable_properties) 504 505 if self.static_attrs: 506 self.non_static_encodable_properties.difference_update(self.static_attrs) 507 508 self.shortcut_encode = True 509 510 if self.encodable_properties or self.static_attrs or self.exclude_attrs: 511 self.shortcut_encode = False 512 513 self._compiled = True
514
515 - def is_compiled(self):
516 return self._compiled
517
518 - def __str__(self):
519 return self.alias
520
521 - def __repr__(self):
522 return '<ClassAlias alias=%s class=%s @ 0x%x>' % ( 523 self.alias, self.klass, id(self))
524
525 - def __eq__(self, other):
526 if isinstance(other, basestring): 527 return self.alias == other 528 elif isinstance(other, self.__class__): 529 return self.klass == other.klass 530 elif isinstance(other, (type, types.ClassType)): 531 return self.klass == other 532 else: 533 return False
534
535 - def __hash__(self):
536 return id(self)
537
538 - def checkClass(self, klass):
539 """ 540 This function is used to check if the class being aliased fits certain 541 criteria. The default is to check that the C{__init__} constructor does 542 not pass in arguments. 543 544 @since: 0.4 545 @raise TypeError: C{__init__} doesn't support additional arguments 546 """ 547 # Check that the constructor of the class doesn't require any additonal 548 # arguments. 549 if not (hasattr(klass, '__init__') and hasattr(klass.__init__, 'im_func')): 550 return 551 552 klass_func = klass.__init__.im_func 553 554 # built-in classes don't have func_code 555 if hasattr(klass_func, 'func_code') and ( 556 klass_func.func_code.co_argcount - len(klass_func.func_defaults or []) > 1): 557 args = list(klass_func.func_code.co_varnames) 558 values = list(klass_func.func_defaults or []) 559 560 if not values: 561 sign = "%s.__init__(%s)" % (klass.__name__, ", ".join(args)) 562 else: 563 named_args = zip(args[len(args) - len(values):], values) 564 sign = "%s.%s.__init__(%s, %s)" % ( 565 klass.__module__, klass.__name__, 566 ", ".join(args[:0-len(values)]), 567 ", ".join(map(lambda x: "%s=%s" % x, named_args))) 568 569 raise TypeError("__init__ doesn't support additional arguments: %s" 570 % sign)
571
572 - def getEncodableAttributes(self, obj, codec=None):
573 """ 574 Returns a C{tuple} containing a dict of static and dynamic attributes 575 for an object to encode. 576 577 @param codec: An optional argument that will contain the en/decoder 578 instance calling this function. 579 @since: 0.5 580 """ 581 if not self._compiled: 582 self.compile() 583 584 static_attrs = {} 585 dynamic_attrs = {} 586 587 if self.static_attrs: 588 for attr in self.static_attrs: 589 try: 590 static_attrs[attr] = getattr(obj, attr) 591 except AttributeError: 592 static_attrs[attr] = Undefined 593 594 if not self.dynamic: 595 if self.non_static_encodable_properties: 596 for attr in self.non_static_encodable_properties: 597 dynamic_attrs[attr] = getattr(obj, attr) 598 599 if not static_attrs: 600 static_attrs = None 601 602 if not dynamic_attrs: 603 dynamic_attrs = None 604 605 return static_attrs, dynamic_attrs 606 607 dynamic_props = util.get_properties(obj) 608 609 if not self.shortcut_encode: 610 dynamic_props = set(dynamic_props) 611 612 if self.encodable_properties: 613 dynamic_props.update(self.encodable_properties) 614 615 if self.static_attrs: 616 dynamic_props.difference_update(self.static_attrs) 617 618 if self.exclude_attrs: 619 dynamic_props.difference_update(self.exclude_attrs) 620 621 if self.klass is dict: 622 for attr in dynamic_props: 623 dynamic_attrs[attr] = obj[attr] 624 else: 625 for attr in dynamic_props: 626 dynamic_attrs[attr] = getattr(obj, attr) 627 628 if self.proxy_attrs is not None: 629 if static_attrs: 630 for k, v in static_attrs.copy().iteritems(): 631 if k in self.proxy_attrs: 632 static_attrs[k] = self.getProxiedAttribute(k, v) 633 634 if dynamic_attrs: 635 for k, v in dynamic_attrs.copy().iteritems(): 636 if k in self.proxy_attrs: 637 dynamic_attrs[k] = self.getProxiedAttribute(k, v) 638 639 if not static_attrs: 640 static_attrs = None 641 642 if not dynamic_attrs: 643 dynamic_attrs = None 644 645 return static_attrs, dynamic_attrs
646
647 - def getDecodableAttributes(self, obj, attrs, codec=None):
648 """ 649 Returns a dictionary of attributes for C{obj} that has been filtered, 650 based on the supplied C{attrs}. This allows for fine grain control 651 over what will finally end up on the object or not .. 652 653 @param obj: The reference object. 654 @param attrs: The attrs dictionary that has been decoded. 655 @param codec: An optional argument that will contain the codec 656 instance calling this function. 657 @return: A dictionary of attributes that can be applied to C{obj} 658 @since: 0.5 659 """ 660 if not self._compiled: 661 self.compile() 662 663 changed = False 664 665 props = set(attrs.keys()) 666 667 if self.static_attrs: 668 missing_attrs = [] 669 670 for p in self.static_attrs: 671 if p not in props: 672 missing_attrs.append(p) 673 674 if missing_attrs: 675 raise AttributeError('Static attributes %r expected ' 676 'when decoding %r' % (missing_attrs, self.klass)) 677 678 if not self.dynamic: 679 if not self.decodable_properties: 680 props = set() 681 else: 682 props.intersection_update(self.decodable_properties) 683 684 changed = True 685 686 if self.readonly_attrs: 687 props.difference_update(self.readonly_attrs) 688 changed = True 689 690 if self.exclude_attrs: 691 props.difference_update(self.exclude_attrs) 692 changed = True 693 694 if self.proxy_attrs is not None: 695 from pyamf import flex 696 697 for k in self.proxy_attrs: 698 try: 699 v = attrs[k] 700 except KeyError: 701 continue 702 703 attrs[k] = flex.unproxy_object(v) 704 705 if not changed: 706 return attrs 707 708 a = {} 709 710 [a.__setitem__(p, attrs[p]) for p in props] 711 712 return a
713
714 - def getProxiedAttribute(self, attr, obj):
715 """ 716 Returns the proxied equivalent for C{obj}. 717 718 @param attr: The attribute of the proxy request. Useful for class 719 introspection. 720 @type attr: C{str} 721 @param obj: The object to proxy. 722 @return: The proxied object or the original object if it cannot be 723 proxied. 724 """ 725 # the default is to just check basic types 726 from pyamf import flex 727 728 if type(obj) is list: 729 return flex.ArrayCollection(obj) 730 elif type(obj) is dict: 731 return flex.ObjectProxy(obj) 732 733 return obj
734
735 - def applyAttributes(self, obj, attrs, codec=None):
736 """ 737 Applies the collection of attributes C{attrs} to aliased object C{obj}. 738 Called when decoding reading aliased objects from an AMF byte stream. 739 740 Override this to provide fine grain control of application of 741 attributes to C{obj}. 742 743 @param codec: An optional argument that will contain the en/decoder 744 instance calling this function. 745 """ 746 attrs = self.getDecodableAttributes(obj, attrs, codec=codec) 747 748 util.set_attrs(obj, attrs)
749
750 - def getCustomProperties(self):
751 """ 752 Overrride this to provide known static properties based on the aliased 753 class. 754 755 @since: 0.5 756 """
757
758 - def createInstance(self, codec=None, *args, **kwargs):
759 """ 760 Creates an instance of the klass. 761 762 @return: Instance of C{self.klass}. 763 """ 764 return self.klass(*args, **kwargs)
765 766
767 -class TypedObject(dict):
768 """ 769 This class is used when a strongly typed object is decoded but there is no 770 registered class to apply it to. 771 772 This object can only be used for 'simple' streams - i.e. not externalized 773 data. If encountered, a L{DecodeError} will be raised. 774 775 @ivar alias: The alias of the typed object. 776 @type alias: C{unicode} 777 @since: 0.4 778 """ 779
780 - def __init__(self, alias):
781 dict.__init__(self) 782 783 self.alias = alias
784
785 - def __readamf__(self, o):
786 raise DecodeError('Unable to decode an externalised stream with ' 787 'class alias \'%s\'.\n\nThe class alias was found and because ' 788 'strict mode is False an attempt was made to decode the object ' 789 'automatically. To decode this stream, a registered class with ' 790 'the alias and a corresponding __readamf__ method will be ' 791 'required.' % (self.alias,))
792
793 - def __writeamf__(self, o):
794 raise EncodeError('Unable to encode an externalised stream with ' 795 'class alias \'%s\'.\n\nThe class alias was found and because ' 796 'strict mode is False an attempt was made to encode the object ' 797 'automatically. To encode this stream, a registered class with ' 798 'the alias and a corresponding __readamf__ method will be ' 799 'required.' % (self.alias,))
800 801
802 -class TypedObjectClassAlias(ClassAlias):
803 """ 804 @since: 0.4 805 """ 806 807 klass = TypedObject 808
809 - def __init__(self, klass, alias, *args, **kwargs):
810 # klass attr is ignored 811 812 ClassAlias.__init__(self, self.klass, alias)
813
814 - def createInstance(self, codec=None):
815 return self.klass(self.alias)
816
817 - def checkClass(kls, klass):
818 pass
819 820
821 -class ErrorAlias(ClassAlias):
822 """ 823 Adapts Python exception objects to Adobe Flash Player error objects. 824 825 @since: 0.5 826 """ 827
828 - def getCustomProperties(self):
829 self.exclude_attrs.update(['args'])
830
831 - def getEncodableAttributes(self, obj, **kwargs):
832 sa, da = ClassAlias.getEncodableAttributes(self, obj, **kwargs) 833 834 if not da: 835 da = {} 836 837 da['message'] = str(obj) 838 da['name'] = obj.__class__.__name__ 839 840 return sa, da
841 842
843 -class BaseDecoder(object):
844 """ 845 Base AMF decoder. 846 847 @ivar context_class: The context for the decoding. 848 @type context_class: An instance of C{BaseDecoder.context_class} 849 @ivar type_map: 850 @type type_map: C{list} 851 @ivar stream: The underlying data stream. 852 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 853 @ivar strict: Defines how strict the decoding should be. For the time 854 being this relates to typed objects in the stream that do not have a 855 registered alias. Introduced in 0.4. 856 @type strict: C{bool} 857 @ivar timezone_offset: The offset from UTC for any datetime objects being 858 decoded. Default to C{None} means no offset. 859 @type timezone_offset: L{datetime.timedelta} 860 """ 861 862 context_class = BaseContext 863 type_map = {} 864
865 - def __init__(self, stream=None, context=None, strict=False, timezone_offset=None):
866 if isinstance(stream, util.BufferedByteStream): 867 self.stream = stream 868 else: 869 self.stream = util.BufferedByteStream(stream) 870 871 if context is None: 872 self.context = self.context_class() 873 else: 874 self.context = context 875 876 self.context.exceptions = False 877 self.strict = strict 878 879 self.timezone_offset = timezone_offset
880
881 - def readElement(self):
882 """ 883 Reads an AMF3 element from the data stream. 884 885 @raise DecodeError: The ActionScript type is unsupported. 886 @raise EOStream: No more data left to decode. 887 """ 888 pos = self.stream.tell() 889 890 try: 891 t = self.stream.read(1) 892 except IOError: 893 raise EOStream 894 895 try: 896 func = getattr(self, self.type_map[t]) 897 except KeyError: 898 raise DecodeError("Unsupported ActionScript type %r" % (t,)) 899 900 try: 901 return func() 902 except IOError: 903 self.stream.seek(pos) 904 905 raise
906
907 - def __iter__(self):
908 try: 909 while 1: 910 yield self.readElement() 911 except EOStream: 912 raise StopIteration
913 914
915 -class CustomTypeFunc(object):
916 """ 917 Custom type mappings. 918 """ 919
920 - def __init__(self, encoder, func):
921 self.encoder = encoder 922 self.func = func
923
924 - def __call__(self, data, *args, **kwargs):
925 self.encoder.writeElement(self.func(data, encoder=self.encoder))
926 927
928 -class BaseEncoder(object):
929 """ 930 Base AMF encoder. 931 932 @ivar type_map: A list of types -> functions. The types is a list of 933 possible instances or functions to call (that return a C{bool}) to 934 determine the correct function to call to encode the data. 935 @type type_map: C{list} 936 @ivar context_class: Holds the class that will create context objects for 937 the implementing C{Encoder}. 938 @type context_class: C{type} or C{types.ClassType} 939 @ivar stream: The underlying data stream. 940 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 941 @ivar context: The context for the encoding. 942 @type context: An instance of C{BaseEncoder.context_class} 943 @ivar strict: Whether the encoder should operate in 'strict' mode. Nothing 944 is really affected by this for the time being - its just here for 945 flexibility. 946 @type strict: C{bool}, default is False. 947 @ivar timezone_offset: The offset from UTC for any datetime objects being 948 encoded. Default to C{None} means no offset. 949 @type timezone_offset: L{datetime.timedelta} 950 """ 951 952 context_class = BaseContext 953 type_map = [] 954
955 - def __init__(self, stream=None, context=None, strict=False, timezone_offset=None):
956 if isinstance(stream, util.BufferedByteStream): 957 self.stream = stream 958 else: 959 self.stream = util.BufferedByteStream(stream) 960 961 if context is None: 962 self.context = self.context_class() 963 else: 964 self.context = context 965 966 self.context.exceptions = False 967 self._write_elem_func_cache = {} 968 self.strict = strict 969 self.timezone_offset = timezone_offset
970
971 - def writeFunc(self, obj, **kwargs):
972 """ 973 Not possible to encode functions. 974 975 @raise EncodeError: Unable to encode function/methods. 976 """ 977 raise EncodeError("Unable to encode function/methods")
978
979 - def _getWriteElementFunc(self, data):
980 """ 981 Gets a function used to encode the data. 982 983 @type data: C{mixed} 984 @param data: Python data. 985 @rtype: callable or C{None}. 986 @return: The function used to encode data to the stream. 987 """ 988 for type_, func in TYPE_MAP.iteritems(): 989 try: 990 if isinstance(data, type_): 991 return CustomTypeFunc(self, func) 992 except TypeError: 993 if callable(type_) and type_(data): 994 return CustomTypeFunc(self, func) 995 996 for tlist, method in self.type_map: 997 for t in tlist: 998 try: 999 if isinstance(data, t): 1000 return getattr(self, method) 1001 except TypeError: 1002 if callable(t) and t(data): 1003 return getattr(self, method) 1004 1005 return None
1006
1007 - def _writeElementFunc(self, data):
1008 """ 1009 Gets a function used to encode the data. 1010 1011 @type data: C{mixed} 1012 @param data: Python data. 1013 @rtype: callable or C{None}. 1014 @return: The function used to encode data to the stream. 1015 """ 1016 try: 1017 key = data.__class__ 1018 except AttributeError: 1019 return self._getWriteElementFunc(data) 1020 1021 try: 1022 return self._write_elem_func_cache[key] 1023 except KeyError: 1024 self._write_elem_func_cache[key] = self._getWriteElementFunc(data) 1025 1026 return self._write_elem_func_cache[key]
1027
1028 - def writeElement(self, data):
1029 """ 1030 Writes the data. Overridden in subclass. 1031 1032 @type data: C{mixed} 1033 @param data: The data to be encoded to the data stream. 1034 """ 1035 raise NotImplementedError
1036 1037
1038 -def register_class(klass, alias=None):
1039 """ 1040 Registers a class to be used in the data streaming. 1041 1042 @return: The registered L{ClassAlias}. 1043 """ 1044 meta = util.get_class_meta(klass) 1045 1046 if alias is not None: 1047 meta['alias'] = alias 1048 1049 alias_klass = util.get_class_alias(klass) 1050 1051 x = alias_klass(klass, defer=True, **meta) 1052 1053 if not x.anonymous: 1054 CLASS_CACHE[x.alias] = x 1055 1056 CLASS_CACHE[klass] = x 1057 1058 return x
1059 1060
1061 -def unregister_class(alias):
1062 """ 1063 Deletes a class from the cache. 1064 1065 If C{alias} is a class, the matching alias is found. 1066 1067 @type alias: C{class} or C{str} 1068 @param alias: Alias for class to delete. 1069 @raise UnknownClassAlias: Unknown alias. 1070 """ 1071 try: 1072 x = CLASS_CACHE[alias] 1073 except KeyError: 1074 raise UnknownClassAlias('Unknown alias %r' % (alias,)) 1075 1076 if not x.anonymous: 1077 del CLASS_CACHE[x.alias] 1078 1079 del CLASS_CACHE[x.klass] 1080 1081 return x
1082 1083
1084 -def get_class_alias(klass):
1085 """ 1086 Finds the alias registered to the class. 1087 1088 @type klass: C{object} or class object. 1089 @return: The class alias linked to C{klass}. 1090 @rtype: L{ClassAlias} 1091 @raise UnknownClassAlias: Class not found. 1092 """ 1093 if isinstance(klass, basestring): 1094 try: 1095 return CLASS_CACHE[klass] 1096 except KeyError: 1097 return load_class(klass) 1098 1099 if not isinstance(klass, (type, types.ClassType)): 1100 if isinstance(klass, types.InstanceType): 1101 klass = klass.__class__ 1102 elif isinstance(klass, types.ObjectType): 1103 klass = type(klass) 1104 1105 try: 1106 return CLASS_CACHE[klass] 1107 except KeyError: 1108 raise UnknownClassAlias('Unknown alias for %r' % (klass,))
1109 1110
1111 -def register_class_loader(loader):
1112 """ 1113 Registers a loader that is called to provide the C{Class} for a specific 1114 alias. 1115 1116 The L{loader} is provided with one argument, the C{Class} alias. If the 1117 loader succeeds in finding a suitable class then it should return that 1118 class, otherwise it should return C{None}. 1119 1120 @type loader: C{callable} 1121 @raise TypeError: The C{loader} is not callable. 1122 @raise ValueError: The C{loader} is already registered. 1123 """ 1124 if not callable(loader): 1125 raise TypeError("loader must be callable") 1126 1127 if loader in CLASS_LOADERS: 1128 raise ValueError("loader has already been registered") 1129 1130 CLASS_LOADERS.append(loader)
1131 1132
1133 -def unregister_class_loader(loader):
1134 """ 1135 Unregisters a class loader. 1136 1137 @type loader: C{callable} 1138 @param loader: The object to be unregistered 1139 1140 @raise LookupError: The C{loader} was not registered. 1141 """ 1142 if loader not in CLASS_LOADERS: 1143 raise LookupError("loader not found") 1144 1145 CLASS_LOADERS.remove(loader)
1146 1147
1148 -def get_module(mod_name):
1149 """ 1150 Load a module based on C{mod_name}. 1151 1152 @type mod_name: C{str} 1153 @param mod_name: The module name. 1154 @return: Module. 1155 1156 @raise ImportError: Unable to import an empty module. 1157 """ 1158 if mod_name is '': 1159 raise ImportError("Unable to import empty module") 1160 1161 mod = __import__(mod_name) 1162 components = mod_name.split('.') 1163 1164 for comp in components[1:]: 1165 mod = getattr(mod, comp) 1166 1167 return mod
1168 1169
1170 -def load_class(alias):
1171 """ 1172 Finds the class registered to the alias. 1173 1174 The search is done in order: 1175 1. Checks if the class name has been registered via L{register_class} or 1176 L{register_package}. 1177 2. Checks all functions registered via L{register_class_loader}. 1178 3. Attempts to load the class via standard module loading techniques. 1179 1180 @type alias: C{str} 1181 @param alias: The class name. 1182 @raise UnknownClassAlias: The C{alias} was not found. 1183 @raise TypeError: Expecting class type or L{ClassAlias} from loader. 1184 @return: Class registered to the alias. 1185 """ 1186 alias = str(alias) 1187 1188 # Try the CLASS_CACHE first 1189 try: 1190 return CLASS_CACHE[alias] 1191 except KeyError: 1192 pass 1193 1194 # Check each CLASS_LOADERS in turn 1195 for loader in CLASS_LOADERS: 1196 klass = loader(alias) 1197 1198 if klass is None: 1199 continue 1200 1201 if isinstance(klass, (type, types.ClassType)): 1202 return register_class(klass, alias) 1203 elif isinstance(klass, ClassAlias): 1204 CLASS_CACHE[str(alias)] = klass 1205 CLASS_CACHE[klass.klass] = klass 1206 1207 return klass 1208 else: 1209 raise TypeError("Expecting class type or ClassAlias from loader") 1210 1211 # XXX nick: Are there security concerns for loading classes this way? 1212 mod_class = alias.split('.') 1213 1214 if mod_class: 1215 module = '.'.join(mod_class[:-1]) 1216 klass = mod_class[-1] 1217 1218 try: 1219 module = get_module(module) 1220 except (ImportError, AttributeError): 1221 # XXX What to do here? 1222 pass 1223 else: 1224 klass = getattr(module, klass) 1225 1226 if isinstance(klass, (type, types.ClassType)): 1227 return register_class(klass, alias) 1228 elif isinstance(klass, ClassAlias): 1229 CLASS_CACHE[str(alias)] = klass 1230 CLASS_CACHE[klass.klass] = klass 1231 1232 return klass.klass 1233 else: 1234 raise TypeError("Expecting class type or ClassAlias from loader") 1235 1236 # All available methods for finding the class have been exhausted 1237 raise UnknownClassAlias("Unknown alias for %r" % (alias,))
1238 1239
1240 -def decode(*args, **kwargs):
1241 """ 1242 A generator function to decode a datastream. 1243 1244 @kwarg stream: AMF data. 1245 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 1246 @type encoding: C{int} 1247 @kwarg encoding: AMF encoding type. 1248 @type context: L{AMF0 Context<pyamf.amf0.Context>} or 1249 L{AMF3 Context<pyamf.amf3.Context>} 1250 @kwarg context: Context. 1251 @return: Each element in the stream. 1252 """ 1253 encoding = kwargs.pop('encoding', DEFAULT_ENCODING) 1254 decoder = _get_decoder_class(encoding)(*args, **kwargs) 1255 1256 while 1: 1257 try: 1258 yield decoder.readElement() 1259 except EOStream: 1260 break
1261 1262
1263 -def encode(*args, **kwargs):
1264 """ 1265 A helper function to encode an element. 1266 1267 @type args: C{mixed} 1268 @keyword element: Python data. 1269 @type encoding: C{int} 1270 @keyword encoding: AMF encoding type. 1271 @type context: L{amf0.Context<pyamf.amf0.Context>} or 1272 L{amf3.Context<pyamf.amf3.Context>} 1273 @keyword context: Context. 1274 1275 @rtype: C{StringIO} 1276 @return: File-like object. 1277 """ 1278 encoding = kwargs.pop('encoding', DEFAULT_ENCODING) 1279 1280 encoder = _get_encoder_class(encoding)(**kwargs) 1281 stream = encoder.stream 1282 1283 for el in args: 1284 encoder.writeElement(el) 1285 1286 stream.seek(0) 1287 1288 return stream
1289 1290
1291 -def get_decoder(encoding, *args, **kwargs):
1292 """ 1293 Returns a subclassed instance of L{pyamf.BaseDecoder}, based on C{encoding} 1294 """ 1295 return _get_decoder_class(encoding)(*args, **kwargs)
1296 1297
1298 -def _get_decoder_class(encoding):
1299 """ 1300 Get compatible decoder. 1301 1302 @type encoding: C{int} 1303 @param encoding: AMF encoding version. 1304 @raise ValueError: AMF encoding version is unknown. 1305 1306 @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or 1307 L{amf3.Decoder<pyamf.amf3.Decoder>} 1308 @return: AMF0 or AMF3 decoder. 1309 """ 1310 if encoding == AMF0: 1311 from pyamf import amf0 1312 1313 return amf0.Decoder 1314 elif encoding == AMF3: 1315 from pyamf import amf3 1316 1317 return amf3.Decoder 1318 1319 raise ValueError("Unknown encoding %s" % (encoding,))
1320 1321
1322 -def get_encoder(encoding, *args, **kwargs):
1323 """ 1324 Returns a subclassed instance of L{pyamf.BaseEncoder}, based on C{encoding} 1325 """ 1326 return _get_encoder_class(encoding)(*args, **kwargs)
1327 1328
1329 -def _get_encoder_class(encoding):
1330 """ 1331 Get compatible encoder. 1332 1333 @type encoding: C{int} 1334 @param encoding: AMF encoding version. 1335 @raise ValueError: AMF encoding version is unknown. 1336 1337 @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or 1338 L{amf3.Encoder<pyamf.amf3.Encoder>} 1339 @return: AMF0 or AMF3 encoder. 1340 """ 1341 if encoding == AMF0: 1342 from pyamf import amf0 1343 1344 return amf0.Encoder 1345 elif encoding == AMF3: 1346 from pyamf import amf3 1347 1348 return amf3.Encoder 1349 1350 raise ValueError("Unknown encoding %s" % (encoding,))
1351 1352
1353 -def get_context(encoding, **kwargs):
1354 return _get_context_class(encoding)(**kwargs)
1355 1356
1357 -def _get_context_class(encoding):
1358 """ 1359 Gets a compatible context class. 1360 1361 @type encoding: C{int} 1362 @param encoding: AMF encoding version. 1363 @raise ValueError: AMF encoding version is unknown. 1364 1365 @rtype: L{amf0.Context<pyamf.amf0.Context>} or 1366 L{amf3.Context<pyamf.amf3.Context>} 1367 @return: AMF0 or AMF3 context class. 1368 """ 1369 if encoding == AMF0: 1370 from pyamf import amf0 1371 1372 return amf0.Context 1373 elif encoding == AMF3: 1374 from pyamf import amf3 1375 1376 return amf3.Context 1377 1378 raise ValueError("Unknown encoding %s" % (encoding,))
1379 1380
1381 -def blaze_loader(alias):
1382 """ 1383 Loader for BlazeDS framework compatibility classes, specifically 1384 implementing C{ISmallMessage}. 1385 1386 @see: U{BlazeDS (external)<http://opensource.adobe.com/wiki/display/blazeds/BlazeDS>} 1387 @since: 0.5 1388 """ 1389 if alias not in ['DSC', 'DSK']: 1390 return 1391 1392 import pyamf.flex.messaging 1393 1394 return CLASS_CACHE[alias]
1395 1396
1397 -def flex_loader(alias):
1398 """ 1399 Loader for L{Flex<pyamf.flex>} framework compatibility classes. 1400 1401 @raise UnknownClassAlias: Trying to load unknown Flex compatibility class. 1402 """ 1403 if not alias.startswith('flex.'): 1404 return 1405 1406 try: 1407 if alias.startswith('flex.messaging.messages'): 1408 import pyamf.flex.messaging 1409 elif alias.startswith('flex.messaging.io'): 1410 import pyamf.flex 1411 elif alias.startswith('flex.data.messages'): 1412 import pyamf.flex.data 1413 1414 return CLASS_CACHE[alias] 1415 except KeyError: 1416 raise UnknownClassAlias(alias)
1417 1418
1419 -def add_type(type_, func=None):
1420 """ 1421 Adds a custom type to L{TYPE_MAP}. A custom type allows fine grain control 1422 of what to encode to an AMF data stream. 1423 1424 @raise TypeError: Unable to add as a custom type (expected a class or callable). 1425 @raise KeyError: Type already exists. 1426 """ 1427 def _check_type(type_): 1428 if not (isinstance(type_, (type, types.ClassType)) or callable(type_)): 1429 raise TypeError(r'Unable to add '%r' as a custom type (expected a ' 1430 'class or callable)' % (type_,))
1431 1432 if isinstance(type_, list): 1433 type_ = tuple(type_) 1434 1435 if type_ in TYPE_MAP: 1436 raise KeyError('Type %r already exists' % (type_,)) 1437 1438 if isinstance(type_, types.TupleType): 1439 for x in type_: 1440 _check_type(x) 1441 else: 1442 _check_type(type_) 1443 1444 TYPE_MAP[type_] = func 1445 1446
1447 -def get_type(type_):
1448 """ 1449 Gets the declaration for the corresponding custom type. 1450 1451 @raise KeyError: Unknown type. 1452 """ 1453 if isinstance(type_, list): 1454 type_ = tuple(type_) 1455 1456 for (k, v) in TYPE_MAP.iteritems(): 1457 if k == type_: 1458 return v 1459 1460 raise KeyError("Unknown type %r" % (type_,))
1461 1462
1463 -def remove_type(type_):
1464 """ 1465 Removes the custom type declaration. 1466 1467 @return: Custom type declaration. 1468 """ 1469 declaration = get_type(type_) 1470 1471 del TYPE_MAP[type_] 1472 1473 return declaration
1474 1475
1476 -def add_error_class(klass, code):
1477 """ 1478 Maps an exception class to a string code. Used to map remoting C{onStatus} 1479 objects to an exception class so that an exception can be built to 1480 represent that error:: 1481 1482 class AuthenticationError(Exception): 1483 pass 1484 1485 An example: C{add_error_class(AuthenticationError, 'Auth.Failed')} 1486 1487 @type code: C{str} 1488 1489 @raise TypeError: C{klass} must be a C{class} type. 1490 @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class. 1491 @raise ValueError: Code is already registered. 1492 """ 1493 if not isinstance(code, basestring): 1494 code = str(code) 1495 1496 if not isinstance(klass, (type, types.ClassType)): 1497 raise TypeError("klass must be a class type") 1498 1499 mro = inspect.getmro(klass) 1500 1501 if not Exception in mro: 1502 raise TypeError('Error classes must subclass the __builtin__.Exception class') 1503 1504 if code in ERROR_CLASS_MAP.keys(): 1505 raise ValueError('Code %s is already registered' % (code,)) 1506 1507 ERROR_CLASS_MAP[code] = klass
1508 1509
1510 -def remove_error_class(klass):
1511 """ 1512 Removes a class from C{ERROR_CLASS_MAP}. 1513 1514 @raise ValueError: Code is not registered. 1515 @raise ValueError: Class is not registered. 1516 @raise TypeError: Invalid type, expected C{class} or C{string}. 1517 """ 1518 if isinstance(klass, basestring): 1519 if not klass in ERROR_CLASS_MAP.keys(): 1520 raise ValueError('Code %s is not registered' % (klass,)) 1521 elif isinstance(klass, (type, types.ClassType)): 1522 classes = ERROR_CLASS_MAP.values() 1523 if not klass in classes: 1524 raise ValueError('Class %s is not registered' % (klass,)) 1525 1526 klass = ERROR_CLASS_MAP.keys()[classes.index(klass)] 1527 else: 1528 raise TypeError("Invalid type, expected class or string") 1529 1530 del ERROR_CLASS_MAP[klass]
1531 1532
1533 -def register_alias_type(klass, *args):
1534 """ 1535 This function allows you to map subclasses of L{ClassAlias} to classes 1536 listed in C{args}. 1537 1538 When an object is read/written from/to the AMF stream, a paired 1539 L{ClassAlias} instance is created (or reused), based on the Python class 1540 of that object. L{ClassAlias} provides important metadata for the class 1541 and can also control how the equivalent Python object is created, how the 1542 attributes are applied etc. 1543 1544 Use this function if you need to do something non-standard. 1545 1546 @see: L{pyamf.adapters._google_appengine_ext_db.DataStoreClassAlias} for a 1547 good example. 1548 @since: 0.4 1549 @raise RuntimeError: Type is already registered. 1550 @raise TypeError: C{klass} must be a class. 1551 @raise ValueError: New aliases must subclass L{pyamf.ClassAlias}. 1552 @raise ValueError: At least one type must be supplied. 1553 """ 1554 1555 def check_type_registered(arg): 1556 # FIXME: Create a reverse index of registered types and do a quicker lookup 1557 for k, v in ALIAS_TYPES.iteritems(): 1558 for kl in v: 1559 if arg is kl: 1560 raise RuntimeError('%r is already registered under %r' % (arg, k))
1561 1562 if not isinstance(klass, (type, types.ClassType)): 1563 raise TypeError('klass must be class') 1564 1565 if not issubclass(klass, ClassAlias): 1566 raise ValueError('New aliases must subclass pyamf.ClassAlias') 1567 1568 if len(args) == 0: 1569 raise ValueError('At least one type must be supplied') 1570 1571 if len(args) == 1 and callable(args[0]): 1572 c = args[0] 1573 1574 check_type_registered(c) 1575 else: 1576 for arg in args: 1577 if not isinstance(arg, (type, types.ClassType)): 1578 raise TypeError('%r must be class' % (arg,)) 1579 1580 check_type_registered(arg) 1581 1582 ALIAS_TYPES[klass] = args 1583 1584
1585 -def register_package(module=None, package=None, separator='.', ignore=[], strict=True):
1586 """ 1587 This is a helper function that takes the concept of Actionscript packages 1588 and registers all the classes in the supplied Python module under that 1589 package. It auto-aliased all classes in C{module} based on C{package}. 1590 1591 e.g. C{mymodule.py}:: 1592 class User(object): 1593 pass 1594 1595 class Permission(object): 1596 pass 1597 1598 >>> import mymodule 1599 >>> pyamf.register_package(mymodule, 'com.example.app') 1600 1601 Now all instances of C{mymodule.User} will appear in Actionscript under the 1602 alias 'com.example.app.User'. Same goes for C{mymodule.Permission} - the 1603 Actionscript alias is 'com.example.app.Permission'. The reverse is also 1604 true, any objects with the correct aliases will now be instances of the 1605 relevant Python class. 1606 1607 This function respects the C{__all__} attribute of the module but you can 1608 have further control of what not to auto alias by populating the C{ignore} 1609 argument. 1610 1611 This function provides the ability to register the module it is being 1612 called in, an example: 1613 1614 >>> class Foo: 1615 ... pass 1616 ... 1617 >>> class Bar: 1618 ... pass 1619 ... 1620 >>> import pyamf 1621 >>> pyamf.register_package('foo') 1622 1623 You can also supply a list of classes to register. An example, taking the 1624 above classes: 1625 1626 >>> import pyamf 1627 >>> pyamf.register_package([Foo, Bar], 'foo') 1628 1629 @param module: The Python module that will contain all the classes to 1630 auto alias. 1631 @type module: C{module} or C{dict} 1632 @param package: The base package name. e.g. 'com.example.app'. If this 1633 is C{None} then the value is inferred from module.__name__. 1634 @type package: C{str} or C{unicode} or C{None} 1635 @param separator: The separator used to append to C{package} to form the 1636 complete alias. 1637 @type separator: C{str} 1638 @param ignore: To give fine grain control over what gets aliased and what 1639 doesn't, supply a list of classes that you B{do not} want to be aliased. 1640 @type ignore: C{iterable} 1641 @param strict: If this value is C{True} then only classes that originate 1642 from C{module} will be registered, all others will be left in peace. 1643 @type strict: C{bool} 1644 @return: A collection of all the classes that were registered and their 1645 respective L{ClassAlias} objects. 1646 @since: 0.5 1647 """ 1648 if isinstance(module, basestring): 1649 if module == '': 1650 raise TypeError('Cannot get list of classes from %r' % (module,)) 1651 1652 package = module 1653 module = None 1654 1655 if module is None: 1656 import inspect 1657 1658 prev_frame = inspect.stack()[1][0] 1659 module = prev_frame.f_locals 1660 1661 if type(module) is dict: 1662 has = lambda x: x in module.keys() 1663 get = module.__getitem__ 1664 elif type(module) is list: 1665 has = lambda x: x in module 1666 get = module.__getitem__ 1667 strict = False 1668 else: 1669 has = lambda x: hasattr(module, x) 1670 get = lambda x: getattr(module, x) 1671 1672 if package is None: 1673 if has('__name__'): 1674 package = get('__name__') 1675 else: 1676 raise TypeError('Cannot get list of classes from %r' % (module,)) 1677 1678 if has('__all__'): 1679 keys = get('__all__') 1680 elif hasattr(module, '__dict__'): 1681 keys = module.__dict__.keys() 1682 elif hasattr(module, 'keys'): 1683 keys = module.keys() 1684 elif isinstance(module, list): 1685 keys = range(len(module)) 1686 else: 1687 raise TypeError('Cannot get list of classes from %r' % (module,)) 1688 1689 def check_attr(attr): 1690 if not isinstance(attr, (types.ClassType, types.TypeType)): 1691 return False 1692 1693 if attr.__name__ in ignore: 1694 return False 1695 1696 try: 1697 if strict and attr.__module__ != get('__name__'): 1698 return False 1699 except AttributeError: 1700 return False 1701 1702 return True
1703 1704 # gotta love python 1705 classes = filter(check_attr, [get(x) for x in keys]) 1706 1707 registered = {} 1708 1709 for klass in classes: 1710 alias = '%s%s%s' % (package, separator, klass.__name__) 1711 1712 registered[klass] = register_class(klass, alias) 1713 1714 return registered 1715 1716 1717 # init module here 1718 register_class(ASObject) 1719 register_class_loader(flex_loader) 1720 register_class_loader(blaze_loader) 1721 register_alias_type(TypedObjectClassAlias, TypedObject) 1722 register_alias_type(ErrorAlias, Exception) 1723 1724 register_adapters() 1725