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
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)
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 klass=%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 if hasattr(obj, attr):
590 static_attrs[attr] = getattr(obj, attr)
591 else:
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 if key not in self._write_elem_func_cache:
1022 self._write_elem_func_cache[key] = self._getWriteElementFunc(data)
1023
1024 return self._write_elem_func_cache[key]
1025
1027 """
1028 Writes the data. Overridden in subclass.
1029
1030 @type data: C{mixed}
1031 @param data: The data to be encoded to the data stream.
1032 """
1033 raise NotImplementedError
1034
1035
1037 """
1038 Registers a class to be used in the data streaming.
1039
1040 @return: The registered L{ClassAlias}.
1041 """
1042 meta = util.get_class_meta(klass)
1043
1044 if alias is not None:
1045 meta['alias'] = alias
1046
1047 alias_klass = util.get_class_alias(klass)
1048
1049 x = alias_klass(klass, defer=True, **meta)
1050
1051 if not x.anonymous:
1052 CLASS_CACHE[x.alias] = x
1053
1054 CLASS_CACHE[klass] = x
1055
1056 return x
1057
1058
1060 """
1061 Deletes a class from the cache.
1062
1063 If C{alias} is a class, the matching alias is found.
1064
1065 @type alias: C{class} or C{str}
1066 @param alias: Alias for class to delete.
1067 @raise UnknownClassAlias: Unknown alias.
1068 """
1069 try:
1070 x = CLASS_CACHE[alias]
1071 except KeyError:
1072 raise UnknownClassAlias('Unknown alias %r' % (alias,))
1073
1074 if not x.anonymous:
1075 del CLASS_CACHE[x.alias]
1076
1077 del CLASS_CACHE[x.klass]
1078
1079 return x
1080
1081
1083 """
1084 Finds the alias registered to the class.
1085
1086 @type klass: C{object} or class object.
1087 @return: The class alias linked to C{klass}.
1088 @rtype: L{ClassAlias}
1089 @raise UnknownClassAlias: Class not found.
1090 """
1091 if isinstance(klass, basestring):
1092 try:
1093 return CLASS_CACHE[klass]
1094 except KeyError:
1095 return load_class(klass)
1096
1097 if not isinstance(klass, (type, types.ClassType)):
1098 if isinstance(klass, types.InstanceType):
1099 klass = klass.__class__
1100 elif isinstance(klass, types.ObjectType):
1101 klass = type(klass)
1102
1103 try:
1104 return CLASS_CACHE[klass]
1105 except KeyError:
1106 raise UnknownClassAlias('Unknown alias for %r' % (klass,))
1107
1108
1110 """
1111 Registers a loader that is called to provide the C{Class} for a specific
1112 alias.
1113
1114 The L{loader} is provided with one argument, the C{Class} alias. If the
1115 loader succeeds in finding a suitable class then it should return that
1116 class, otherwise it should return C{None}.
1117
1118 @type loader: C{callable}
1119 @raise TypeError: The C{loader} is not callable.
1120 @raise ValueError: The C{loader} is already registered.
1121 """
1122 if not callable(loader):
1123 raise TypeError("loader must be callable")
1124
1125 if loader in CLASS_LOADERS:
1126 raise ValueError("loader has already been registered")
1127
1128 CLASS_LOADERS.append(loader)
1129
1130
1132 """
1133 Unregisters a class loader.
1134
1135 @type loader: C{callable}
1136 @param loader: The object to be unregistered
1137
1138 @raise LookupError: The C{loader} was not registered.
1139 """
1140 if loader not in CLASS_LOADERS:
1141 raise LookupError("loader not found")
1142
1143 CLASS_LOADERS.remove(loader)
1144
1145
1147 """
1148 Load a module based on C{mod_name}.
1149
1150 @type mod_name: C{str}
1151 @param mod_name: The module name.
1152 @return: Module.
1153
1154 @raise ImportError: Unable to import an empty module.
1155 """
1156 if mod_name is '':
1157 raise ImportError("Unable to import empty module")
1158
1159 mod = __import__(mod_name)
1160 components = mod_name.split('.')
1161
1162 for comp in components[1:]:
1163 mod = getattr(mod, comp)
1164
1165 return mod
1166
1167
1169 """
1170 Finds the class registered to the alias.
1171
1172 The search is done in order:
1173 1. Checks if the class name has been registered via L{register_class} or
1174 L{register_package}.
1175 2. Checks all functions registered via L{register_class_loader}.
1176 3. Attempts to load the class via standard module loading techniques.
1177
1178 @type alias: C{str}
1179 @param alias: The class name.
1180 @raise UnknownClassAlias: The C{alias} was not found.
1181 @raise TypeError: Expecting class type or L{ClassAlias} from loader.
1182 @return: Class registered to the alias.
1183 """
1184 alias = str(alias)
1185
1186
1187 try:
1188 return CLASS_CACHE[alias]
1189 except KeyError:
1190 pass
1191
1192
1193 for loader in CLASS_LOADERS:
1194 klass = loader(alias)
1195
1196 if klass is None:
1197 continue
1198
1199 if isinstance(klass, (type, types.ClassType)):
1200 return register_class(klass, alias)
1201 elif isinstance(klass, ClassAlias):
1202 CLASS_CACHE[str(alias)] = klass
1203 CLASS_CACHE[klass.klass] = klass
1204
1205 return klass
1206 else:
1207 raise TypeError("Expecting class type or ClassAlias from loader")
1208
1209
1210 mod_class = alias.split('.')
1211
1212 if mod_class:
1213 module = '.'.join(mod_class[:-1])
1214 klass = mod_class[-1]
1215
1216 try:
1217 module = get_module(module)
1218 except (ImportError, AttributeError):
1219
1220 pass
1221 else:
1222 klass = getattr(module, klass)
1223
1224 if isinstance(klass, (type, types.ClassType)):
1225 return register_class(klass, alias)
1226 elif isinstance(klass, ClassAlias):
1227 CLASS_CACHE[str(alias)] = klass
1228 CLASS_CACHE[klass.klass] = klass
1229
1230 return klass.klass
1231 else:
1232 raise TypeError("Expecting class type or ClassAlias from loader")
1233
1234
1235 raise UnknownClassAlias("Unknown alias for %r" % (alias,))
1236
1237
1239 """
1240 A generator function to decode a datastream.
1241
1242 @kwarg stream: AMF data.
1243 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
1244 @type encoding: C{int}
1245 @kwarg encoding: AMF encoding type.
1246 @type context: L{AMF0 Context<pyamf.amf0.Context>} or
1247 L{AMF3 Context<pyamf.amf3.Context>}
1248 @kwarg context: Context.
1249 @return: Each element in the stream.
1250 """
1251 encoding = kwargs.pop('encoding', DEFAULT_ENCODING)
1252 decoder = _get_decoder_class(encoding)(*args, **kwargs)
1253
1254 while 1:
1255 try:
1256 yield decoder.readElement()
1257 except EOStream:
1258 break
1259
1260
1262 """
1263 A helper function to encode an element.
1264
1265 @type args: C{mixed}
1266 @keyword element: Python data.
1267 @type encoding: C{int}
1268 @keyword encoding: AMF encoding type.
1269 @type context: L{amf0.Context<pyamf.amf0.Context>} or
1270 L{amf3.Context<pyamf.amf3.Context>}
1271 @keyword context: Context.
1272
1273 @rtype: C{StringIO}
1274 @return: File-like object.
1275 """
1276 encoding = kwargs.pop('encoding', DEFAULT_ENCODING)
1277
1278 encoder = _get_encoder_class(encoding)(**kwargs)
1279 stream = encoder.stream
1280
1281 for el in args:
1282 encoder.writeElement(el)
1283
1284 stream.seek(0)
1285
1286 return stream
1287
1288
1290 """
1291 Returns a subclassed instance of L{pyamf.BaseDecoder}, based on C{encoding}
1292 """
1293 return _get_decoder_class(encoding)(*args, **kwargs)
1294
1295
1297 """
1298 Get compatible decoder.
1299
1300 @type encoding: C{int}
1301 @param encoding: AMF encoding version.
1302 @raise ValueError: AMF encoding version is unknown.
1303
1304 @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or
1305 L{amf3.Decoder<pyamf.amf3.Decoder>}
1306 @return: AMF0 or AMF3 decoder.
1307 """
1308 if encoding == AMF0:
1309 from pyamf import amf0
1310
1311 return amf0.Decoder
1312 elif encoding == AMF3:
1313 from pyamf import amf3
1314
1315 return amf3.Decoder
1316
1317 raise ValueError("Unknown encoding %s" % (encoding,))
1318
1319
1321 """
1322 Returns a subclassed instance of L{pyamf.BaseEncoder}, based on C{encoding}
1323 """
1324 return _get_encoder_class(encoding)(*args, **kwargs)
1325
1326
1328 """
1329 Get compatible encoder.
1330
1331 @type encoding: C{int}
1332 @param encoding: AMF encoding version.
1333 @raise ValueError: AMF encoding version is unknown.
1334
1335 @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or
1336 L{amf3.Encoder<pyamf.amf3.Encoder>}
1337 @return: AMF0 or AMF3 encoder.
1338 """
1339 if encoding == AMF0:
1340 from pyamf import amf0
1341
1342 return amf0.Encoder
1343 elif encoding == AMF3:
1344 from pyamf import amf3
1345
1346 return amf3.Encoder
1347
1348 raise ValueError("Unknown encoding %s" % (encoding,))
1349
1350
1351 -def get_context(encoding, **kwargs):
1352 return _get_context_class(encoding)(**kwargs)
1353
1354
1355 -def _get_context_class(encoding):
1356 """
1357 Gets a compatible context class.
1358
1359 @type encoding: C{int}
1360 @param encoding: AMF encoding version.
1361 @raise ValueError: AMF encoding version is unknown.
1362
1363 @rtype: L{amf0.Context<pyamf.amf0.Context>} or
1364 L{amf3.Context<pyamf.amf3.Context>}
1365 @return: AMF0 or AMF3 context class.
1366 """
1367 if encoding == AMF0:
1368 from pyamf import amf0
1369
1370 return amf0.Context
1371 elif encoding == AMF3:
1372 from pyamf import amf3
1373
1374 return amf3.Context
1375
1376 raise ValueError("Unknown encoding %s" % (encoding,))
1377
1378
1380 """
1381 Loader for BlazeDS framework compatibility classes, specifically
1382 implementing C{ISmallMessage}.
1383
1384 @see: U{BlazeDS (external)<http://opensource.adobe.com/wiki/display/blazeds/BlazeDS>}
1385 @since: 0.5
1386 """
1387 if alias not in ['DSC', 'DSK']:
1388 return
1389
1390 import pyamf.flex.messaging
1391
1392 return CLASS_CACHE[alias]
1393
1394
1396 """
1397 Loader for L{Flex<pyamf.flex>} framework compatibility classes.
1398
1399 @raise UnknownClassAlias: Trying to load unknown Flex compatibility class.
1400 """
1401 if not alias.startswith('flex.'):
1402 return
1403
1404 try:
1405 if alias.startswith('flex.messaging.messages'):
1406 import pyamf.flex.messaging
1407 elif alias.startswith('flex.messaging.io'):
1408 import pyamf.flex
1409 elif alias.startswith('flex.data.messages'):
1410 import pyamf.flex.data
1411
1412 return CLASS_CACHE[alias]
1413 except KeyError:
1414 raise UnknownClassAlias(alias)
1415
1416
1418 """
1419 Adds a custom type to L{TYPE_MAP}. A custom type allows fine grain control
1420 of what to encode to an AMF data stream.
1421
1422 @raise TypeError: Unable to add as a custom type (expected a class or callable).
1423 @raise KeyError: Type already exists.
1424 """
1425 def _check_type(type_):
1426 if not (isinstance(type_, (type, types.ClassType)) or callable(type_)):
1427 raise TypeError(r'Unable to add '%r' as a custom type (expected a '
1428 'class or callable)' % (type_,))
1429
1430 if isinstance(type_, list):
1431 type_ = tuple(type_)
1432
1433 if type_ in TYPE_MAP:
1434 raise KeyError('Type %r already exists' % (type_,))
1435
1436 if isinstance(type_, types.TupleType):
1437 for x in type_:
1438 _check_type(x)
1439 else:
1440 _check_type(type_)
1441
1442 TYPE_MAP[type_] = func
1443
1444
1446 """
1447 Gets the declaration for the corresponding custom type.
1448
1449 @raise KeyError: Unknown type.
1450 """
1451 if isinstance(type_, list):
1452 type_ = tuple(type_)
1453
1454 for (k, v) in TYPE_MAP.iteritems():
1455 if k == type_:
1456 return v
1457
1458 raise KeyError("Unknown type %r" % (type_,))
1459
1460
1462 """
1463 Removes the custom type declaration.
1464
1465 @return: Custom type declaration.
1466 """
1467 declaration = get_type(type_)
1468
1469 del TYPE_MAP[type_]
1470
1471 return declaration
1472
1473
1475 """
1476 Maps an exception class to a string code. Used to map remoting C{onStatus}
1477 objects to an exception class so that an exception can be built to
1478 represent that error::
1479
1480 class AuthenticationError(Exception):
1481 pass
1482
1483 An example: C{add_error_class(AuthenticationError, 'Auth.Failed')}
1484
1485 @type code: C{str}
1486
1487 @raise TypeError: C{klass} must be a C{class} type.
1488 @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class.
1489 @raise ValueError: Code is already registered.
1490 """
1491 if not isinstance(code, basestring):
1492 code = str(code)
1493
1494 if not isinstance(klass, (type, types.ClassType)):
1495 raise TypeError("klass must be a class type")
1496
1497 mro = inspect.getmro(klass)
1498
1499 if not Exception in mro:
1500 raise TypeError('Error classes must subclass the __builtin__.Exception class')
1501
1502 if code in ERROR_CLASS_MAP.keys():
1503 raise ValueError('Code %s is already registered' % (code,))
1504
1505 ERROR_CLASS_MAP[code] = klass
1506
1507
1509 """
1510 Removes a class from C{ERROR_CLASS_MAP}.
1511
1512 @raise ValueError: Code is not registered.
1513 @raise ValueError: Class is not registered.
1514 @raise TypeError: Invalid type, expected C{class} or C{string}.
1515 """
1516 if isinstance(klass, basestring):
1517 if not klass in ERROR_CLASS_MAP.keys():
1518 raise ValueError('Code %s is not registered' % (klass,))
1519 elif isinstance(klass, (type, types.ClassType)):
1520 classes = ERROR_CLASS_MAP.values()
1521 if not klass in classes:
1522 raise ValueError('Class %s is not registered' % (klass,))
1523
1524 klass = ERROR_CLASS_MAP.keys()[classes.index(klass)]
1525 else:
1526 raise TypeError("Invalid type, expected class or string")
1527
1528 del ERROR_CLASS_MAP[klass]
1529
1530
1532 """
1533 This function allows you to map subclasses of L{ClassAlias} to classes
1534 listed in C{args}.
1535
1536 When an object is read/written from/to the AMF stream, a paired
1537 L{ClassAlias} instance is created (or reused), based on the Python class
1538 of that object. L{ClassAlias} provides important metadata for the class
1539 and can also control how the equivalent Python object is created, how the
1540 attributes are applied etc.
1541
1542 Use this function if you need to do something non-standard.
1543
1544 @see: L{pyamf.adapters._google_appengine_ext_db.DataStoreClassAlias} for a
1545 good example.
1546 @since: 0.4
1547 @raise RuntimeError: Type is already registered.
1548 @raise TypeError: C{klass} must be a class.
1549 @raise ValueError: New aliases must subclass L{pyamf.ClassAlias}.
1550 @raise ValueError: At least one type must be supplied.
1551 """
1552
1553 def check_type_registered(arg):
1554
1555 for k, v in ALIAS_TYPES.iteritems():
1556 for kl in v:
1557 if arg is kl:
1558 raise RuntimeError('%r is already registered under %r' % (arg, k))
1559
1560 if not isinstance(klass, (type, types.ClassType)):
1561 raise TypeError('klass must be class')
1562
1563 if not issubclass(klass, ClassAlias):
1564 raise ValueError('New aliases must subclass pyamf.ClassAlias')
1565
1566 if len(args) == 0:
1567 raise ValueError('At least one type must be supplied')
1568
1569 if len(args) == 1 and callable(args[0]):
1570 c = args[0]
1571
1572 check_type_registered(c)
1573 else:
1574 for arg in args:
1575 if not isinstance(arg, (type, types.ClassType)):
1576 raise TypeError('%r must be class' % (arg,))
1577
1578 check_type_registered(arg)
1579
1580 ALIAS_TYPES[klass] = args
1581
1582
1583 -def register_package(module=None, package=None, separator='.', ignore=[], strict=True):
1584 """
1585 This is a helper function that takes the concept of Actionscript packages
1586 and registers all the classes in the supplied Python module under that
1587 package. It auto-aliased all classes in C{module} based on C{package}.
1588
1589 e.g. C{mymodule.py}::
1590 class User(object):
1591 pass
1592
1593 class Permission(object):
1594 pass
1595
1596 >>> import mymodule
1597 >>> pyamf.register_package(mymodule, 'com.example.app')
1598
1599 Now all instances of C{mymodule.User} will appear in Actionscript under the
1600 alias 'com.example.app.User'. Same goes for C{mymodule.Permission} - the
1601 Actionscript alias is 'com.example.app.Permission'. The reverse is also
1602 true, any objects with the correct aliases will now be instances of the
1603 relevant Python class.
1604
1605 This function respects the C{__all__} attribute of the module but you can
1606 have further control of what not to auto alias by populating the C{ignore}
1607 argument.
1608
1609 This function provides the ability to register the module it is being
1610 called in, an example:
1611
1612 >>> class Foo:
1613 ... pass
1614 ...
1615 >>> class Bar:
1616 ... pass
1617 ...
1618 >>> import pyamf
1619 >>> pyamf.register_package('foo')
1620
1621 @param module: The Python module that will contain all the classes to
1622 auto alias.
1623 @type module: C{module} or C{dict}
1624 @param package: The base package name. e.g. 'com.example.app'. If this
1625 is C{None} then the value is inferred from module.__name__.
1626 @type package: C{str} or C{unicode} or C{None}
1627 @param separator: The separator used to append to C{package} to form the
1628 complete alias.
1629 @type separator: C{str}
1630 @param ignore: To give fine grain control over what gets aliased and what
1631 doesn't, supply a list of classes that you B{do not} want to be aliased.
1632 @type ignore: C{iterable}
1633 @param strict: If this value is C{True} then only classes that originate
1634 from C{module} will be registered, all others will be left in peace.
1635 @type strict: C{bool}
1636 @return: A collection of all the classes that were registered and their
1637 respective L{ClassAlias} objects.
1638 @since: 0.5
1639 """
1640 if isinstance(module, basestring):
1641 if module == '':
1642 raise TypeError('Cannot get list of classes from %r' % (module,))
1643
1644 package = module
1645 module = None
1646
1647 if module is None:
1648 import inspect
1649
1650 prev_frame = inspect.stack()[1][0]
1651 module = prev_frame.f_locals
1652
1653 if type(module) is dict:
1654 has = lambda x: x in module.keys()
1655 get = module.__getitem__
1656 else:
1657 has = lambda x: hasattr(module, x)
1658 get = lambda x: getattr(module, x)
1659
1660 if package is None:
1661 if has('__name__'):
1662 package = get('__name__')
1663 else:
1664 raise TypeError('Cannot get list of classes from %r' % (module,))
1665
1666 if has('__all__'):
1667 keys = get('__all__')
1668 elif hasattr(module, '__dict__'):
1669 keys = module.__dict__.keys()
1670 elif hasattr(module, 'keys'):
1671 keys = module.keys()
1672 else:
1673 raise TypeError('Cannot get list of classes from %r' % (module,))
1674
1675 def check_attr(attr):
1676 if not isinstance(attr, (types.ClassType, types.TypeType)):
1677 return False
1678
1679 if attr.__name__ in ignore:
1680 return False
1681
1682 try:
1683 if strict and attr.__module__ != get('__name__'):
1684 return False
1685 except AttributeError:
1686 return False
1687
1688 return True
1689
1690
1691 classes = filter(check_attr, [get(x) for x in keys])
1692
1693 registered = {}
1694
1695 for klass in classes:
1696 alias = '%s%s%s' % (package, separator, klass.__name__)
1697
1698 registered[klass] = register_class(klass, alias)
1699
1700 return registered
1701
1702
1703
1704 register_class(ASObject)
1705 register_class_loader(flex_loader)
1706 register_class_loader(blaze_loader)
1707 register_alias_type(TypedObjectClassAlias, TypedObject)
1708 register_alias_type(ErrorAlias, Exception)
1709
1710 register_adapters()
1711