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