Commits

beyzend committed 75ff3e2 Draft

-Updated PyAMF to 0.6
-Added mob service for remote gateway calls.

Comments (0)

Files changed (39)

 <?xml version="1.0"?>
 <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
 <cross-domain-policy>
-<!-- Place top level domain name -->
-<allow-access-from domain="http://projectace3.appspot.com" secure="false"/>
-<allow-access-from domain="http://projectace3.appspot.com" to-ports="80,443">projectace3.appspot</allow-access-from>
-<allow-http-request-headers-from domain="localhost" headers="*" />
-<!-- use if you need access from subdomains. testing/www/staging.domain.com -->
-<allow-access-from domain="http://localhost" secure="false" />
-<allow-access-from domain="http://localhost" to-ports="80,443" />
-<allow-http-request-headers-from domain="*.gaeswf.org" headers="*" />
+<allow-access-from domain="*" />
 </cross-domain-policy>
 from pyamf.remoting.gateway.wsgi import WSGIGateway
 
 # You can also use a wildcard to import services here.
-from services import user
-from services import photo
-
+#from services import user
+#from services import photo
+from services import mob
 # Service mappings
 s = {
-	'user': user,
-	'photo': photo
+	#'user': user,
+	#'photo': photo
+	'mob': mob.mob
 }
 
 def main():
 		self.response.out.write(template.render(path, template_vars, debug=True))
 
 def main():
