Package pyamf

[hide private]
[frames] | no frames]

# Source Code for Package pyamf

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