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 
  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) 
  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 klass=%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 if hasattr(obj, attr): 590 static_attrs[attr] = getattr(obj, attr) 591 else: 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 if key not in self._write_elem_func_cache: 1022 self._write_elem_func_cache[key] = self._getWriteElementFunc(data) 1023 1024 return self._write_elem_func_cache[key]
1025
1026 - def writeElement(self, data):
1027 """ 1028 Writes the data. Overridden in subclass. 1029 1030 @type data: C{mixed} 1031 @param data: The data to be encoded to the data stream. 1032 """ 1033 raise NotImplementedError
1034 1035
1036 -def register_class(klass, alias=None):
1037 """ 1038 Registers a class to be used in the data streaming. 1039 1040 @return: The registered L{ClassAlias}. 1041 """ 1042 meta = util.get_class_meta(klass) 1043 1044 if alias is not None: 1045 meta['alias'] = alias 1046 1047 alias_klass = util.get_class_alias(klass) 1048 1049 x = alias_klass(klass, defer=True, **meta) 1050 1051 if not x.anonymous: 1052 CLASS_CACHE[x.alias] = x 1053 1054 CLASS_CACHE[klass] = x 1055 1056 return x
1057 1058
1059 -def unregister_class(alias):
1060 """ 1061 Deletes a class from the cache. 1062 1063 If C{alias} is a class, the matching alias is found. 1064 1065 @type alias: C{class} or C{str} 1066 @param alias: Alias for class to delete. 1067 @raise UnknownClassAlias: Unknown alias. 1068 """ 1069 try: 1070 x = CLASS_CACHE[alias] 1071 except KeyError: 1072 raise UnknownClassAlias('Unknown alias %r' % (alias,)) 1073 1074 if not x.anonymous: 1075 del CLASS_CACHE[x.alias] 1076 1077 del CLASS_CACHE[x.klass] 1078 1079 return x
1080 1081
1082 -def get_class_alias(klass):
1083 """ 1084 Finds the alias registered to the class. 1085 1086 @type klass: C{object} or class object. 1087 @return: The class alias linked to C{klass}. 1088 @rtype: L{ClassAlias} 1089 @raise UnknownClassAlias: Class not found. 1090 """ 1091 if isinstance(klass, basestring): 1092 try: 1093 return CLASS_CACHE[klass] 1094 except KeyError: 1095 return load_class(klass) 1096 1097 if not isinstance(klass, (type, types.ClassType)): 1098 if isinstance(klass, types.InstanceType): 1099 klass = klass.__class__ 1100 elif isinstance(klass, types.ObjectType): 1101 klass = type(klass) 1102 1103 try: 1104 return CLASS_CACHE[klass] 1105 except KeyError: 1106 raise UnknownClassAlias('Unknown alias for %r' % (klass,))
1107 1108
1109 -def register_class_loader(loader):
1110 """ 1111 Registers a loader that is called to provide the C{Class} for a specific 1112 alias. 1113 1114 The L{loader} is provided with one argument, the C{Class} alias. If the 1115 loader succeeds in finding a suitable class then it should return that 1116 class, otherwise it should return C{None}. 1117 1118 @type loader: C{callable} 1119 @raise TypeError: The C{loader} is not callable. 1120 @raise ValueError: The C{loader} is already registered. 1121 """ 1122 if not callable(loader): 1123 raise TypeError("loader must be callable") 1124 1125 if loader in CLASS_LOADERS: 1126 raise ValueError("loader has already been registered") 1127 1128 CLASS_LOADERS.append(loader)
1129 1130
1131 -def unregister_class_loader(loader):
1132 """ 1133 Unregisters a class loader. 1134 1135 @type loader: C{callable} 1136 @param loader: The object to be unregistered 1137 1138 @raise LookupError: The C{loader} was not registered. 1139 """ 1140 if loader not in CLASS_LOADERS: 1141 raise LookupError("loader not found") 1142 1143 CLASS_LOADERS.remove(loader)
1144 1145
1146 -def get_module(mod_name):
1147 """ 1148 Load a module based on C{mod_name}. 1149 1150 @type mod_name: C{str} 1151 @param mod_name: The module name. 1152 @return: Module. 1153 1154 @raise ImportError: Unable to import an empty module. 1155 """ 1156 if mod_name is '': 1157 raise ImportError("Unable to import empty module") 1158 1159 mod = __import__(mod_name) 1160 components = mod_name.split('.') 1161 1162 for comp in components[1:]: 1163 mod = getattr(mod, comp) 1164 1165 return mod
1166 1167
1168 -def load_class(alias):
1169 """ 1170 Finds the class registered to the alias. 1171 1172 The search is done in order: 1173 1. Checks if the class name has been registered via L{register_class} or 1174 L{register_package}. 1175 2. Checks all functions registered via L{register_class_loader}. 1176 3. Attempts to load the class via standard module loading techniques. 1177 1178 @type alias: C{str} 1179 @param alias: The class name. 1180 @raise UnknownClassAlias: The C{alias} was not found. 1181 @raise TypeError: Expecting class type or L{ClassAlias} from loader. 1182 @return: Class registered to the alias. 1183 """ 1184 alias = str(alias) 1185 1186 # Try the CLASS_CACHE first 1187 try: 1188 return CLASS_CACHE[alias] 1189 except KeyError: 1190 pass 1191 1192 # Check each CLASS_LOADERS in turn 1193 for loader in CLASS_LOADERS: 1194 klass = loader(alias) 1195 1196 if klass is None: 1197 continue 1198 1199 if isinstance(klass, (type, types.ClassType)): 1200 return register_class(klass, alias) 1201 elif isinstance(klass, ClassAlias): 1202 CLASS_CACHE[str(alias)] = klass 1203 CLASS_CACHE[klass.klass] = klass 1204 1205 return klass 1206 else: 1207 raise TypeError("Expecting class type or ClassAlias from loader") 1208 1209 # XXX nick: Are there security concerns for loading classes this way? 1210 mod_class = alias.split('.') 1211 1212 if mod_class: 1213 module = '.'.join(mod_class[:-1]) 1214 klass = mod_class[-1] 1215 1216 try: 1217 module = get_module(module) 1218 except (ImportError, AttributeError): 1219 # XXX What to do here? 1220 pass 1221 else: 1222 klass = getattr(module, klass) 1223 1224 if isinstance(klass, (type, types.ClassType)): 1225 return register_class(klass, alias) 1226 elif isinstance(klass, ClassAlias): 1227 CLASS_CACHE[str(alias)] = klass 1228 CLASS_CACHE[klass.klass] = klass 1229 1230 return klass.klass 1231 else: 1232 raise TypeError("Expecting class type or ClassAlias from loader") 1233 1234 # All available methods for finding the class have been exhausted 1235 raise UnknownClassAlias("Unknown alias for %r" % (alias,))
1236 1237
1238 -def decode(*args, **kwargs):
1239 """ 1240 A generator function to decode a datastream. 1241 1242 @kwarg stream: AMF data. 1243 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 1244 @type encoding: C{int} 1245 @kwarg encoding: AMF encoding type. 1246 @type context: L{AMF0 Context<pyamf.amf0.Context>} or 1247 L{AMF3 Context<pyamf.amf3.Context>} 1248 @kwarg context: Context. 1249 @return: Each element in the stream. 1250 """ 1251 encoding = kwargs.pop('encoding', DEFAULT_ENCODING) 1252 decoder = _get_decoder_class(encoding)(*args, **kwargs) 1253 1254 while 1: 1255 try: 1256 yield decoder.readElement() 1257 except EOStream: 1258 break
1259 1260
1261 -def encode(*args, **kwargs):
1262 """ 1263 A helper function to encode an element. 1264 1265 @type args: C{mixed} 1266 @keyword element: Python data. 1267 @type encoding: C{int} 1268 @keyword encoding: AMF encoding type. 1269 @type context: L{amf0.Context<pyamf.amf0.Context>} or 1270 L{amf3.Context<pyamf.amf3.Context>} 1271 @keyword context: Context. 1272 1273 @rtype: C{StringIO} 1274 @return: File-like object. 1275 """ 1276 encoding = kwargs.pop('encoding', DEFAULT_ENCODING) 1277 1278 encoder = _get_encoder_class(encoding)(**kwargs) 1279 stream = encoder.stream 1280 1281 for el in args: 1282 encoder.writeElement(el) 1283 1284 stream.seek(0) 1285 1286 return stream
1287 1288
1289 -def get_decoder(encoding, *args, **kwargs):
1290 """ 1291 Returns a subclassed instance of L{pyamf.BaseDecoder}, based on C{encoding} 1292 """ 1293 return _get_decoder_class(encoding)(*args, **kwargs)
1294 1295
1296 -def _get_decoder_class(encoding):
1297 """ 1298 Get compatible decoder. 1299 1300 @type encoding: C{int} 1301 @param encoding: AMF encoding version. 1302 @raise ValueError: AMF encoding version is unknown. 1303 1304 @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or 1305 L{amf3.Decoder<pyamf.amf3.Decoder>} 1306 @return: AMF0 or AMF3 decoder. 1307 """ 1308 if encoding == AMF0: 1309 from pyamf import amf0 1310 1311 return amf0.Decoder 1312 elif encoding == AMF3: 1313 from pyamf import amf3 1314 1315 return amf3.Decoder 1316 1317 raise ValueError("Unknown encoding %s" % (encoding,))
1318 1319
1320 -def get_encoder(encoding, *args, **kwargs):
1321 """ 1322 Returns a subclassed instance of L{pyamf.BaseEncoder}, based on C{encoding} 1323 """ 1324 return _get_encoder_class(encoding)(*args, **kwargs)
1325 1326
1327 -def _get_encoder_class(encoding):
1328 """ 1329 Get compatible encoder. 1330 1331 @type encoding: C{int} 1332 @param encoding: AMF encoding version. 1333 @raise ValueError: AMF encoding version is unknown. 1334 1335 @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or 1336 L{amf3.Encoder<pyamf.amf3.Encoder>} 1337 @return: AMF0 or AMF3 encoder. 1338 """ 1339 if encoding == AMF0: 1340 from pyamf import amf0 1341 1342 return amf0.Encoder 1343 elif encoding == AMF3: 1344 from pyamf import amf3 1345 1346 return amf3.Encoder 1347 1348 raise ValueError("Unknown encoding %s" % (encoding,))
1349 1350
1351 -def get_context(encoding, **kwargs):
1352 return _get_context_class(encoding)(**kwargs)
1353 1354
1355 -def _get_context_class(encoding):
1356 """ 1357 Gets a compatible context class. 1358 1359 @type encoding: C{int} 1360 @param encoding: AMF encoding version. 1361 @raise ValueError: AMF encoding version is unknown. 1362 1363 @rtype: L{amf0.Context<pyamf.amf0.Context>} or 1364 L{amf3.Context<pyamf.amf3.Context>} 1365 @return: AMF0 or AMF3 context class. 1366 """ 1367 if encoding == AMF0: 1368 from pyamf import amf0 1369 1370 return amf0.Context 1371 elif encoding == AMF3: 1372 from pyamf import amf3 1373 1374 return amf3.Context 1375 1376 raise ValueError("Unknown encoding %s" % (encoding,))
1377 1378
1379 -def blaze_loader(alias):
1380 """ 1381 Loader for BlazeDS framework compatibility classes, specifically 1382 implementing C{ISmallMessage}. 1383 1384 @see: U{BlazeDS (external)<http://opensource.adobe.com/wiki/display/blazeds/BlazeDS>} 1385 @since: 0.5 1386 """ 1387 if alias not in ['DSC', 'DSK']: 1388 return 1389 1390 import pyamf.flex.messaging 1391 1392 return CLASS_CACHE[alias]
1393 1394
1395 -def flex_loader(alias):
1396 """ 1397 Loader for L{Flex<pyamf.flex>} framework compatibility classes. 1398 1399 @raise UnknownClassAlias: Trying to load unknown Flex compatibility class. 1400 """ 1401 if not alias.startswith('flex.'): 1402 return 1403 1404 try: 1405 if alias.startswith('flex.messaging.messages'): 1406 import pyamf.flex.messaging 1407 elif alias.startswith('flex.messaging.io'): 1408 import pyamf.flex 1409 elif alias.startswith('flex.data.messages'): 1410 import pyamf.flex.data 1411 1412 return CLASS_CACHE[alias] 1413 except KeyError: 1414 raise UnknownClassAlias(alias)
1415 1416
1417 -def add_type(type_, func=None):
1418 """ 1419 Adds a custom type to L{TYPE_MAP}. A custom type allows fine grain control 1420 of what to encode to an AMF data stream. 1421 1422 @raise TypeError: Unable to add as a custom type (expected a class or callable). 1423 @raise KeyError: Type already exists. 1424 """ 1425 def _check_type(type_): 1426 if not (isinstance(type_, (type, types.ClassType)) or callable(type_)): 1427 raise TypeError(r'Unable to add '%r' as a custom type (expected a ' 1428 'class or callable)' % (type_,))
1429 1430 if isinstance(type_, list): 1431 type_ = tuple(type_) 1432 1433 if type_ in TYPE_MAP: 1434 raise KeyError('Type %r already exists' % (type_,)) 1435 1436 if isinstance(type_, types.TupleType): 1437 for x in type_: 1438 _check_type(x) 1439 else: 1440 _check_type(type_) 1441 1442 TYPE_MAP[type_] = func 1443 1444
1445 -def get_type(type_):
1446 """ 1447 Gets the declaration for the corresponding custom type. 1448 1449 @raise KeyError: Unknown type. 1450 """ 1451 if isinstance(type_, list): 1452 type_ = tuple(type_) 1453 1454 for (k, v) in TYPE_MAP.iteritems(): 1455 if k == type_: 1456 return v 1457 1458 raise KeyError("Unknown type %r" % (type_,))
1459 1460
1461 -def remove_type(type_):
1462 """ 1463 Removes the custom type declaration. 1464 1465 @return: Custom type declaration. 1466 """ 1467 declaration = get_type(type_) 1468 1469 del TYPE_MAP[type_] 1470 1471 return declaration
1472 1473
1474 -def add_error_class(klass, code):
1475 """ 1476 Maps an exception class to a string code. Used to map remoting C{onStatus} 1477 objects to an exception class so that an exception can be built to 1478 represent that error:: 1479 1480 class AuthenticationError(Exception): 1481 pass 1482 1483 An example: C{add_error_class(AuthenticationError, 'Auth.Failed')} 1484 1485 @type code: C{str} 1486 1487 @raise TypeError: C{klass} must be a C{class} type. 1488 @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class. 1489 @raise ValueError: Code is already registered. 1490 """ 1491 if not isinstance(code, basestring): 1492 code = str(code) 1493 1494 if not isinstance(klass, (type, types.ClassType)): 1495 raise TypeError("klass must be a class type") 1496 1497 mro = inspect.getmro(klass) 1498 1499 if not Exception in mro: 1500 raise TypeError('Error classes must subclass the __builtin__.Exception class') 1501 1502 if code in ERROR_CLASS_MAP.keys(): 1503 raise ValueError('Code %s is already registered' % (code,)) 1504 1505 ERROR_CLASS_MAP[code] = klass
1506 1507
1508 -def remove_error_class(klass):
1509 """ 1510 Removes a class from C{ERROR_CLASS_MAP}. 1511 1512 @raise ValueError: Code is not registered. 1513 @raise ValueError: Class is not registered. 1514 @raise TypeError: Invalid type, expected C{class} or C{string}. 1515 """ 1516 if isinstance(klass, basestring): 1517 if not klass in ERROR_CLASS_MAP.keys(): 1518 raise ValueError('Code %s is not registered' % (klass,)) 1519 elif isinstance(klass, (type, types.ClassType)): 1520 classes = ERROR_CLASS_MAP.values() 1521 if not klass in classes: 1522 raise ValueError('Class %s is not registered' % (klass,)) 1523 1524 klass = ERROR_CLASS_MAP.keys()[classes.index(klass)] 1525 else: 1526 raise TypeError("Invalid type, expected class or string") 1527 1528 del ERROR_CLASS_MAP[klass]
1529 1530
1531 -def register_alias_type(klass, *args):
1532 """ 1533 This function allows you to map subclasses of L{ClassAlias} to classes 1534 listed in C{args}. 1535 1536 When an object is read/written from/to the AMF stream, a paired 1537 L{ClassAlias} instance is created (or reused), based on the Python class 1538 of that object. L{ClassAlias} provides important metadata for the class 1539 and can also control how the equivalent Python object is created, how the 1540 attributes are applied etc. 1541 1542 Use this function if you need to do something non-standard. 1543 1544 @see: L{pyamf.adapters._google_appengine_ext_db.DataStoreClassAlias} for a 1545 good example. 1546 @since: 0.4 1547 @raise RuntimeError: Type is already registered. 1548 @raise TypeError: C{klass} must be a class. 1549 @raise ValueError: New aliases must subclass L{pyamf.ClassAlias}. 1550 @raise ValueError: At least one type must be supplied. 1551 """ 1552 1553 def check_type_registered(arg): 1554 # FIXME: Create a reverse index of registered types and do a quicker lookup 1555 for k, v in ALIAS_TYPES.iteritems(): 1556 for kl in v: 1557 if arg is kl: 1558 raise RuntimeError('%r is already registered under %r' % (arg, k))
1559 1560 if not isinstance(klass, (type, types.ClassType)): 1561 raise TypeError('klass must be class') 1562 1563 if not issubclass(klass, ClassAlias): 1564 raise ValueError('New aliases must subclass pyamf.ClassAlias') 1565 1566 if len(args) == 0: 1567 raise ValueError('At least one type must be supplied') 1568 1569 if len(args) == 1 and callable(args[0]): 1570 c = args[0] 1571 1572 check_type_registered(c) 1573 else: 1574 for arg in args: 1575 if not isinstance(arg, (type, types.ClassType)): 1576 raise TypeError('%r must be class' % (arg,)) 1577 1578 check_type_registered(arg) 1579 1580 ALIAS_TYPES[klass] = args 1581 1582
1583 -def register_package(module=None, package=None, separator='.', ignore=[], strict=True):
1584 """ 1585 This is a helper function that takes the concept of Actionscript packages 1586 and registers all the classes in the supplied Python module under that 1587 package. It auto-aliased all classes in C{module} based on C{package}. 1588 1589 e.g. C{mymodule.py}:: 1590 class User(object): 1591 pass 1592 1593 class Permission(object): 1594 pass 1595 1596 >>> import mymodule 1597 >>> pyamf.register_package(mymodule, 'com.example.app') 1598 1599 Now all instances of C{mymodule.User} will appear in Actionscript under the 1600 alias 'com.example.app.User'. Same goes for C{mymodule.Permission} - the 1601 Actionscript alias is 'com.example.app.Permission'. The reverse is also 1602 true, any objects with the correct aliases will now be instances of the 1603 relevant Python class. 1604 1605 This function respects the C{__all__} attribute of the module but you can 1606 have further control of what not to auto alias by populating the C{ignore} 1607 argument. 1608 1609 This function provides the ability to register the module it is being 1610 called in, an example: 1611 1612 >>> class Foo: 1613 ... pass 1614 ... 1615 >>> class Bar: 1616 ... pass 1617 ... 1618 >>> import pyamf 1619 >>> pyamf.register_package('foo') 1620 1621 @param module: The Python module that will contain all the classes to 1622 auto alias. 1623 @type module: C{module} or C{dict} 1624 @param package: The base package name. e.g. 'com.example.app'. If this 1625 is C{None} then the value is inferred from module.__name__. 1626 @type package: C{str} or C{unicode} or C{None} 1627 @param separator: The separator used to append to C{package} to form the 1628 complete alias. 1629 @type separator: C{str} 1630 @param ignore: To give fine grain control over what gets aliased and what 1631 doesn't, supply a list of classes that you B{do not} want to be aliased. 1632 @type ignore: C{iterable} 1633 @param strict: If this value is C{True} then only classes that originate 1634 from C{module} will be registered, all others will be left in peace. 1635 @type strict: C{bool} 1636 @return: A collection of all the classes that were registered and their 1637 respective L{ClassAlias} objects. 1638 @since: 0.5 1639 """ 1640 if isinstance(module, basestring): 1641 if module == '': 1642 raise TypeError('Cannot get list of classes from %r' % (module,)) 1643 1644 package = module 1645 module = None 1646 1647 if module is None: 1648 import inspect 1649 1650 prev_frame = inspect.stack()[1][0] 1651 module = prev_frame.f_locals 1652 1653 if type(module) is dict: 1654 has = lambda x: x in module.keys() 1655 get = module.__getitem__ 1656 else: 1657 has = lambda x: hasattr(module, x) 1658 get = lambda x: getattr(module, x) 1659 1660 if package is None: 1661 if has('__name__'): 1662 package = get('__name__') 1663 else: 1664 raise TypeError('Cannot get list of classes from %r' % (module,)) 1665 1666 if has('__all__'): 1667 keys = get('__all__') 1668 elif hasattr(module, '__dict__'): 1669 keys = module.__dict__.keys() 1670 elif hasattr(module, 'keys'): 1671 keys = module.keys() 1672 else: 1673 raise TypeError('Cannot get list of classes from %r' % (module,)) 1674 1675 def check_attr(attr): 1676 if not isinstance(attr, (types.ClassType, types.TypeType)): 1677 return False 1678 1679 if attr.__name__ in ignore: 1680 return False 1681 1682 try: 1683 if strict and attr.__module__ != get('__name__'): 1684 return False 1685 except AttributeError: 1686 return False 1687 1688 return True
1689 1690 # gotta love python 1691 classes = filter(check_attr, [get(x) for x in keys]) 1692 1693 registered = {} 1694 1695 for klass in classes: 1696 alias = '%s%s%s' % (package, separator, klass.__name__) 1697 1698 registered[klass] = register_class(klass, alias) 1699 1700 return registered 1701 1702 1703 # init module here 1704 register_class(ASObject) 1705 register_class_loader(flex_loader) 1706 register_class_loader(blaze_loader) 1707 register_alias_type(TypedObjectClassAlias, TypedObject) 1708 register_alias_type(ErrorAlias, Exception) 1709 1710 register_adapters() 1711