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