1
2
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
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{dev@pyamf.org<mailto:dev@pyamf.org>}
12 @see: U{http://pyamf.org}
13
14 @since: October 2007
15 @version: 0.4
16 @status: Production/Stable
17 """
18
19 import types
20
21 from pyamf import util
22 from pyamf.adapters import register_adapters
23
24 __all__ = [
25 'register_class',
26 'register_class_loader',
27 'encode',
28 'decode',
29 '__version__'
30 ]
31
32
33 __version__ = (0, 4)
34
35
36 CLASS_CACHE = {}
37
38 CLASS_LOADERS = []
39
40 TYPE_MAP = {}
41
42 ERROR_CLASS_MAP = {}
43
44 ALIAS_TYPES = {}
45
46
47 AMF0 = 0
48
49 AMF3 = 3
50
51 ENCODING_TYPES = (AMF0, AMF3)
52
54 """
55 Typecodes used to identify AMF clients and servers.
56
57 @see: U{Flash Player on WikiPedia (external)
58 <http://en.wikipedia.org/wiki/Flash_Player>}
59 @see: U{Flash Media Server on WikiPedia (external)
60 <http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server>}
61 """
62
63 Flash6 = 0
64
65 FlashCom = 1
66
67 Flash9 = 3
68
69
70 CLIENT_TYPES = []
71
72 for x in ClientTypes.__dict__:
73 if not x.startswith('_'):
74 CLIENT_TYPES.append(ClientTypes.__dict__[x])
75 del x
76
79 return 'pyamf.Undefined'
80
81
82 Undefined = UndefinedType()
83
85 """
86 Base AMF Error.
87
88 All AMF related errors should be subclassed from this class.
89 """
90
92 """
93 Raised if there is an error in decoding an AMF data stream.
94 """
95
97 """
98 Raised if the data stream has come to a natural end.
99 """
100
102 """
103 Raised if an AMF data stream refers to a non-existent object
104 or string reference.
105 """
106
108 """
109 Raised if the element could not be encoded to the stream.
110
111 @bug: See U{Docuverse blog (external)
112 <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>}
113 for more info about the empty key string array bug.
114 """
115
117 """
118 Raised if the AMF stream specifies a class that does not
119 have an alias.
120
121 @see: L{register_class}
122 """
123
124 -class BaseContext(object):
125 """
126 I hold the AMF context for en/decoding streams.
127 """
128
129 - def __init__(self):
130 self.objects = util.IndexedCollection()
131 self.clear()
132
134 """
135 Completely clears the context.
136 """
137 self.objects.clear()
138 self.class_aliases = {}
139
141 """
142 Resets the context. This is subtly different to the
143 L{BaseContext.clear} method, which is a hard delete of the context.
144 This method is mainly used by the L{remoting api<pyamf.remoting>} to
145 handle context clearing between requests.
146 """
147 self.objects.clear()
148 self.class_aliases = {}
149
150 - def getObject(self, ref):
151 """
152 Gets an object based on a reference.
153
154 @raise TypeError: Bad reference type.
155 """
156 try:
157 return self.objects.getByReference(ref)
158 except ReferenceError:
159 raise ReferenceError("Unknown object reference %r" % (ref,))
160
161 - def getObjectReference(self, obj):
162 """
163 Gets a reference for an object.
164 """
165 try:
166 return self.objects.getReferenceTo(obj)
167 except ReferenceError:
168 raise ReferenceError("Object %r not a valid reference" % (obj,))
169
170 - def addObject(self, obj):
171 """
172 Adds a reference to C{obj}.
173
174 @type obj: C{mixed}
175 @param obj: The object to add to the context.
176 @rtype: C{int}
177 @return: Reference to C{obj}.
178 """
179 return self.objects.append(obj)
180
181 - def getClassAlias(self, klass):
182 """
183 Gets a class alias based on the supplied C{klass}.
184 """
185 if klass not in self.class_aliases.keys():
186 try:
187 self.class_aliases[klass] = get_class_alias(klass)
188 except UnknownClassAlias:
189
190 alias = util.get_class_alias(klass)
191
192 if alias is not None:
193 self.class_aliases[klass] = alias(klass, None)
194 else:
195 self.class_aliases[klass] = None
196
197 return self.class_aliases[klass]
198
199 - def __copy__(self):
200 raise NotImplementedError
201
203 """
204 This class represents a Flash Actionscript Object (typed or untyped).
205
206 I supply a C{__builtin__.dict} interface to support C{get}/C{setattr}
207 calls.
208
209 @raise AttributeError: Unknown attribute.
210 """
211
213 dict.__init__(self, *args, **kwargs)
214
216 try:
217 return self[k]
218 except KeyError:
219 raise AttributeError('Unknown attribute \'%s\'' % (k,))
220
223
226
229
231 """
232 Used to be able to specify the C{mixedarray} type.
233 """
234
301
302
303
305 """
306 Class alias.
307
308 All classes are initially set to a dynamic state.
309
310 @ivar attrs: A list of attributes to encode for this class.
311 @type attrs: C{list}
312 @ivar metadata: A list of metadata tags similar to ActionScript tags.
313 @type metadata: C{list}
314 """
315
316 - def __init__(self, klass, alias, attrs=None, attr_func=None, metadata=[]):
317 """
318 @type klass: C{class}
319 @param klass: The class to alias.
320 @type alias: C{str}
321 @param alias: The alias to the class e.g. C{org.example.Person}. If the
322 value of this is C{None}, then it is worked out based on the C{klass}.
323 The anonymous tag is also added to the class.
324 @type attrs: A list of attributes to encode for this class.
325 @param attrs: C{list}
326 @type metadata: A list of metadata tags similar to ActionScript tags.
327 @param metadata: C{list}
328
329 @raise TypeError: The C{klass} must be a class type.
330 @raise TypeError: The C{attr_func} must be callable.
331 @raise TypeError: C{__readamf__} must be callable.
332 @raise TypeError: C{__writeamf__} must be callable.
333 @raise AttributeError: An externalised class was specified, but no
334 C{__readamf__} attribute was found.
335 @raise AttributeError: An externalised class was specified, but no
336 C{__writeamf__} attribute was found.
337 @raise ValueError: The C{attrs} keyword must be specified for static
338 classes.
339 """
340 if not isinstance(klass, (type, types.ClassType)):
341 raise TypeError("klass must be a class type")
342
343 self.checkClass(klass)
344
345 self.metadata = ClassMetaData(metadata)
346
347 if alias is None:
348 self.metadata.append('anonymous')
349 alias = "%s.%s" % (klass.__module__, klass.__name__,)
350
351 self.klass = klass
352 self.alias = alias
353 self.attr_func = attr_func
354 self.attrs = attrs
355
356 if 'external' in self.metadata:
357
358 if not hasattr(klass, '__readamf__'):
359 raise AttributeError("An externalised class was specified, but"
360 " no __readamf__ attribute was found for class %s" % (
361 klass.__name__))
362
363 if not hasattr(klass, '__writeamf__'):
364 raise AttributeError("An externalised class was specified, but"
365 " no __writeamf__ attribute was found for class %s" % (
366 klass.__name__))
367
368 if not isinstance(klass.__readamf__, types.UnboundMethodType):
369 raise TypeError("%s.__readamf__ must be callable" % (
370 klass.__name__))
371
372 if not isinstance(klass.__writeamf__, types.UnboundMethodType):
373 raise TypeError("%s.__writeamf__ must be callable" % (
374 klass.__name__))
375
376 if 'dynamic' in self.metadata:
377 if attr_func is not None and not callable(attr_func):
378 raise TypeError("attr_func must be callable")
379
380 if 'static' in self.metadata:
381 if attrs is None:
382 raise ValueError("attrs keyword must be specified for static classes")
383
386
388 return '<ClassAlias alias=%s klass=%s @ %s>' % (
389 self.alias, self.klass, id(self))
390
392 if isinstance(other, basestring):
393 return self.alias == other
394 elif isinstance(other, self.__class__):
395 return self.klass == other.klass
396 elif isinstance(other, (type, types.ClassType)):
397 return self.klass == other
398 else:
399 return False
400
403
405 """
406 This function is used to check the class being aliased to fits certain
407 criteria. The default is to check that the __init__ constructor does
408 not pass in arguments.
409
410 @since: 0.4
411 """
412
413
414 if not (hasattr(klass, '__init__') and hasattr(klass.__init__, 'im_func')):
415 return
416
417 klass_func = klass.__init__.im_func
418
419
420 if hasattr(klass_func, 'func_code') and (
421 klass_func.func_code.co_argcount - len(klass_func.func_defaults or []) > 1):
422 args = list(klass_func.func_code.co_varnames)
423 values = list(klass_func.func_defaults or [])
424
425 if not values:
426 sign = "%s.__init__(%s)" % (klass.__name__, ", ".join(args))
427 else:
428 named_args = zip(args[len(args) - len(values):], values)
429 sign = "%s.__init__(%s, %s)" % (
430 klass.__name__,
431 ", ".join(args[:0-len(values)]),
432 ", ".join(map(lambda x: "%s=%s" % (x,), named_args)))
433
434 raise TypeError("__init__ doesn't support additional arguments: %s"
435 % sign)
436
437 checkClass = classmethod(checkClass)
438
439 - def _getAttrs(self, obj, static_attrs=None, dynamic_attrs=None, traverse=True):
440 if static_attrs is None:
441 static_attrs = []
442
443 if dynamic_attrs is None:
444 dynamic_attrs = []
445
446 modified_attrs = False
447
448 if self.attrs is not None:
449 modified_attrs = True
450 static_attrs.extend(self.attrs)
451 elif traverse is True and hasattr(obj, '__slots__'):
452 modified_attrs = True
453 static_attrs.extend(obj.__slots__)
454
455 if self.attr_func is not None:
456 modified_attrs = True
457 extra_attrs = self.attr_func(obj)
458
459 dynamic_attrs.extend([key for key in extra_attrs if key not in static_attrs])
460
461 if traverse is True:
462 for base in util.get_mro(obj.__class__):
463 try:
464 alias = get_class_alias(base)
465 except UnknownClassAlias:
466 continue
467
468 x, y = alias._getAttrs(obj, static_attrs, dynamic_attrs, False)
469
470 if x is not None:
471 static_attrs.extend(x)
472 modified_attrs = True
473
474 if y is not None:
475 dynamic_attrs.extend(y)
476 modified_attrs = True
477
478 if modified_attrs is False:
479 return None, None
480
481 sa = []
482 da = []
483
484 for x in static_attrs:
485 if x not in sa:
486 sa.append(x)
487
488 for x in dynamic_attrs:
489 if x not in da:
490 da.append(x)
491
492 return (sa, da)
493
495 """
496 Returns a tuple of lists, static and dynamic attrs to encode.
497 """
498 return self._getAttrs(obj)
499
501 """
502 Returns a collection of attributes for an object
503 Returns a C{tuple} containing a dict of static and dynamic attributes
504 for C{obj}
505 """
506 dynamic_attrs = {}
507 static_attrs = {}
508 static_attr_names, dynamic_attr_names = self.getAttrs(obj)
509
510 if static_attr_names is None and dynamic_attr_names is None:
511 dynamic_attrs = util.get_attrs(obj)
512
513 if static_attr_names is not None:
514 for attr in static_attr_names:
515 if hasattr(obj, attr):
516 static_attrs[attr] = getattr(obj, attr)
517 else:
518 static_attrs[attr] = Undefined
519
520 if dynamic_attr_names is not None:
521 for attr in dynamic_attr_names:
522 if attr in static_attrs:
523 continue
524
525 if hasattr(obj, attr):
526 dynamic_attrs[attr] = getattr(obj, attr)
527
528 return (static_attrs, dynamic_attrs)
529
531 """
532 Applies the collection of attributes C{attrs} to aliased object C{obj}.
533 It is mainly used when reading aliased objects from an AMF byte stream.
534 """
535 if 'static' in self.metadata:
536 s, d = self.getAttrs(obj)
537
538 if s is not None:
539 for k in attrs.keys():
540 if k not in s:
541 del attrs[k]
542
543 util.set_attrs(obj, attrs)
544
546 """
547 Creates an instance of the klass.
548
549 @return: Instance of C{self.klass}.
550 """
551 return self.klass(*args, **kwargs)
552
554 """
555 This class is used when a strongly typed object is decoded but there is no
556 registered class to apply it to.
557
558 This object can only be used for 'simple' streams - i.e. not externalized
559 data. If encountered, a L{DecodeError} will be raised.
560
561 @ivar alias: The alias of the typed object.
562 @type alias: C{unicode}
563
564 @since: 0.4
565 """
566
568 dict.__init__(self)
569
570 self.alias = alias
571
573 raise DecodeError('Unable to decode an externalised stream.\n\nThe '
574 'class alias \'%s\' was found and because strict mode is False an'
575 ' attempt was made to decode the object automatically. To decode '
576 'this stream, a registered class with the alias and a '
577 'corresponding __readamf__ method will be required.' % (
578 self.alias,))
579
581 raise EncodeError('Unable to encode an externalised stream.\n\nThe '
582 'class alias \'%s\' was found and because strict mode is False an'
583 'attempt was made to encode the object automatically. To encode '
584 'this stream, a registered class with the alias and a '
585 'corresponding __readamf__ method will be required.' % (
586 self.alias,))
587
589 """
590 @since: 0.4
591 """
592
595
598
600 """
601 Base AMF decoder.
602
603 @ivar context_class: The context for the decoding.
604 @type context_class: An instance of C{BaseDecoder.context_class}
605 @ivar type_map:
606 @type type_map: C{list}
607 @ivar stream: The underlying data stream.
608 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
609 @ivar strict: Defines how strict the decoding should be. For the time
610 being this relates to typed objects in the stream that do not have a
611 registered alias. Introduced in 0.4.
612 @type strict: C{bool}
613 """
614
615 context_class = BaseContext
616 type_map = {}
617
618 - def __init__(self, data=None, context=None, strict=False):
619 """
620 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
621 @param data: Data stream.
622 @type context: L{Context<pyamf.amf0.Context>}
623 @param context: Context.
624 @raise TypeError: The C{context} parameter must be of
625 type L{Context<pyamf.amf0.Context>}.
626 """
627
628 if isinstance(data, util.BufferedByteStream):
629 self.stream = data
630 else:
631 self.stream = util.BufferedByteStream(data)
632
633 if context == None:
634 self.context = self.context_class()
635 elif isinstance(context, self.context_class):
636 self.context = context
637 else:
638 raise TypeError("context must be of type %s.%s" % (
639 self.context_class.__module__, self.context_class.__name__))
640
641 self.strict = strict
642
644 """
645 Override in a subclass.
646 """
647 raise NotImplementedError
648
650 """
651 Reads an AMF3 element from the data stream.
652
653 @raise DecodeError: The ActionScript type is unsupported.
654 @raise EOStream: No more data left to decode.
655 """
656 try:
657 type = self.readType()
658 except EOFError:
659 raise EOStream
660
661 try:
662 func = getattr(self, self.type_map[type])
663 except KeyError:
664 raise DecodeError("Unsupported ActionScript type 0x%02x" % (type,))
665
666 return func()
667
669 """
670 @raise StopIteration:
671 """
672 try:
673 while 1:
674 yield self.readElement()
675 except EOFError:
676 raise StopIteration
677
679 """
680 Custom type mappings.
681 """
683 self.encoder = encoder
684 self.func = func
685
688
690 """
691 Base AMF encoder.
692
693 @ivar type_map: A list of types -> functions. The types is a list of
694 possible instances or functions to call (that return a C{bool}) to
695 determine the correct function to call to encode the data.
696 @type type_map: C{list}
697 @ivar context_class: Holds the class that will create context objects for
698 the implementing C{Encoder}.
699 @type context_class: C{type} or C{types.ClassType}
700 @ivar stream: The underlying data stream.
701 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
702 @ivar context: The context for the encoding.
703 @type context: An instance of C{BaseEncoder.context_class}
704 @ivar strict: Whether the encoder should operate in 'strict' mode. Nothing
705 is really affected by this for the time being - its just here for
706 flexibility.
707 @type strict: C{bool}, default is False.
708 """
709 context_class = BaseContext
710 type_map = []
711
712 - def __init__(self, data=None, context=None, strict=False):
713 """
714 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
715 @param data: Data stream.
716 @type context: L{Context<pyamf.amf0.Context>}
717 @param context: Context.
718 @raise TypeError: The C{context} parameter must be of type
719 L{Context<pyamf.amf0.Context>}.
720 """
721
722 if isinstance(data, util.BufferedByteStream):
723 self.stream = data
724 else:
725 self.stream = util.BufferedByteStream(data)
726
727 if context == None:
728 self.context = self.context_class()
729 elif isinstance(context, self.context_class):
730 self.context = context
731 else:
732 raise TypeError("context must be of type %s.%s" % (
733 self.context_class.__module__, self.context_class.__name__))
734
735 self._write_elem_func_cache = {}
736 self.strict = strict
737
739 """
740 Not possible to encode functions.
741
742 @raise EncodeError: Unable to encode function/methods.
743 """
744 raise EncodeError("Unable to encode function/methods")
745
747 """
748 Gets a function used to encode the data.
749
750 @type data: C{mixed}
751 @param data: Python data.
752 @rtype: callable or C{None}.
753 @return: The function used to encode data to the stream.
754 """
755 for type_, func in TYPE_MAP.iteritems():
756 try:
757 if isinstance(data, type_):
758 return CustomTypeFunc(self, func)
759 except TypeError:
760 if callable(type_) and type_(data):
761 return CustomTypeFunc(self, func)
762
763 for tlist, method in self.type_map:
764 for t in tlist:
765 try:
766 if isinstance(data, t):
767 return getattr(self, method)
768 except TypeError:
769 if callable(t) and t(data):
770 return getattr(self, method)
771
772 return None
773
775 """
776 Gets a function used to encode the data.
777
778 @type data: C{mixed}
779 @param data: Python data.
780 @rtype: callable or C{None}.
781 @return: The function used to encode data to the stream.
782 """
783 try:
784 key = data.__class__
785 except AttributeError:
786 return self._getWriteElementFunc(data)
787
788 if key not in self._write_elem_func_cache:
789 self._write_elem_func_cache[key] = self._getWriteElementFunc(data)
790
791 return self._write_elem_func_cache[key]
792
794 """
795 Writes the data. Overridden in subclass.
796
797 @type data: C{mixed}
798 @param data: The data to be encoded to the data stream.
799 """
800 raise NotImplementedError
801
802 -def register_class(klass, alias=None, attrs=None, attr_func=None, metadata=[]):
803 """
804 Registers a class to be used in the data streaming.
805
806 @type alias: C{str}
807 @param alias: The alias of klass, i.e. C{org.example.Person}.
808 @param attrs: A list of attributes that will be encoded for the class.
809 @type attrs: C{list} or C{None}
810 @type attr_func:
811 @param attr_func:
812 @type metadata:
813 @param metadata:
814 @raise TypeError: PyAMF doesn't support required init arguments.
815 @raise TypeError: The C{klass} is not callable.
816 @raise ValueError: The C{klass} or C{alias} is already registered.
817 @return: The registered L{ClassAlias}.
818 """
819 if not callable(klass):
820 raise TypeError("klass must be callable")
821
822 if klass in CLASS_CACHE:
823 raise ValueError("klass %s already registered" % (klass,))
824
825 if alias is not None and alias in CLASS_CACHE.keys():
826 raise ValueError("alias '%s' already registered" % (alias,))
827
828 alias_klass = util.get_class_alias(klass)
829
830 if alias_klass is None:
831 alias_klass = ClassAlias
832
833 x = alias_klass(klass, alias, attr_func=attr_func,
834 attrs=attrs, metadata=metadata)
835
836 if alias is None:
837 alias = "%s.%s" % (klass.__module__, klass.__name__,)
838
839 CLASS_CACHE[alias] = x
840
841 return x
842
844 """
845 Deletes a class from the cache.
846
847 If C{alias} is a class, the matching alias is found.
848
849 @type alias: C{class} or C{str}
850 @param alias: Alias for class to delete.
851 @raise UnknownClassAlias: Unknown alias.
852 """
853 if isinstance(alias, (type, types.ClassType)):
854 for s, a in CLASS_CACHE.iteritems():
855 if a.klass == alias:
856 alias = s
857 break
858 try:
859 del CLASS_CACHE[alias]
860 except KeyError:
861 raise UnknownClassAlias("Unknown alias %s" % (alias,))
862
864 """
865 Registers a loader that is called to provide the C{Class} for a specific
866 alias.
867
868 The L{loader} is provided with one argument, the C{Class} alias. If the
869 loader succeeds in finding a suitable class then it should return that
870 class, otherwise it should return C{None}.
871
872 @type loader: C{callable}
873 @raise TypeError: The C{loader} is not callable.
874 @raise ValueError: The C{loader} is already registered.
875 """
876 if not callable(loader):
877 raise TypeError("loader must be callable")
878
879 if loader in CLASS_LOADERS:
880 raise ValueError("loader has already been registered")
881
882 CLASS_LOADERS.append(loader)
883
885 """
886 Unregisters a class loader.
887
888 @type loader: C{callable}
889 @param loader: The object to be unregistered
890
891 @raise LookupError: The C{loader} was not registered.
892 """
893 if loader not in CLASS_LOADERS:
894 raise LookupError("loader not found")
895
896 del CLASS_LOADERS[CLASS_LOADERS.index(loader)]
897
899 """
900 Load a module based on C{mod_name}.
901
902 @type mod_name: C{str}
903 @param mod_name: The module name.
904 @return: Module.
905
906 @raise ImportError: Unable to import an empty module.
907 """
908 if mod_name is '':
909 raise ImportError("Unable to import empty module")
910
911 mod = __import__(mod_name)
912 components = mod_name.split('.')
913
914 for comp in components[1:]:
915 mod = getattr(mod, comp)
916
917 return mod
918
920 """
921 Finds the class registered to the alias.
922
923 The search is done in order:
924 1. Checks if the class name has been registered via L{register_class}.
925 2. Checks all functions registered via L{register_class_loader}.
926 3. Attempts to load the class via standard module loading techniques.
927
928 @type alias: C{str}
929 @param alias: The class name.
930 @raise UnknownClassAlias: The C{alias} was not found.
931 @raise TypeError: Expecting class type or L{ClassAlias} from loader.
932 @return: Class registered to the alias.
933 """
934 alias = str(alias)
935
936
937 try:
938 return CLASS_CACHE[alias]
939 except KeyError:
940 pass
941
942
943 for loader in CLASS_LOADERS:
944 klass = loader(alias)
945
946 if klass is None:
947 continue
948
949 if isinstance(klass, (type, types.ClassType)):
950 return register_class(klass, alias)
951 elif isinstance(klass, ClassAlias):
952 CLASS_CACHE[str(alias)] = klass
953 else:
954 raise TypeError("Expecting class type or ClassAlias from loader")
955
956 return klass
957
958
959 mod_class = alias.split('.')
960
961 if mod_class:
962 module = '.'.join(mod_class[:-1])
963 klass = mod_class[-1]
964
965 try:
966 module = get_module(module)
967 except ImportError, AttributeError:
968
969 pass
970 else:
971 klass = getattr(module, klass)
972
973 if callable(klass):
974 CLASS_CACHE[alias] = klass
975
976 return klass
977
978
979 raise UnknownClassAlias("Unknown alias %s" % (alias,))
980
982 """
983 Finds the alias registered to the class.
984
985 @type klass: C{object} or class
986 @rtype: L{ClassAlias}
987 @return: The class alias linked to the C{klass}.
988 @raise UnknownClassAlias: Class not found.
989 @raise TypeError: Expecting C{string} or C{class} type.
990 """
991 if not isinstance(klass, (type, types.ClassType, basestring)):
992 if isinstance(klass, types.InstanceType):
993 klass = klass.__class__
994 elif isinstance(klass, types.ObjectType):
995 klass = type(klass)
996
997 if isinstance(klass, basestring):
998 for a, k in CLASS_CACHE.iteritems():
999 if klass == a:
1000 return k
1001 else:
1002 for a, k in CLASS_CACHE.iteritems():
1003 if klass == k.klass:
1004 return k
1005
1006 if isinstance(klass, basestring):
1007 return load_class(klass)
1008
1009 raise UnknownClassAlias("Unknown alias %s" % (klass,))
1010
1012 """
1013 @rtype: C{bool}
1014 @return: Alias is available.
1015 """
1016 try:
1017 alias = get_class_alias(obj)
1018 return True
1019 except UnknownClassAlias:
1020 return False
1021
1022 -def decode(stream, encoding=AMF0, context=None, strict=False):
1023 """
1024 A generator function to decode a datastream.
1025
1026 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
1027 @param stream: AMF data.
1028 @type encoding: C{int}
1029 @param encoding: AMF encoding type.
1030 @type context: L{AMF0 Context<pyamf.amf0.Context>} or
1031 L{AMF3 Context<pyamf.amf3.Context>}
1032 @param context: Context.
1033 @return: Each element in the stream.
1034 """
1035 decoder = _get_decoder_class(encoding)(stream, context, strict)
1036
1037 while 1:
1038 try:
1039 yield decoder.readElement()
1040 except EOStream:
1041 break
1042
1044 """
1045 A helper function to encode an element.
1046
1047 @type element: C{mixed}
1048 @keyword element: Python data.
1049 @type encoding: C{int}
1050 @keyword encoding: AMF encoding type.
1051 @type context: L{amf0.Context<pyamf.amf0.Context>} or
1052 L{amf3.Context<pyamf.amf3.Context>}
1053 @keyword context: Context.
1054
1055 @rtype: C{StringIO}
1056 @return: File-like object.
1057 """
1058 encoding = kwargs.get('encoding', AMF0)
1059 context = kwargs.get('context', None)
1060 strict = kwargs.get('strict', False)
1061
1062 stream = util.BufferedByteStream()
1063 encoder = _get_encoder_class(encoding)(stream, context, strict)
1064
1065 for el in args:
1066 encoder.writeElement(el)
1067
1068 stream.seek(0)
1069
1070 return stream
1071
1072 -def get_decoder(encoding, data=None, context=None, strict=False):
1074
1076 """
1077 Get compatible decoder.
1078
1079 @type encoding: C{int}
1080 @param encoding: AMF encoding version.
1081 @raise ValueError: AMF encoding version is unknown.
1082
1083 @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or
1084 L{amf3.Decoder<pyamf.amf3.Decoder>}
1085 @return: AMF0 or AMF3 decoder.
1086 """
1087 if encoding == AMF0:
1088 from pyamf import amf0
1089
1090 return amf0.Decoder
1091 elif encoding == AMF3:
1092 from pyamf import amf3
1093
1094 return amf3.Decoder
1095
1096 raise ValueError("Unknown encoding %s" % (encoding,))
1097
1098 -def get_encoder(encoding, data=None, context=None, strict=False):
1099 """
1100 Returns a subclassed instance of L{pyamf.BaseEncoder}, based on C{encoding}
1101 """
1102 return _get_encoder_class(encoding)(data=data, context=context,
1103 strict=strict)
1104
1106 """
1107 Get compatible encoder.
1108
1109 @type encoding: C{int}
1110 @param encoding: AMF encoding version.
1111 @raise ValueError: AMF encoding version is unknown.
1112
1113 @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or
1114 L{amf3.Encoder<pyamf.amf3.Encoder>}
1115 @return: AMF0 or AMF3 encoder.
1116 """
1117 if encoding == AMF0:
1118 from pyamf import amf0
1119
1120 return amf0.Encoder
1121 elif encoding == AMF3:
1122 from pyamf import amf3
1123
1124 return amf3.Encoder
1125
1126 raise ValueError("Unknown encoding %s" % (encoding,))
1127
1128 -def get_context(encoding):
1129 return _get_context_class(encoding)()
1130
1131 -def _get_context_class(encoding):
1132 """
1133 Gets a compatible context class.
1134
1135 @type encoding: C{int}
1136 @param encoding: AMF encoding version.
1137 @raise ValueError: AMF encoding version is unknown.
1138
1139 @rtype: L{amf0.Context<pyamf.amf0.Context>} or
1140 L{amf3.Context<pyamf.amf3.Context>}
1141 @return: AMF0 or AMF3 context class.
1142 """
1143 if encoding == AMF0:
1144 from pyamf import amf0
1145
1146 return amf0.Context
1147 elif encoding == AMF3:
1148 from pyamf import amf3
1149
1150 return amf3.Context
1151
1152 raise ValueError("Unknown encoding %s" % (encoding,))
1153
1155 """
1156 Loader for L{Flex<pyamf.flex>} framework compatibility classes.
1157
1158 @raise UnknownClassAlias: Trying to load unknown Flex compatibility class.
1159 """
1160 if not alias.startswith('flex.'):
1161 return
1162
1163 try:
1164 if alias.startswith('flex.messaging.messages'):
1165 import pyamf.flex.messaging
1166 elif alias.startswith('flex.messaging.io'):
1167 import pyamf.flex
1168 elif alias.startswith('flex.data.messages'):
1169 import pyamf.flex.data
1170
1171 return CLASS_CACHE[alias]
1172 except KeyError:
1173 raise UnknownClassAlias(alias)
1174
1176 """
1177 Adds a custom type to L{TYPE_MAP}. A custom type allows fine grain control
1178 of what to encode to an AMF data stream.
1179
1180 @raise TypeError: Unable to add as a custom type (expected a class or callable).
1181 @raise KeyError: Type already exists.
1182 """
1183 def _check_type(type_):
1184 if not (isinstance(type_, (type, types.ClassType)) or callable(type_)):
1185 raise TypeError('Unable to add \'%r\' as a custom type (expected a class or callable)' % (type_,))
1186
1187 if isinstance(type_, list):
1188 type_ = tuple(type_)
1189
1190 if type_ in TYPE_MAP:
1191 raise KeyError('Type %r already exists' % (type_,))
1192
1193 if isinstance(type_, types.TupleType):
1194 for x in type_:
1195 _check_type(x)
1196 else:
1197 _check_type(type_)
1198
1199 TYPE_MAP[type_] = func
1200
1202 """
1203 Gets the declaration for the corresponding custom type.
1204
1205 @raise KeyError: Unknown type.
1206 """
1207 if isinstance(type_, list):
1208 type_ = tuple(type_)
1209
1210 for (k, v) in TYPE_MAP.iteritems():
1211 if k == type_:
1212 return v
1213
1214 raise KeyError("Unknown type %r" % (type_,))
1215
1217 """
1218 Removes the custom type declaration.
1219
1220 @return: Custom type declaration.
1221 """
1222 declaration = get_type(type_)
1223
1224 del TYPE_MAP[type_]
1225
1226 return declaration
1227
1229 """
1230 Maps an exception class to a string code. Used to map remoting C{onStatus}
1231 objects to an exception class so that an exception can be built to
1232 represent that error::
1233
1234 class AuthenticationError(Exception):
1235 pass
1236
1237 An example: C{add_error_class(AuthenticationError, 'Auth.Failed')}
1238
1239 @type code: C{str}
1240
1241 @raise TypeError: C{klass} must be a C{class} type.
1242 @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class.
1243 @raise ValueError: Code is already registered.
1244 """
1245 if not isinstance(code, basestring):
1246 code = str(code)
1247
1248 if not isinstance(klass, (type, types.ClassType)):
1249 raise TypeError("klass must be a class type")
1250
1251 mro = util.get_mro(klass)
1252
1253 if not Exception in mro:
1254 raise TypeError('Error classes must subclass the __builtin__.Exception class')
1255
1256 if code in ERROR_CLASS_MAP.keys():
1257 raise ValueError('Code %s is already registered' % (code,))
1258
1259 ERROR_CLASS_MAP[code] = klass
1260
1262 """
1263 Removes a class from C{ERROR_CLASS_MAP}.
1264
1265 @raise ValueError: Code is not registered.
1266 @raise ValueError: Class is not registered.
1267 @raise TypeError: Invalid type, expected C{class} or C{string}.
1268 """
1269 if isinstance(klass, basestring):
1270 if not klass in ERROR_CLASS_MAP.keys():
1271 raise ValueError('Code %s is not registered' % (klass,))
1272 elif isinstance(klass, (type, types.ClassType)):
1273 classes = ERROR_CLASS_MAP.values()
1274 if not klass in classes:
1275 raise ValueError('Class %s is not registered' % (klass,))
1276
1277 klass = ERROR_CLASS_MAP.keys()[classes.index(klass)]
1278 else:
1279 raise TypeError("Invalid type, expected class or string")
1280
1281 del ERROR_CLASS_MAP[klass]
1282
1284 """
1285 This function allows you to map subclasses of L{ClassAlias} to classes
1286 listed in C{args}.
1287
1288 When an object is read/written from/to the AMF stream, a paired
1289 L{ClassAlias} instance is created (or reused), based on the Python class
1290 of that object. L{ClassAlias} provides important metadata for the class
1291 and can also control how the equivalent Python object is created, how the
1292 attributes are applied etc.
1293
1294 Use this function if you need to do something non-standard.
1295
1296 @see: L{pyamf.adapters._google_appengine_ext_db.DataStoreClassAlias} for a
1297 good example.
1298 @since: 0.4
1299 """
1300 def check_type_registered(arg):
1301
1302 for k, v in ALIAS_TYPES.iteritems():
1303 for kl in v:
1304 if arg is kl:
1305 raise RuntimeError('%r is already registered under %r' % (arg, k))
1306
1307 if not isinstance(klass, (type, types.ClassType)):
1308 raise TypeError('klass must be class')
1309
1310 if not issubclass(klass, ClassAlias):
1311 raise ValueError('New aliases must subclass pyamf.ClassAlias')
1312
1313 if len(args) == 0:
1314 raise ValueError('At least one type must be supplied')
1315
1316 if len(args) == 1 and callable(args[0]):
1317 c = args[0]
1318
1319 check_type_registered(c)
1320 else:
1321 for arg in args:
1322 if not isinstance(arg, (type, types.ClassType)):
1323 raise TypeError('%r must be class' % (arg,))
1324
1325 check_type_registered(arg)
1326
1327 ALIAS_TYPES[klass] = args
1328
1329 register_class_loader(flex_loader)
1330 register_adapters()
1331
1332 register_alias_type(TypedObjectClassAlias, TypedObject)
1333