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