+	
+	
+
+	os.environ['DJANGO_SETTINGS_MODULE'] = 'myapp.settings'
+
+	
 	application = webapp.WSGIApplication([
 		('/', IndexHandler),
 		#('/test', TestUploadHandler),

pyamf/__init__.py

-# Copyright (c) 2007-2008 The PyAMF Project.
-# See LICENSE for details.
+# Copyright (c) The PyAMF Project.
+# See LICENSE.txt for details.
 
 """
-B{PyAMF} provides B{A}ction B{M}essage B{F}ormat
-(U{AMF<http://en.wikipedia.org/wiki/Action_Message_Format>}) support for
-Python that is compatible with the
-U{Flash Player<http://en.wikipedia.org/wiki/Flash_Player>}.
-
-@author: U{Arnar Birgisson<mailto:arnarbi@gmail.com>}
-@author: U{Thijs Triemstra<mailto:info@collab.nl>}
-@author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
-
-@copyright: Copyright (c) 2007-2008 The PyAMF Project. All Rights Reserved.
-@contact: U{dev@pyamf.org<mailto:dev@pyamf.org>}
-@see: U{http://pyamf.org}
+U{PyAMF<http://pyamf.org>} provides Action Message Format (U{AMF
+<http://en.wikipedia.org/wiki/Action_Message_Format>}) support for Python that
+is compatible with the Adobe U{Flash Player
+<http://en.wikipedia.org/wiki/Flash_Player>}.
 
 @since: October 2007
-@status: Beta
-@version: 0.3.0
+@status: Production/Stable
 """
 
 import types
+import inspect
 
-from pyamf import util
+from pyamf import util, _version
 from pyamf.adapters import register_adapters
+from pyamf import python
+from pyamf.alias import ClassAlias, UnknownClassAlias
+
 
 __all__ = [
     'register_class',
     'register_class_loader',
     'encode',
     'decode',
-    '__version__']
+    '__version__',
+    'version'
+]
 
 #: PyAMF version number.
-__version__ = (0, 3, 0, 'beta')
+__version__ = version = _version.version
 
-#: Class mapping support.
+#: Class alias mapping support. Contains two types of keys: The string alias
+#: related to the class and the class object itself. Both point to the linked
+#: L{ClassAlias} object.
+#: @see: L{register_class}, L{unregister_class}, and L{register_package}
 CLASS_CACHE = {}
-#: Class loaders.
-CLASS_LOADERS = []
+#: Class loaders. An iterable of callables that are handed a string alias and
+#: return a class object or C{None} it not handled.
+#: @see: L{register_class_loader} and L{unregister_class_loader}
+CLASS_LOADERS = set()
+#: Custom type map.
+#: @see: L{get_type}, L{add_type}, and L{remove_type}
+TYPE_MAP = {}
+#: Maps error classes to string codes.
+#: @see: L{add_error_class} and L{remove_error_class}
+ERROR_CLASS_MAP = {
+    TypeError.__name__: TypeError,
+    KeyError.__name__: KeyError,
+    LookupError.__name__: LookupError,
+    IndexError.__name__: IndexError,
+    NameError.__name__: NameError,
+    ValueError.__name__: ValueError
+}
+#: Alias mapping support.
+#: @see: L{get_class_alias}, L{register_alias_type}, and L{unregister_alias_type}
+ALIAS_TYPES = {}
 
-#: Custom type map.
-TYPE_MAP = {}
-
-#: Maps error classes to string codes.
-ERROR_CLASS_MAP = {}
-
-#: Specifies that objects are serialized using AMF for ActionScript 1.0 and 2.0.
+#: Specifies that objects are serialized using AMF for ActionScript 1.0
+#: and 2.0 that were introduced in the Adobe Flash Player 6.
 AMF0 = 0
-#: Specifies that objects are serialized using AMF for ActionScript 3.0.
+#: Specifies that objects are serialized using AMF for ActionScript 3.0
+#: that was introduced in the Adobe Flash Player 9.
 AMF3 = 3
 #: Supported AMF encoding types.
+#: @see: L{AMF0}, L{AMF3}, and L{DEFAULT_ENCODING}
 ENCODING_TYPES = (AMF0, AMF3)
 
-class ClientTypes:
+#: Default encoding
+DEFAULT_ENCODING = AMF3
+
+
+class UndefinedType(object):
     """
-    Typecodes used to identify AMF clients and servers.
+    Represents the C{undefined} value in the Adobe Flash Player client.
+    """
+    def __repr__(self):
+        return 'pyamf.Undefined'
 
-    @see: U{Flash Player on WikiPedia (external)
-    <http://en.wikipedia.org/wiki/Flash_Player>}
-    @see: U{Flash Media Server on WikiPedia (external)
-    <http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server>}
-    """
-    #: Specifies a Flash Player 6.0 - 8.0 client.
-    Flash6   = 0
-    #: Specifies a FlashCom / Flash Media Server client.
-    FlashCom = 1
-    #: Specifies a Flash Player 9.0 client.
-    Flash9   = 3
+#: Represents the C{undefined} value in the Adobe Flash Player client.
+Undefined = UndefinedType()
 
-#: List of AMF client typecodes.
-CLIENT_TYPES = []
-
-for x in ClientTypes.__dict__:
-    if not x.startswith('_'):
-        CLIENT_TYPES.append(ClientTypes.__dict__[x])
-del x
-
-#: Represents the C{undefined} value in a Flash client.
-Undefined = object()
 
 class BaseError(Exception):
     """
     All AMF related errors should be subclassed from this class.
     """
 
+
 class DecodeError(BaseError):
     """
     Raised if there is an error in decoding an AMF data stream.
     """
 
+
 class EOStream(BaseError):
     """
     Raised if the data stream has come to a natural end.
     """
 
+
 class ReferenceError(BaseError):
     """
-    Raised if an AMF data stream refers to a non-existent object
-    or string reference.
+    Raised if an AMF data stream refers to a non-existent object or string
+    reference (in the case of AMF3).
     """
 
+
 class EncodeError(BaseError):
     """
-    Raised if the element could not be encoded to the stream.
-
-    @bug: See U{Docuverse blog (external)
-    <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>}
-    for more info about the empty key string array bug.
+    Raised if the element could not be encoded to AMF.
     """
 
-class UnknownClassAlias(BaseError):
-    """
-    Raised if the AMF stream specifies a class that does not
-    have an alias.
-
-    @see: L{register_class}
-    """
-
-class BaseContext(object):
-    """
-    I hold the AMF context for en/decoding streams.
-    """
-
-    def __init__(self):
-        self.clear()
-
-    def clear(self):
-        self.objects = []
-        self.rev_objects = {}
-
-        self.class_aliases = {}
-
-    def getObject(self, ref):
-        """
-        Gets an object based on a reference.
-
-        @raise TypeError: Bad reference type.
-        """
-        if not isinstance(ref, (int, long)):
-            raise TypeError, "Bad reference type"
-
-        try:
-            return self.objects[ref]
-        except IndexError:
-            raise ReferenceError
-
-    def getObjectReference(self, obj):
-        """
-        Gets a reference for an object.
-        """
-        try:
-            return self.rev_objects[id(obj)]
-        except KeyError:
-            raise ReferenceError
-
-    def addObject(self, obj):
-        """
-        Adds a reference to C{obj}.
-
-        @type obj: C{mixed}
-        @param obj: The object to add to the context.
-
-        @rtype: C{int}
-        @return: Reference to C{obj}.
-        """
-        self.objects.append(obj)
-        idx = len(self.objects) - 1
-        self.rev_objects[id(obj)] = idx
-
-        return idx
-
-    def getClassAlias(self, klass):
-        """
-        Gets a class alias based on the supplied C{klass}.
-        """
-        if klass not in self.class_aliases:
-            try:
-                self.class_aliases[klass] = get_class_alias(klass)
-            except UnknownClassAlias:
-                self.class_aliases[klass] = None
-
-        return self.class_aliases[klass]
-
-    def __copy__(self):
-        raise NotImplementedError
 
 class ASObject(dict):
     """
-    This class represents a Flash Actionscript Object (typed or untyped).
+    Represents a Flash Actionscript Object (typed or untyped).
 
-    I supply a C{__builtin__.dict} interface to support get/setattr calls.
+    I supply a C{dict} interface to support C{getattr}/C{setattr} calls.
     """
 
-    def __init__(self, *args, **kwargs):
-        dict.__init__(self, *args, **kwargs)
+    class __amf__:
+        dynamic = True
 
     def __getattr__(self, k):
         try:
             return self[k]
         except KeyError:
-            raise AttributeError, 'unknown attribute \'%s\'' % k
+            raise AttributeError('Unknown attribute \'%s\'' % (k,))
 
     def __setattr__(self, k, v):
         self[k] = v
     def __repr__(self):
         return dict.__repr__(self)
 
-class MixedArray(dict):
-    """
-    Used to be able to specify the mixedarray type.
-    """
-
-class ClassMetaData(list):
-    """
-    I hold a list of tags relating to the class. The idea behind this is
-    to emulate the metadata tags you can supply to ActionScript,
-    e.g. static/dynamic.
-    """
-
-    _allowed_tags = (
-        ('static', 'dynamic', 'external'),
-        ('amf3', 'amf0'),
-        ('anonymous',),
-    )
-
-    def __init__(self, *args):
-        if len(args) == 1 and hasattr(args[0], '__iter__'):
-            for x in args[0]:
-                self.append(x)
-        else:
-            for x in args:
-                self.append(x)
-
-    def _is_tag_allowed(self, x):
-        for y in self._allowed_tags:
-            if isinstance(y, (types.ListType, types.TupleType)):
-                if x in y:
-                    return (True, y)
-            else:
-                if x == y:
-                    return (True, None)
-
-        return (False, None)
-
-    def append(self, x):
-        """
-        Adds a tag to the metadata.
-
-        @param x: Tag.
-        @type x:
-
-        @raise ValueError: Unknown tag.
-        """
-        x = str(x).lower()
-
-        allowed, tags = self._is_tag_allowed(x)
-
-        if not allowed:
-            raise ValueError("Unknown tag %s" % x)
-
-        if tags is not None:
-            # check to see if a tag in the list is about to be clobbered if so,
-            # raise a warning
-            for y in tags:
-                if y in self:
-                    if x != y:
-                        import warnings
-
-                        warnings.warn(
-                            "Previously defined tag %s superceded by %s" % (
-                                y, x))
-
-                    list.pop(self, self.index(y))
-                    break
-
-        list.append(self, x)
-
-    def __contains__(self, other):
-        return list.__contains__(self, str(other).lower())
-
-    # TODO nick: deal with slices
-
-class ClassAlias(object):
-    """
-    Class alias.
-
-    All classes are initially set to a dynamic state.
-
-    @ivar attrs: A list of attributes to encode for this class.
-    @type attrs: C{list}
-    @ivar metadata: A list of metadata tags similar to ActionScript tags.
-    @type metadata: C{list}
-    """
-    def __init__(self, klass, alias, attrs=None, attr_func=None, metadata=[]):
-        """
-        @type klass: C{class}
-        @param klass: The class to alias.
-        @type alias: C{str}
-        @param alias: The alias to the class e.g. C{org.example.Person}. If the
-            value of this is C{None}, then it is worked out based on the C{klass}.
-            The anonymous tag is also added to the class.
-        @type attrs:
-        @param attrs:
-        @type metadata:
-        @param metadata:
-
-        @raise TypeError: The C{klass} must be a class type.
-        @raise TypeError: The C{read_func} must be callable.
-        @raise TypeError: The C{write_func} must be callable.
-        """
-        if not isinstance(klass, (type, types.ClassType)):
-            raise TypeError, "klass must be a class type"
-
-        self.metadata = ClassMetaData(metadata)
-
-        if alias is None:
-            self.metadata.append('anonymous')
-            alias = "%s.%s" % (klass.__module__, klass.__name__,)
-
-        self.klass = klass
-        self.alias = alias
-        self.attr_func = attr_func
-        self.attrs = attrs
-
-        if 'external' in self.metadata:
-            # class is declared as external, lets check
-            if not hasattr(klass, '__readamf__'):
-                raise AttributeError("An externalised class was specified, but"
-                    " no __readamf__ attribute was found for class %s" % (
-                        klass.__name__))
-
-            if not hasattr(klass, '__writeamf__'):
-                raise AttributeError("An externalised class was specified, but"
-                    " no __writeamf__ attribute was found for class %s" % (
-                        klass.__name__))
-
-            if not isinstance(klass.__readamf__, types.UnboundMethodType):
-                raise TypeError, "%s.__readamf__ must be callable" % (
-                    klass.__name__)
-
-            if not isinstance(klass.__writeamf__, types.UnboundMethodType):
-                raise TypeError, "%s.__writeamf__ must be callable" % (
-                    klass.__name__)
-
-        if 'dynamic' in self.metadata:
-            if attr_func is not None and not callable(attr_func):
-                raise TypeError, "attr_func must be callable"
-
-        if 'static' in self.metadata:
-            if attrs is None:
-                raise ValueError, "attrs keyword must be specified for static classes"
-
-    def __call__(self, *args, **kwargs):
-        """
-        Creates an instance of the klass.
-
-        @return: Instance of C{self.klass}.
-        """
-        if hasattr(self.klass, '__setstate__') or hasattr(self.klass, '__getstate__'):
-            if type(self.klass) is types.TypeType:  # new-style class
-                return self.klass.__new__(self.klass)
-            elif type(self.klass) is types.ClassType: # classic class
-                return util.make_classic_instance(self.klass)
-
- 	        raise TypeError, 'invalid class type %r' % self.klass
-
-        return self.klass(*args, **kwargs)
-
-    def __str__(self):
-        return self.alias
-
-    def __repr__(self):
-        return '<ClassAlias alias=%s klass=%s @ %s>' % (
-            self.alias, self.klass, id(self))
-
-    def __eq__(self, other):
-        if isinstance(other, basestring):
-            return self.alias == other
-        elif isinstance(other, self.__class__):
-            return self.klass == other.klass
-        elif isinstance(other, (type, types.ClassType)):
-            return self.klass == other
-        else:
-            return False
-
     def __hash__(self):
         return id(self)
 
-    def getAttrs(self, obj, attrs=None, traverse=True):
-        if attrs is None:
-            attrs = []
 
-        did_something = False
+class MixedArray(dict):
+    """
+    Used to be able to specify the C{mixedarray} type.
+    """
 
-        if self.attrs is not None:
-            did_something = True
-            attrs += self.attrs
 
-        if 'dynamic' in self.metadata and self.attr_func is not None:
-            did_something = True
-            extra_attrs = self.attr_func(obj)
+class TypedObject(dict):
+    """
+    This class is used when a strongly typed object is decoded but there is no
+    registered class to apply it to.
 
-            for key in extra_attrs:
-                if key not in attrs:
-                    attrs.append(key)
+    This object can only be used for standard streams - i.e. not externalized
+    data. If encountered, a L{DecodeError} will be raised.
 
-        if traverse is True:
-            for base in util.get_mro(obj.__class__):
-                try:
-                    alias = get_class_alias(base)
-                except UnknownClassAlias:
-                    continue
+    @ivar alias: The alias of the typed object.
+    @type alias: C{string}
+    @since: 0.4
+    """
 
-                x = alias.getAttrs(obj, attrs, False)
+    def __init__(self, alias):
+        dict.__init__(self)
 
-                if x is not None:
-                    attrs += x
-                    did_something = True
+        self.alias = alias
 
-        if did_something is False:
-            return None
+    def __readamf__(self, o):
+        raise DecodeError('Unable to decode an externalised stream with '
+            'class alias \'%s\'.\n\nA class alias was found and because '
+            'strict mode is False an attempt was made to decode the object '
+            'automatically. To decode this stream, a registered class with '
+            'the alias and a corresponding __readamf__ method will be '
+            'required.' % (self.alias,))
 
-        a = []
+    def __writeamf__(self, o):
+        raise EncodeError('Unable to encode an externalised stream with '
+            'class alias \'%s\'.\n\nA class alias was found and because '
+            'strict mode is False an attempt was made to encode the object '
+            'automatically. To encode this stream, a registered class with '
+            'the alias and a corresponding __writeamf__ method will be '
+            'required.' % (self.alias,))
 
-        for x in attrs:
-            if x not in a:
-                a.append(x)
 
-        return a
+class TypedObjectClassAlias(ClassAlias):
+    """
+    The meta class for L{TypedObject} used to adapt PyAMF.
 
-class BaseDecoder(object):
+    @since: 0.4
     """
-    Base AMF decoder.
 
-    @ivar context_class: The context for the decoding.
-    @type context_class: An instance of C{BaseDecoder.context_class}
-    @ivar type_map:
-    @type type_map: C{list}
-    @ivar stream: The underlying data stream.
-    @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
+    klass = TypedObject
+
+    def __init__(self, *args, **kwargs):
+        ClassAlias.__init__(self, self.klass, kwargs.pop('alias', args[0]))
+
+    def createInstance(self, codec=None):
+        return self.klass(self.alias)
+
+    def checkClass(kls, klass):
+        pass
+
+
+class ErrorAlias(ClassAlias):
     """
-    context_class = BaseContext
-    type_map = {}
+    Adapts Python exception objects to Adobe Flash Player error objects.
 
-    def __init__(self, data=None, context=None):
-        """
-        @type   data: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
-        @param  data: Data stream.
-        @type   context: L{Context<pyamf.amf0.Context>}
-        @param  context: Context.
-        @raise TypeError: The C{context} parameter must be of
-        type L{Context<pyamf.amf0.Context>}.
-        """
-        # coersce data to BufferedByteStream
-        if isinstance(data, util.BufferedByteStream):
-            self.stream = data
-        else:
-            self.stream = util.BufferedByteStream(data)
+    @since: 0.5
+    """
 
-        if context == None:
-            self.context = self.context_class()
-        elif isinstance(context, self.context_class):
-            self.context = context
-        else:
-            raise TypeError, "context must be of type %s.%s" % (
-                self.context_class.__module__, self.context_class.__name__)
+    def getCustomProperties(self):
+        self.exclude_attrs.update(['args'])
 
-    def readType(self):
-        raise NotImplementedError
+    def getEncodableAttributes(self, obj, **kwargs):
+        attrs = ClassAlias.getEncodableAttributes(self, obj, **kwargs)
 
-    def readElement(self):
-        """
-        Reads an AMF3 element from the data stream.
+        attrs['message'] = str(obj)
+        attrs['name'] = obj.__class__.__name__
 
-        @raise DecodeError: The ActionScript type is unknown.
-        @raise EOStream: No more data left to decode.
-        """
-        try:
-            type = self.readType()
-        except EOFError:
-            raise EOStream
+        return attrs
 
-        try:
-            func = getattr(self, self.type_map[type])
-        except KeyError, e:
-            raise DecodeError, "Unsupported ActionScript type 0x%02x" % type
 
-        return func()
+def register_class(klass, alias=None):
+    """
+    Registers a class to be used in the data streaming. This is the equivalent
+    to the C{[RemoteClass(alias="foobar")]} AS3 metatag.
 
-    def __iter__(self):
-        try:
-            while 1:
-                yield self.readElement()
-        except EOFError:
-            raise StopIteration
+    @return: The registered L{ClassAlias} instance.
+    @see: L{unregister_class}
+    """
+    meta = util.get_class_meta(klass)
 
-class CustomTypeFunc(object):
-    """
-    Custom type mappings.
-    """
-    def __init__(self, encoder, func):
-        self.encoder = encoder
-        self.func = func
+    if alias is not None:
+        meta['alias'] = alias
 
-    def __call__(self, data):
-        self.encoder.writeElement(self.func(data))
+    alias_klass = util.get_class_alias(klass) or ClassAlias
 
-class BaseEncoder(object):
-    """
-    Base AMF encoder.
+    x = alias_klass(klass, defer=True, **meta)
 
-    @ivar type_map: A list of types -> functions. The types is a list of
-        possible instances or functions to call (that return a C{bool}) to
-        determine the correct function to call to encode the data.
-    @type type_map: C{list}
-    @ivar context_class: Holds the class that will create context objects for
-        the implementing C{Encoder}.
-    @type context_class: C{type} or C{types.ClassType}
-    @ivar stream: The underlying data stream.
-    @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
-    @ivar context: The context for the encoding.
-    @type context: An instance of C{BaseEncoder.context_class}
-    """
-    context_class = BaseContext
-    type_map = []
+    if not x.anonymous:
+        CLASS_CACHE[x.alias] = x
 
-    def __init__(self, data=None, context=None):
-        """
-        @type   data: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
-        @param  data: Data stream.
-        @type   context: L{Context<pyamf.amf0.Context>}
-        @param  context: Context.
-        @raise TypeError: The C{context} parameter must be of type
-            L{Context<pyamf.amf0.Context>}.
-        """
-        # coersce data to BufferedByteStream
-        if isinstance(data, util.BufferedByteStream):
-            self.stream = data
-        else:
-            self.stream = util.BufferedByteStream(data)
-
-        if context == None:
-            self.context = self.context_class()
-        elif isinstance(context, self.context_class):
-            self.context = context
-        else:
-            raise TypeError, "context must be of type %s.%s" % (
-                self.context_class.__module__, self.context_class.__name__)
-
-        self._write_elem_func_cache = {}
-
-    def _getWriteElementFunc(self, data):
-        """
-        Gets a function used to encode the data.
-
-        @type   data: C{mixed}
-        @param  data: Python data.
-        @rtype: callable or C{None}.
-        @return: The function used to encode data to the stream.
-        """
-        for type_, func in TYPE_MAP.iteritems():
-            try:
-                if isinstance(data, type_):
-                    return CustomTypeFunc(self, func)
-            except TypeError:
-                if callable(type_) and type_(data):
-                    return CustomTypeFunc(self, func)
-
-        for tlist, method in self.type_map:
-            for t in tlist:
-                try:
-                    if isinstance(data, t):
-                        return getattr(self, method)
-                except TypeError:
-                    if callable(t) and t(data):
-                        return getattr(self, method)
-
-        return None
-
-    def _writeElementFunc(self, data):
-        """
-        Gets a function used to encode the data.
-
-        @type   data: C{mixed}
-        @param  data: Python data.
-        @rtype: callable or C{None}.
-        @return: The function used to encode data to the stream.
-        """
-        try:
-            key = data.__class__
-        except AttributeError:
-            return self._getWriteElementFunc(data)
-
-        if key not in self._write_elem_func_cache:
-            self._write_elem_func_cache[key] =  self._getWriteElementFunc(data)
-
-        return self._write_elem_func_cache[key]
-
-    def writeElement(self, data):
-        """
-        Writes the data.
-
-        @type   data: C{mixed}
-        @param  data: The data to be encoded to the data stream.
-        """
-        raise NotImplementedError
-
-def register_class(klass, alias=None, attrs=None, attr_func=None, metadata=[]):
-    """
-    Registers a class to be used in the data streaming.
-
-    @type alias: C{str}
-    @param alias: The alias of klass, i.e. C{org.example.Person}.
-    @param attrs: A list of attributes that will be encoded for the class.
-    @type attrs: C{list} or C{None}
-    @type attr_func:
-    @param attr_func:
-    @type metadata:
-    @param metadata:
-    @raise TypeError: The C{klass} is not callable.
-    @raise ValueError: The C{klass} or C{alias} is already registered.
-    @return: The registered L{ClassAlias}.
-    """
-    if not callable(klass):
-        raise TypeError, "klass must be callable"
-
-    if klass in CLASS_CACHE:
-        raise ValueError, "klass %s already registered" % klass
-
-    if alias is not None and alias in CLASS_CACHE.keys():
-        raise ValueError, "alias '%s' already registered" % alias
-
-    x = ClassAlias(klass, alias, attr_func=attr_func, attrs=attrs,
-        metadata=metadata)
-
-    if alias is None:
-        alias = "%s.%s" % (klass.__module__, klass.__name__,)
-
-    CLASS_CACHE[alias] = x
+    CLASS_CACHE[klass] = x
 
     return x
 
+
 def unregister_class(alias):
     """
-    Deletes a class from the cache.
+    Opposite of L{register_class}.
 
-    If C{alias} is a class, the matching alias is found.
-
-    @type alias: C{class} or C{str}
-    @param alias: Alias for class to delete.
     @raise UnknownClassAlias: Unknown alias.
     """
-    if isinstance(alias, (type, types.ClassType)):
-        for s, a in CLASS_CACHE.iteritems():
-            if a.klass == alias:
-                alias = s
-                break
     try:
-        del CLASS_CACHE[alias]
+        x = CLASS_CACHE[alias]
     except KeyError:
-        raise UnknownClassAlias, "Unknown alias %s" % alias
+        raise UnknownClassAlias('Unknown alias %r' % (alias,))
+
+    if not x.anonymous:
+        del CLASS_CACHE[x.alias]
+
+    del CLASS_CACHE[x.klass]
+
+    return x
+
+
+def get_class_alias(klass_or_alias):
+    """
+    Finds the L{ClassAlias} that is registered to C{klass_or_alias}.
+
+    If a string is supplied and no related L{ClassAlias} is found, the alias is
+    loaded via L{load_class}.
+
+    @raise UnknownClassAlias: Unknown alias
+    """
+    if isinstance(klass_or_alias, python.str_types):
+        try:
+            return CLASS_CACHE[klass_or_alias]
+        except KeyError:
+            return load_class(klass_or_alias)
+
+    try:
+        return CLASS_CACHE[klass_or_alias]
+    except KeyError:
+        raise UnknownClassAlias('Unknown alias for %r' % (klass_or_alias,))
+
 
 def register_class_loader(loader):
     """
-    Registers a loader that is called to provide the C{Class} for a specific
+    Registers a loader that is called to provide the C{class} for a specific
     alias.
 
-    The L{loader} is provided with one argument, the C{Class} alias. If the
-    loader succeeds in finding a suitable class then it should return that
-    class, otherwise it should return C{None}.
+    The C{loader} is provided with one argument, the class alias (as a string).
+    If the loader succeeds in finding a suitable class then it should return
+    that class, otherwise it should return C{None}.
 
-    @type loader: C{callable}
-    @param loader:
+    An example::
 
-    @raise TypeError: The C{loader} is not callable.
-    @raise ValueError: The C{loader} is already registered.
+        def lazy_load_from_my_module(alias):
+            if not alias.startswith('foo.bar.'):
+                return None
+
+            from foo import bar
+
+            if alias == 'foo.bar.Spam':
+                return bar.Spam
+            elif alias == 'foo.bar.Eggs':
+                return bar.Eggs
+
+        pyamf.register_class_loader(lazy_load_from_my_module)
+
+    @raise TypeError: C{loader} must be callable
+    @see: L{unregister_class_loader}
     """
-    if not callable(loader):
-        raise TypeError, "loader must be callable"
+    if not hasattr(loader, '__call__'):
+        raise TypeError("loader must be callable")
 
-    if loader in CLASS_LOADERS:
-        raise ValueError, "loader has already been registered"
+    CLASS_LOADERS.update([loader])
 
-    CLASS_LOADERS.append(loader)
 
 def unregister_class_loader(loader):
     """
     Unregisters a class loader.
 
-    @type loader: C{callable}
-    @param loader: The object to be unregistered
+    @param loader: The class loader to be unregistered.
+    @raise LookupError: The C{loader} was not registered.
+    @see: L{register_class_loader}
+    """
+    try:
+        CLASS_LOADERS.remove(loader)
+    except KeyError:
+        raise LookupError("loader not found")
 
-    @raise LookupError: The C{loader} was not registered.
-    """
-    if loader not in CLASS_LOADERS:
-        raise LookupError, "loader not found"
-
-    del CLASS_LOADERS[CLASS_LOADERS.index(loader)]
-
-def get_module(mod_name):
-    """
-    Load a module based on C{mod_name}.
-
-    @type mod_name: C{str}
-    @param mod_name: The module name.
-    @rtype:
-    @return: Module.
-
-    @raise ImportError: Unable to import empty module.
-    """
-    if mod_name is '':
-        raise ImportError, "Unable to import empty module"
-
-    mod = __import__(mod_name)
-    components = mod_name.split('.')
-
-    for comp in components[1:]:
-        mod = getattr(mod, comp)
-
-    return mod
 
 def load_class(alias):
     """
     Finds the class registered to the alias.
 
     The search is done in order:
-      1. Checks if the class name has been registered via L{register_class}.
+      1. Checks if the class name has been registered via L{register_class}
+         or L{register_package}.
       2. Checks all functions registered via L{register_class_loader}.
       3. Attempts to load the class via standard module loading techniques.
 
-    @type alias: C{str}
     @param alias: The class name.
+    @type alias: C{string}
     @raise UnknownClassAlias: The C{alias} was not found.
     @raise TypeError: Expecting class type or L{ClassAlias} from loader.
     @return: Class registered to the alias.
-    @rtype:
+    @rtype: C{classobj}
     """
-    alias = str(alias)
-
     # Try the CLASS_CACHE first
     try:
         return CLASS_CACHE[alias]
     except KeyError:
         pass
 
-    # Check each CLASS_LOADERS in turn
     for loader in CLASS_LOADERS:
         klass = loader(alias)
 
         if klass is None:
             continue
 
-        if isinstance(klass, (type, types.ClassType)):
+        if isinstance(klass, python.class_types):
             return register_class(klass, alias)
         elif isinstance(klass, ClassAlias):
-            CLASS_CACHE[str(alias)] = klass
-        else:
-            raise TypeError, "Expecting class type or ClassAlias from loader"
+            CLASS_CACHE[klass.alias] = klass
+            CLASS_CACHE[klass.klass] = klass
 
-        return klass
+            return klass
 
-    # XXX nick: Are there security concerns for loading classes this way?
+        raise TypeError("Expecting class object or ClassAlias from loader")
+
     mod_class = alias.split('.')
 
     if mod_class:
         klass = mod_class[-1]
 
         try:
-            module = get_module(module)
-        except ImportError, AttributeError:
-            # XXX What to do here?
+            module = util.get_module(module)
+        except (ImportError, AttributeError):
             pass
         else:
             klass = getattr(module, klass)
 
-            if callable(klass):
-                CLASS_CACHE[alias] = klass
+            if isinstance(klass, python.class_types):
+                return register_class(klass, alias)
+            elif isinstance(klass, ClassAlias):
+                CLASS_CACHE[klass.alias] = klass
+                CLASS_CACHE[klass.klass] = klass
 
-                return klass
+                return klass.klass
+            else:
+                raise TypeError("Expecting class type or ClassAlias from loader")
 
     # All available methods for finding the class have been exhausted
-    raise UnknownClassAlias, "Unknown alias %s" % alias
+    raise UnknownClassAlias("Unknown alias for %r" % (alias,))
 
-def get_class_alias(klass):
-    """
-    Finds the alias registered to the class.
 
-    @type klass: C{object} or class
-    @raise UnknownClassAlias: Class not found.
-    @raise TypeError: Expecting C{string} or C{class} type.
-
-    @rtype: L{ClassAlias}
-    @return: The class alias linked to the C{klass}.
-    """
-    if not isinstance(klass, (type, types.ClassType, basestring)):
-        if isinstance(klass, types.InstanceType):
-            klass = klass.__class__
-        elif isinstance(klass, types.ObjectType):
-            klass = type(klass)
-
-    if isinstance(klass, basestring):
-        for a, k in CLASS_CACHE.iteritems():
-            if klass == a:
-                return k
-    else:
-        for a, k in CLASS_CACHE.iteritems():
-            if klass == k.klass:
-                return k
-
-    if isinstance(klass, basestring):
-        return load_class(klass)
-
-    raise UnknownClassAlias, "Unknown alias %s" % klass
-
-def has_alias(obj):
-    """
-    @rtype: C{bool}
-    @return: Alias is available.
-    """
-    try:
-        alias = get_class_alias(obj)
-        return True
-    except UnknownClassAlias:
-        return False
-
-def decode(stream, encoding=AMF0, context=None):
+def decode(stream, *args, **kwargs):
     """
     A generator function to decode a datastream.
 
-    @type   stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
-    @param  stream: AMF data.
-    @type   encoding: C{int}
-    @param  encoding: AMF encoding type.
-    @type   context: L{AMF0 Context<pyamf.amf0.Context>} or
-    L{AMF3 Context<pyamf.amf3.Context>}
-    @param  context: Context.
-    @return: Each element in the stream.
+    @param stream: AMF data to be decoded.
+    @type stream: byte data.
+    @kwarg encoding: AMF encoding type. One of L{ENCODING_TYPES}.
+    @return: A generator that will decode each element in the stream.
     """
-    decoder = _get_decoder_class(encoding)(stream, context)
+    encoding = kwargs.pop('encoding', DEFAULT_ENCODING)
+    decoder = get_decoder(encoding, stream, *args, **kwargs)
 
-    while 1:
-        try:
-	    yield decoder.readElement()
-	except EOStream:
-	    break
+    return decoder
+
 
 def encode(*args, **kwargs):
     """
     A helper function to encode an element.
 
-    @type element: C{mixed}
-    @keyword element: Python data.
-    @type encoding: C{int}
-    @keyword encoding: AMF encoding type.
-    @type context: L{amf0.Context<pyamf.amf0.Context>} or
-    L{amf3.Context<pyamf.amf3.Context>}
-    @keyword context: Context.
+    @param args: The python data to be encoded.
+    @kwarg encoding: AMF encoding type. One of L{ENCODING_TYPES}.
+    @return: A L{util.BufferedByteStream} object that contains the data.
+    """
+    encoding = kwargs.pop('encoding', DEFAULT_ENCODING)
+    encoder = get_encoder(encoding, **kwargs)
 
-    @rtype: C{StringIO}
-    @return: File-like object.
-    """
-    encoding = kwargs.get('encoding', AMF0)
-    context = kwargs.get('context', None)
+    [encoder.writeElement(el) for el in args]
 
-    stream = util.BufferedByteStream()
-    encoder = _get_encoder_class(encoding)(stream, context)
-
-    for el in args:
-        encoder.writeElement(el)
-
+    stream = encoder.stream
     stream.seek(0)
 
     return stream
 
-def get_decoder(encoding, data=None, context=None):
-    return _get_decoder_class(encoding)(data=data, context=context)
 
-def _get_decoder_class(encoding):
+def get_decoder(encoding, *args, **kwargs):
     """
-    Get compatible decoder.
+    Returns a L{codec.Decoder} capable of decoding AMF[C{encoding}] streams.
 
-    @type encoding: C{int}
-    @param encoding: AMF encoding version.
-    @raise ValueError: AMF encoding version is unknown.
+    @raise ValueError: Unknown C{encoding}.
+    """
+    def _get_decoder_class():
+        if encoding == AMF0:
+            try:
+                from cpyamf import amf0
+            except ImportError:
+                from pyamf import amf0
 
-    @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or
-    L{amf3.Decoder<pyamf.amf3.Decoder>}
-    @return: AMF0 or AMF3 decoder.
+            return amf0.Decoder
+        elif encoding == AMF3:
+            try:
+                from cpyamf import amf3
+            except ImportError:
+                from pyamf import amf3
+
+            return amf3.Decoder
+
+        raise ValueError("Unknown encoding %r" % (encoding,))
+
+    return _get_decoder_class()(*args, **kwargs)
+
+
+def get_encoder(encoding, *args, **kwargs):
     """
-    if encoding == AMF0:
-        import amf0
+    Returns a L{codec.Encoder} capable of encoding AMF[C{encoding}] streams.
 
-        return amf0.Decoder
-    elif encoding == AMF3:
-        import amf3
+    @raise ValueError: Unknown C{encoding}.
+    """
+    def _get_encoder_class():
+        if encoding == AMF0:
+            try:
+                from cpyamf import amf0
+            except ImportError:
+                from pyamf import amf0
 
-        return amf3.Decoder
+            return amf0.Encoder
+        elif encoding == AMF3:
+            try:
+                from cpyamf import amf3
+            except ImportError:
+                from pyamf import amf3
 
-    raise ValueError, "Unknown encoding %s" % encoding
+            return amf3.Encoder
 
-def get_encoder(encoding, data=None, context=None):
-    return _get_encoder_class(encoding)(data=data, context=context)
+        raise ValueError("Unknown encoding %r" % (encoding,))
 
-def _get_encoder_class(encoding):
+    return _get_encoder_class()(*args, **kwargs)
+
+
+def blaze_loader(alias):
     """
-    Get compatible encoder.
+    Loader for BlazeDS framework compatibility classes, specifically
+    implementing C{ISmallMessage}.
 
-    @type encoding: C{int}
-    @param encoding: AMF encoding version.
-    @raise ValueError: AMF encoding version is unknown.
+    @see: U{BlazeDS<http://opensource.adobe.com/wiki/display/blazeds/BlazeDS>}
+    @since: 0.5
+    """
+    if alias not in ['DSC', 'DSK']:
+        return
 
-    @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or
-    L{amf3.Encoder<pyamf.amf3.Encoder>}
-    @return: AMF0 or AMF3 encoder.
-    """
-    if encoding == AMF0:
-        import amf0
+    import pyamf.flex.messaging
 
-        return amf0.Encoder
-    elif encoding == AMF3:
-        import amf3
+    return CLASS_CACHE[alias]
 
-        return amf3.Encoder
-
-    raise ValueError, "Unknown encoding %s" % encoding
-
-def get_context(encoding):
-    return _get_context_class(encoding)()
-
-def _get_context_class(encoding):
-    """
-    Gets a compatible context class.
-
-    @type encoding: C{int}
-    @param encoding: AMF encoding version
-    @raise ValueError: AMF encoding version is unknown.
-
-    @rtype: L{amf0.Context<pyamf.amf0.Context>} or
-    L{amf3.Context<pyamf.amf3.Context>}
-    @return: AMF0 or AMF3 context class.
-    """
-    if encoding == AMF0:
-        import amf0
-
-        return amf0.Context
-    elif encoding == AMF3:
-        import amf3
-
-        return amf3.Context
-
-    raise ValueError, "Unknown encoding %s" % encoding
 
 def flex_loader(alias):
     """
     Loader for L{Flex<pyamf.flex>} framework compatibility classes.
 
-    @type alias:
-    @param alias:
-    @raise UnknownClassAlias:
+    @raise UnknownClassAlias: Trying to load an unknown Flex compatibility class.
     """
     if not alias.startswith('flex.'):
         return
 
         return CLASS_CACHE[alias]
     except KeyError:
-        raise UnknownClassAlias, alias
+        raise UnknownClassAlias(alias)
 
-register_class_loader(flex_loader)
 
 def add_type(type_, func=None):
     """
 
     @raise TypeError: Unable to add as a custom type (expected a class or callable).
     @raise KeyError: Type already exists.
+    @see: L{get_type} and L{remove_type}
     """
     def _check_type(type_):
-        if not (isinstance(type_, (type, types.ClassType)) or callable(type_)):
-            raise TypeError, "Unable to add '%r' as a custom type (expected a class or callable)" % type_
+        if not (isinstance(type_, python.class_types) or
+                hasattr(type_, '__call__')):
+            raise TypeError(r'Unable to add '%r' as a custom type (expected a '
+                'class or callable)' % (type_,))
 
     if isinstance(type_, list):
         type_ = tuple(type_)
 
     if type_ in TYPE_MAP:
-        raise KeyError, 'Type %r already exists' % type_
+        raise KeyError('Type %r already exists' % (type_,))
 
     if isinstance(type_, types.TupleType):
         for x in type_:
-           _check_type(x)
+            _check_type(x)
     else:
         _check_type(type_)
 
     TYPE_MAP[type_] = func
 
+
 def get_type(type_):
     """
     Gets the declaration for the corresponding custom type.
 
     @raise KeyError: Unknown type.
+    @see: L{add_type} and L{remove_type}
     """
     if isinstance(type_, list):
         type_ = tuple(type_)
 
-    for (k, v) in TYPE_MAP.iteritems():
+    for k, v in TYPE_MAP.iteritems():
         if k == type_:
             return v
 
-    raise KeyError, "Unknown type %r" % type_
+    raise KeyError("Unknown type %r" % (type_,))
+
 
 def remove_type(type_):
     """
     Removes the custom type declaration.
 
-    @param type_:
-    @type type_:
-    @rtype:
     @return: Custom type declaration.
+    @see: L{add_type} and L{get_type}
     """
     declaration = get_type(type_)
 
 
     return declaration
 
+
 def add_error_class(klass, code):
     """
     Maps an exception class to a string code. Used to map remoting C{onStatus}
     objects to an exception class so that an exception can be built to
-    represent that error::
+    represent that error.
 
-        class AuthenticationError(Exception):
-            pass
+    An example::
 
-    An example: C{add_error_class(AuthenticationError, 'Auth.Failed')}
+        >>> class AuthenticationError(Exception):
+        ...     pass
+        ...
+        >>> pyamf.add_error_class(AuthenticationError, 'Auth.Failed')
+        >>> print pyamf.ERROR_CLASS_MAP
+        {'TypeError': <type 'exceptions.TypeError'>, 'IndexError': <type 'exceptions.IndexError'>,
+        'Auth.Failed': <class '__main__.AuthenticationError'>, 'KeyError': <type 'exceptions.KeyError'>,
+        'NameError': <type 'exceptions.NameError'>, 'LookupError': <type 'exceptions.LookupError'>}
 
-    @param klass:
-    @type klass:
-    @param code:
+    @param klass: Exception class
+    @param code: Exception code
     @type code: C{str}
+    @see: L{remove_error_class}
+    """
+    if not isinstance(code, python.str_types):
+        code = code.decode('utf-8')
 
-    @raise TypeError: C{klass} must be a C{class} type.
-    @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class.
-    @raise ValueError: Code is already registered.
-    """
-    if not isinstance(code, basestring):
-        code = str(code)
+    if not isinstance(klass, python.class_types):
+        raise TypeError("klass must be a class type")
 
-    if not isinstance(klass, (type, types.ClassType)):
-        raise TypeError, "klass must be a class type"
+    mro = inspect.getmro(klass)
 
-    mro = util.get_mro(klass)
+    if not Exception in mro:
+        raise TypeError(
+            'Error classes must subclass the __builtin__.Exception class')
 
-    if not Exception in util.get_mro(klass):
-        raise TypeError, 'error classes must subclass the __builtin__.Exception class'
-
-    if code in ERROR_CLASS_MAP.keys():
-        raise ValueError, 'Code %s is already registered' % code
+    if code in ERROR_CLASS_MAP:
+        raise ValueError('Code %s is already registered' % (code,))
 
     ERROR_CLASS_MAP[code] = klass
 
+
 def remove_error_class(klass):
     """
-    Removes a class from C{ERROR_CLASS_MAP}.
+    Removes a class from the L{ERROR_CLASS_MAP}.
 
-    @param klass:
-    @type klass:
+    An example::
 
-    @raise ValueError: Code is not registered.
-    @raise ValueError: Class is not registered.
-    @raise TypeError: Invalid type, expected C{class} or C{string}.
+       >>> class AuthenticationError(Exception):
+       ...     pass
+       ...
+       >>> pyamf.add_error_class(AuthenticationError, 'Auth.Failed')
+       >>> pyamf.remove_error_class(AuthenticationError)
+
+    @see: L{add_error_class}
     """
-    if isinstance(klass, basestring):
-        if not klass in ERROR_CLASS_MAP.keys():
-            raise ValueError, 'Code %s is not registered' % klass
-    elif isinstance(klass, (type, types.ClassType)):
+    if isinstance(klass, python.str_types):
+        if klass not in ERROR_CLASS_MAP:
+            raise ValueError('Code %s is not registered' % (klass,))
+    elif isinstance(klass, python.class_types):
         classes = ERROR_CLASS_MAP.values()
-        if not klass in classes:
-            raise ValueError, 'Class %s is not registered' % klass
+        if klass not in classes:
+            raise ValueError('Class %s is not registered' % (klass,))
 
         klass = ERROR_CLASS_MAP.keys()[classes.index(klass)]
     else:
-        raise TypeError, "Invalid type, expected class or string"
+        raise TypeError("Invalid type, expected class or string")
 
     del ERROR_CLASS_MAP[klass]
 
+
+def register_alias_type(klass, *args):
+    """
+    This function allows you to map subclasses of L{ClassAlias} to classes
+    listed in C{args}.
+
+    When an object is read/written from/to the AMF stream, a paired L{ClassAlias}
+    instance is created (or reused), based on the Python class of that object.
+    L{ClassAlias} provides important metadata for the class and can also control
+    how the equivalent Python object is created, how the attributes are applied
+    etc.
+
+    Use this function if you need to do something non-standard.
+
+    @since: 0.4
+    @see:
+     - L{pyamf.adapters._google_appengine_ext_db.DataStoreClassAlias} for a
+       good example.
+     - L{unregister_alias_type}
+    @raise RuntimeError: alias is already registered
+    @raise TypeError: Value supplied to C{klass} is not a class
+    @raise ValueError:
+     - New aliases must subclass L{pyamf.ClassAlias}
+     - At least one type must be supplied
+    """
+    def check_type_registered(arg):
+        for k, v in ALIAS_TYPES.iteritems():
+            for kl in v:
+                if arg is kl:
+                    raise RuntimeError('%r is already registered under %r' % (
+                        arg, k))
+
+    if not isinstance(klass, python.class_types):
+        raise TypeError('klass must be class')
+
+    if not issubclass(klass, ClassAlias):
+        raise ValueError('New aliases must subclass pyamf.ClassAlias')
+
+    if len(args) == 0:
+        raise ValueError('At least one type must be supplied')
+
+    if len(args) == 1 and hasattr(args[0], '__call__'):
+        c = args[0]
+
+        check_type_registered(c)
+    else:
+        for arg in args:
+            if not isinstance(arg, python.class_types):
+                raise TypeError('%r must be class' % (arg,))
+
+            check_type_registered(arg)
+
+    ALIAS_TYPES[klass] = args
+
+    for k, v in CLASS_CACHE.copy().iteritems():
+        new_alias = util.get_class_alias(v.klass)
+
+        if new_alias is klass:
+            meta = util.get_class_meta(v.klass)
+            meta['alias'] = v.alias
+
+            alias_klass = klass(v.klass, **meta)
+
+            CLASS_CACHE[k] = alias_klass
+            CLASS_CACHE[v.klass] = alias_klass
+
+
+def unregister_alias_type(klass):
+    """
+    Removes the klass from the L{ALIAS_TYPES} register.
+
+    @see: L{register_alias_type}
+    """
+    return ALIAS_TYPES.pop(klass, None)
+
+
+def register_package(module=None, package=None, separator='.', ignore=[],
+                     strict=True):
+    """
+    This is a helper function that takes the concept of Actionscript packages
+    and registers all the classes in the supplied Python module under that
+    package. It auto-aliased all classes in C{module} based on the parent
+    C{package}.
+
+    @param module: The Python module that will contain all the classes to
+        auto alias.
+    @type module: C{module} or C{dict}
+    @param package: The base package name. e.g. 'com.example.app'. If this
+        is C{None} then the value is inferred from C{module.__name__}.
+    @type package: C{string} or C{None}
+    @param separator: The separator used to append to C{package} to form the
+        complete alias.
+    @param ignore: To give fine grain control over what gets aliased and what
+        doesn't, supply a list of classes that you B{do not} want to be aliased.
+    @type ignore: C{iterable}
+    @param strict: Whether only classes that originate from C{module} will be
+        registered.
+
+    @return: A dict of all the classes that were registered and their respective
+        L{ClassAlias} counterparts.
+    @since: 0.5
+    @raise TypeError: Cannot get a list of classes from C{module}
+    """
+    if isinstance(module, python.str_types):
+        if module == '':
+            raise TypeError('Cannot get list of classes from %r' % (module,))
+
+        package = module
+        module = None
+
+    if module is None:
+        import inspect
+
+        prev_frame = inspect.stack()[1][0]
+        module = prev_frame.f_locals
+
+    if type(module) is dict:
+        has = lambda x: x in module
+        get = module.__getitem__
+    elif type(module) is list:
+        has = lambda x: x in module
+        get = module.__getitem__
+        strict = False
+    else:
+        has = lambda x: hasattr(module, x)
+        get = lambda x: getattr(module, x)
+
+    if package is None:
+        if has('__name__'):
+            package = get('__name__')
+        else:
+            raise TypeError('Cannot get list of classes from %r' % (module,))
+
+    if has('__all__'):
+        keys = get('__all__')
+    elif hasattr(module, '__dict__'):
+        keys = module.__dict__.keys()
+    elif hasattr(module, 'keys'):
+        keys = module.keys()
+    elif isinstance(module, list):
+        keys = range(len(module))
+    else:
+        raise TypeError('Cannot get list of classes from %r' % (module,))
+
+    def check_attr(attr):
+        if not isinstance(attr, python.class_types):
+            return False
+
+        if attr.__name__ in ignore:
+            return False
+
+        try:
+            if strict and attr.__module__ != get('__name__'):
+                return False
+        except AttributeError:
+            return False
+
+        return True
+
+    # gotta love python
+    classes = filter(check_attr, [get(x) for x in keys])
+
+    registered = {}
+
+    for klass in classes:
+        alias = '%s%s%s' % (package, separator, klass.__name__)
+
+        registered[klass] = register_class(klass, alias)
+
+    return registered
+
+
+def set_default_etree(etree):
+    """
+    Sets the default interface that will called apon to both de/serialise XML
+    entities. This means providing both C{tostring} and C{fromstring} functions.
+
+    For testing purposes, will return the previous value for this (if any).
+    """
+    from pyamf import xml
+
+    return xml.set_default_interface(etree)
+
+
+#: setup some some standard class registrations and class loaders.
+register_class(ASObject)
+register_class_loader(flex_loader)
+register_class_loader(blaze_loader)
+register_alias_type(TypedObjectClassAlias, TypedObject)
+register_alias_type(ErrorAlias, Exception)
+
 register_adapters()

pyamf/adapters/__init__.py

-# Copyright (c) 2007-2008 The PyAMF Project.
-# See LICENSE for details.
+# Copyright (c) The PyAMF Project.
+# See LICENSE.txt for details.
 
 """
 The adapter package provides additional functionality for other Python
 packages. This includes registering classes, setting up type maps etc.
 
-@author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
-
 @since: 0.1.0
 """
 
-import os.path, glob
+import os.path
+import glob
 
 from pyamf.util import imports
 
+
+adapters_registered = False
+
+
 class PackageImporter(object):
     """
     Package importer used for lazy module loading.
     def __init__(self, name):
         self.name = name
 
-    def __call__(self, name):
+    def __call__(self, mod):
         __import__('%s.%s' % ('pyamf.adapters', self.name))
 
-adapters_registered = False
 
 def register_adapters():
     global adapters_registered
             continue
 
         try:
-            module = imports.whenImported(mod[1:].replace('_', '.'), PackageImporter(mod))
+            register_adapter(mod[1:].replace('_', '.'), PackageImporter(mod))
         except ImportError:
             pass
 
     adapters_registered = True
+
+
+def register_adapter(mod, func):
+    """
+    Registers a callable to be executed when a module is imported. If the
+    module already exists then the callable will be executed immediately.
+    You can register the same module multiple times, the callables will be
+    executed in the order they were registered. The root module must exist
+    (i.e. be importable) otherwise an `ImportError` will be thrown.
+
+    @param mod: The fully qualified module string, as used in the imports
+        statement. E.g. 'foo.bar.baz'. The string must map to a module
+        otherwise the callable will not fire.
+    @param func: The function to call when C{mod} is imported. This function
+        must take one arg, the newly imported C{module} object.
+    @type func: callable
+    @raise TypeError: C{func} must be callable
+    """
+    if not hasattr(func, '__call__'):
+        raise TypeError('func must be callable')
+
+    imports.when_imported(mod, func)
+
+
+def get_adapter(mod):
+    """
+    """
+    base_name = '_' + mod.replace('.', '_')
+
+    full_import = '%s.%s' % (__name__, base_name)
+
+    ret = __import__(full_import)
+
+    for attr in full_import.split('.')[1:]:
+        ret = getattr(ret, attr)
+
+    return ret

pyamf/adapters/_google_appengine_ext_db.py

-# Copyright (c) 2007-2008 The PyAMF Project.
-# See LICENSE for details.
+# Copyright (c) The PyAMF Project.
+# See LICENSE.txt for details.
 
 """
 Google App Engine adapter module.
 
-Sets up basic type mapping and class mappings for using the Google App Engine
-db api.
+Sets up basic type mapping and class mappings for using the Datastore API
+in Google App Engine.
 
-@see: U{Google App Engine<http://code.google.com/appengine/>}
-@author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
-@since: 0.3
+@see: U{Datastore API on Google App Engine<http://
+    code.google.com/appengine/docs/python/datastore>}
+@since: 0.3.1
 """
 
 from google.appengine.ext import db
+from google.appengine.ext.db import polymodel
+import datetime
 
 import pyamf
+from pyamf.adapters import util
 
-def get_attrs_for_model(obj):
+
+class ModelStub(object):
     """
-    Returns a list of properties on an C{db.Model} instance
+    This class represents a C{db.Model} or C{db.Expando} class as the typed
+    object is being read from the AMF stream. Once the attributes have been
+    read from the stream and through the magic of Python, the instance of this
+    class will be converted into the correct type.
+
+    @ivar klass: The referenced class either C{db.Model} or C{db.Expando}.
+        This is used so we can proxy some of the method calls during decoding.
+    @type klass: C{db.Model} or C{db.Expando}
+    @see: L{DataStoreClassAlias.applyAttributes}
     """
-    return list(obj.__class__._properties)
 
-def get_attrs_for_expando(obj):
+    def __init__(self, klass):
+        self.klass = klass
+
+    def properties(self):
+        return self.klass.properties()
+
+    def dynamic_properties(self):
+        return []
+
+
+class GAEReferenceCollection(dict):
     """
-    Returns a list of dynamic properties on a L{db.Expando} instance
+    This helper class holds a dict of klass to key/objects loaded from the
+    Datastore.
+
+    @since: 0.4.1
     """
-    return obj.dynamic_properties()
 
-pyamf.register_class(db.Model, attr_func=get_attrs_for_model, metadata=['dynamic'])
-pyamf.register_class(db.Expando, attr_func=get_attrs_for_expando, metadata=['dynamic'])
+    def _getClass(self, klass):
+        if not issubclass(klass, (db.Model, db.Expando)):
+            raise TypeError('expected db.Model/db.Expando class, got %s' % (klass,))
+
+        return self.setdefault(klass, {})
+
+    def getClassKey(self, klass, key):
+        """
+        Return an instance based on klass/key.
+
+        If an instance cannot be found then C{KeyError} is raised.
+
+        @param klass: The class of the instance.
+        @param key: The key of the instance.
+        @return: The instance linked to the C{klass}/C{key}.
+        @rtype: Instance of L{klass}.
+        """
+        d = self._getClass(klass)
+
+        return d[key]
+
+    def addClassKey(self, klass, key, obj):
+        """
+        Adds an object to the collection, based on klass and key.
+
+        @param klass: The class of the object.
+        @param key: The datastore key of the object.
+        @param obj: The loaded instance from the datastore.
+        """
+        d = self._getClass(klass)
+
+        d[key] = obj
+
+
+class DataStoreClassAlias(pyamf.ClassAlias):
+    """
+    This class contains all the business logic to interact with Google's
+    Datastore API's. Any C{db.Model} or C{db.Expando} classes will use this
+    class alias for encoding/decoding.
+
+    We also add a number of indexes to the encoder context to aggressively
+    decrease the number of Datastore API's that we need to complete.
+    """
+
+    # The name of the attribute used to represent the key
+    KEY_ATTR = '_key'
+
+    def _compile_base_class(self, klass):
+        if klass in (db.Model, polymodel.PolyModel):
+            return
+
+        pyamf.ClassAlias._compile_base_class(self, klass)
+
+    def getCustomProperties(self):
+        props = [self.KEY_ATTR]
+        self.reference_properties = {}
+        self.properties = {}
+        reverse_props = []
+
+        for name, prop in self.klass.properties().iteritems():
+            self.properties[name] = prop
+
+            props.append(name)
+
+            if isinstance(prop, db.ReferenceProperty):
+                self.reference_properties[name] = prop
+
+        if issubclass(self.klass, polymodel.PolyModel):
+            del self.properties['_class']
+            props.remove('_class')
+
+        # check if the property is a defined as a collection_name. These types
+        # of properties are read-only and the datastore freaks out if you
+        # attempt to meddle with it. We delete the attribute entirely ..
+        for name, value in self.klass.__dict__.iteritems():
+            if isinstance(value, db._ReverseReferenceProperty):
+                reverse_props.append(name)
+
+        self.encodable_properties.update(self.properties.keys())
+        self.decodable_properties.update(self.properties.keys())
+        self.readonly_attrs.update(reverse_props)
+
+        if not self.reference_properties:
+            self.reference_properties = None
+
+        if not self.properties:
+            self.properties = None
+
+        self.no_key_attr = self.KEY_ATTR in self.exclude_attrs
+
+    def getEncodableAttributes(self, obj, codec=None):
+        attrs = pyamf.ClassAlias.getEncodableAttributes(self, obj, codec=codec)
+
+        gae_objects = getGAEObjects(codec.context) if codec else None
+
+        if self.reference_properties and gae_objects:
+            for name, prop in self.reference_properties.iteritems():
+                klass = prop.reference_class
+                key = prop.get_value_for_datastore(obj)
+
+                if not key:
+                    continue
+
+                try:
+                    attrs[name] = gae_objects.getClassKey(klass, key)
+                except KeyError:
+                    ref_obj = getattr(obj, name)
+                    gae_objects.addClassKey(klass, key, ref_obj)
+                    attrs[name] = ref_obj
+
+        for k in attrs.keys()[:]:
+            if k.startswith('_'):
+                del attrs[k]
+
+        for attr in obj.dynamic_properties():
+            attrs[attr] = getattr(obj, attr)
+
+        if not self.no_key_attr:
+            attrs[self.KEY_ATTR] = str(obj.key()) if obj.is_saved() else None
+
+        return attrs
+
+    def createInstance(self, codec=None):
+        return ModelStub(self.klass)
+
+    def getDecodableAttributes(self, obj, attrs, codec=None):
+        key = attrs.setdefault(self.KEY_ATTR, None)
+        attrs = pyamf.ClassAlias.getDecodableAttributes(self, obj, attrs, codec=codec)
+
+        del attrs[self.KEY_ATTR]
+        new_obj = None
+
+        # attempt to load the object from the datastore if KEY_ATTR exists.
+        if key and codec:
+            new_obj = loadInstanceFromDatastore(self.klass, key, codec)
+
+        # clean up the stub
+        if isinstance(obj, ModelStub) and hasattr(obj, 'klass'):
+            del obj.klass
+
+        if new_obj:
+            obj.__dict__ = new_obj.__dict__.copy()
+
+        obj.__class__ = self.klass
+        apply_init = True
+
+        if self.properties:
+            for k in [k for k in attrs.keys() if k in self.properties.keys()]:
+                prop = self.properties[k]
+                v = attrs[k]
+
+                if isinstance(prop, db.FloatProperty) and isinstance(v, (int, long)):
+                    attrs[k] = float(v)
+                elif isinstance(prop, db.IntegerProperty) and isinstance(v, float):
+                    x = long(v)
+
+                    # only convert the type if there is no mantissa - otherwise
+                    # let the chips fall where they may
+                    if x == v:
+                        attrs[k] = x
+                elif isinstance(prop, db.ListProperty) and v is None:
+                    attrs[k] = []
+                elif isinstance(v, datetime.datetime):
+                    # Date/Time Property fields expect specific types of data
+                    # whereas PyAMF only decodes into datetime.datetime objects.
+                    if isinstance(prop, db.DateProperty):
+                        attrs[k] = v.date()
+                    elif isinstance(prop, db.TimeProperty):
+                        attrs[k] = v.time()
+
+                if new_obj is None and isinstance(v, ModelStub) and prop.required and k in self.reference_properties:
+                    apply_init = False
+                    del attrs[k]
+
+        # If the object does not exist in the datastore, we must fire the
+        # class constructor. This sets internal attributes that pyamf has
+        # no business messing with ..
+        if new_obj is None and apply_init is True:
+            obj.__init__(**attrs)
+
+        return attrs
+
+
+def getGAEObjects(context):
+    """
+    Returns a reference to the C{gae_objects} on the context. If it doesn't
+    exist then it is created.
+
+    @param context: The context to load the C{gae_objects} index from.
+    @return: The C{gae_objects} index reference.
+    @rtype: Instance of L{GAEReferenceCollection}
+    @since: 0.4.1
+    """
+    return context.extra.setdefault('gae_objects', GAEReferenceCollection())
+
+
+def loadInstanceFromDatastore(klass, key, codec=None):
+    """
+    Attempt to load an instance from the datastore, based on C{klass}
+    and C{key}. We create an index on the codec's context (if it exists)
+    so we can check that first before accessing the datastore.
+
+    @param klass: The class that will be loaded from the datastore.
+    @type klass: Sub-class of C{db.Model} or C{db.Expando}
+    @param key: The key which is used to uniquely identify the instance in the
+        datastore.
+    @type key: C{str}
+    @param codec: The codec to reference the C{gae_objects} index. If
+        supplied,The codec must have have a context attribute.
+    @return: The loaded instance from the datastore.
+    @rtype: Instance of C{klass}.
+    @since: 0.4.1
+    """
+    if not issubclass(klass, (db.Model, db.Expando)):
+        raise TypeError('expected db.Model/db.Expando class, got %s' % (klass,))
+
+    if not isinstance(key, basestring):
+        raise TypeError('string expected for key, got %s', (repr(key),))
+
+    key = str(key)
+
+    if codec is None:
+        return klass.get(key)
+
+    gae_objects = getGAEObjects(codec.context)
+
+    try:
+        return gae_objects.getClassKey(klass, key)
+    except KeyError:
+        pass
+
+    obj = klass.get(key)
+    gae_objects.addClassKey(klass, key, obj)
+
+    return obj
+
+
+def writeGAEObject(obj, encoder=None):
+    """
+    The GAE Datastore creates new instances of objects for each get request.
+    This is a problem for PyAMF as it uses the id(obj) of the object to do
+    reference checking.
+
+    We could just ignore the problem, but the objects are conceptually the
+    same so the effort should be made to attempt to resolve references for a
+    given object graph.
+
+    We create a new map on the encoder context object which contains a dict of
+    C{object.__class__: {key1: object1, key2: object2, .., keyn: objectn}}. We
+    use the datastore key to do the reference checking.
+
+    @since: 0.4.1
+    """
+    if not obj.is_saved():
+        encoder.writeObject(obj)
+
+        return
+
+    context = encoder.context
+    kls = obj.__class__
+    s = obj.key()
+
+    gae_objects = getGAEObjects(context)
+
+    try:
+        referenced_object = gae_objects.getClassKey(kls, s)
+    except KeyError:
+        referenced_object = obj
+        gae_objects.addClassKey(kls, s, obj)
+
+    encoder.writeObject(referenced_object)
+
+
+# initialise the module here: hook into pyamf
+
+pyamf.register_alias_type(DataStoreClassAlias, db.Model)
+pyamf.add_type(db.Query, util.to_list)
+pyamf.add_type(db.Model, writeGAEObject)
-# -*- coding: utf-8 -*-
-#
-# Copyright (c) 2007-2008 The PyAMF Project.
-# See LICENSE for details.
+# Copyright (c) The PyAMF Project.
+# See LICENSE.txt for details.
 
 """
 AMF0 implementation.
 
 C{AMF0} supports the basic data types used for the NetConnection, NetStream,
-LocalConnection, SharedObjects and other classes in the Flash Player.
+LocalConnection, SharedObjects and other classes in the Adobe Flash Player.
 
-@see: U{Official AMF0 Specification (external)
-<http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf>}
+@since: 0.1
+@see: U{Official AMF0 Specification in English (external)
+    <http://opensource.adobe.com/wiki/download/attachments/1114283/amf0_spec_121207.pdf>}
+@see: U{Official AMF0 Specification in Japanese (external)
+    <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf0_spec_121207.pdf>}
 @see: U{AMF documentation on OSFlash (external)
-<http://osflash.org/documentation/amf>}
-
-@author: U{Arnar Birgisson<mailto:arnarbi@gmail.com>}
-@author: U{Thijs Triemstra<mailto:info@collab.nl>}
-@author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
-
-@since: 0.1.0
+    <http://osflash.org/documentation/amf>}
 """
 
-import datetime, types
+import datetime
 
 import pyamf