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 @author: U{Arnar Birgisson<mailto:arnarbi@gmail.com>}
11 @author: U{Thijs Triemstra<mailto:info@collab.nl>}
12 @author: U{Nick Joyce<mailto:nick@boxdesign.co.uk>}
13
14 @copyright: Copyright (c) 2007-2008 The PyAMF Project. All Rights Reserved.
15 @contact: U{dev@pyamf.org<mailto:dev@pyamf.org>}
16 @see: U{http://pyamf.org}
17
18 @since: October 2007
19 @status: Beta
20 @version: 0.3.1
21 """
22
23 import types
24
25 from pyamf import util
26 from pyamf.adapters import register_adapters
27
28 __all__ = [
29 'register_class',
30 'register_class_loader',
31 'encode',
32 'decode',
33 '__version__']
34
35
36 __version__ = (0, 3, 1)
37
38
39 CLASS_CACHE = {}
40
41 CLASS_LOADERS = []
42
43
44 TYPE_MAP = {}
45
46
47 ERROR_CLASS_MAP = {}
48
49
50 AMF0 = 0
51
52 AMF3 = 3
53
54 ENCODING_TYPES = (AMF0, AMF3)
55
57 """
58 Typecodes used to identify AMF clients and servers.
59
60 @see: U{Flash Player on WikiPedia (external)
61 <http://en.wikipedia.org/wiki/Flash_Player>}
62 @see: U{Flash Media Server on WikiPedia (external)
63 <http://en.wikipedia.org/wiki/Adobe_Flash_Media_Server>}
64 """
65
66 Flash6 = 0
67
68 FlashCom = 1
69
70 Flash9 = 3
71
72
73 CLIENT_TYPES = []
74
75 for x in ClientTypes.__dict__:
76 if not x.startswith('_'):
77 CLIENT_TYPES.append(ClientTypes.__dict__[x])
78 del x
79
80
81 Undefined = object()
82
84 """
85 Base AMF Error.
86
87 All AMF related errors should be subclassed from this class.
88 """
89
91 """
92 Raised if there is an error in decoding an AMF data stream.
93 """
94
96 """
97 Raised if the data stream has come to a natural end.
98 """
99
101 """
102 Raised if an AMF data stream refers to a non-existent object
103 or string reference.
104 """
105
107 """
108 Raised if the element could not be encoded to the stream.
109
110 @bug: See U{Docuverse blog (external)
111 <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>}
112 for more info about the empty key string array bug.
113 """
114
116 """
117 Raised if the AMF stream specifies a class that does not
118 have an alias.
119
120 @see: L{register_class}
121 """
122
123 -class BaseContext(object):
124 """
125 I hold the AMF context for en/decoding streams.
126 """
127 - def __init__(self):
129
131 self.objects = []
132 self.rev_objects = {}
133
134 self.class_aliases = {}
135
136 - def getObject(self, ref):
137 """
138 Gets an object based on a reference.
139
140 @raise TypeError: Bad reference type.
141 """
142 if not isinstance(ref, (int, long)):
143 raise TypeError, "Bad reference type"
144
145 try:
146 return self.objects[ref]
147 except IndexError:
148 raise ReferenceError
149
150 - def getObjectReference(self, obj):
151 """
152 Gets a reference for an object.
153 """
154 try:
155 return self.rev_objects[id(obj)]
156 except KeyError:
157 raise ReferenceError
158
159 - def addObject(self, obj):
160 """
161 Adds a reference to C{obj}.
162
163 @type obj: C{mixed}
164 @param obj: The object to add to the context.
165
166 @rtype: C{int}
167 @return: Reference to C{obj}.
168 """
169 self.objects.append(obj)
170 idx = len(self.objects) - 1
171 self.rev_objects[id(obj)] = idx
172
173 return idx
174
175 - def getClassAlias(self, klass):
176 """
177 Gets a class alias based on the supplied C{klass}.
178 """
179 if klass not in self.class_aliases:
180 try:
181 self.class_aliases[klass] = get_class_alias(klass)
182 except UnknownClassAlias:
183 self.class_aliases[klass] = None
184
185 return self.class_aliases[klass]
186
187 - def __copy__(self):
188 raise NotImplementedError
189
191 """
192 This class represents a Flash Actionscript Object (typed or untyped).
193
194 I supply a C{__builtin__.dict} interface to support get/setattr calls.
195 """
197 dict.__init__(self, *args, **kwargs)
198
200 try:
201 return self[k]
202 except KeyError:
203 raise AttributeError, 'unknown attribute \'%s\'' % k
204
207
210
212 """
213 Used to be able to specify the C{mixedarray} type.
214 """
215
281
282
283
285 """
286 Class alias.
287
288 All classes are initially set to a dynamic state.
289
290 @ivar attrs: A list of attributes to encode for this class.
291 @type attrs: C{list}
292 @ivar metadata: A list of metadata tags similar to ActionScript tags.
293 @type metadata: C{list}
294 """
295 - def __init__(self, klass, alias, attrs=None, attr_func=None, metadata=[]):
296 """
297 @type klass: C{class}
298 @param klass: The class to alias.
299 @type alias: C{str}
300 @param alias: The alias to the class e.g. C{org.example.Person}. If the
301 value of this is C{None}, then it is worked out based on the C{klass}.
302 The anonymous tag is also added to the class.
303 @type attrs:
304 @param attrs:
305 @type metadata:
306 @param metadata:
307
308 @raise TypeError: The C{klass} must be a class type.
309 @raise TypeError: The C{read_func} must be callable.
310 @raise TypeError: The C{write_func} must be callable.
311 """
312 if not isinstance(klass, (type, types.ClassType)):
313 raise TypeError, "klass must be a class type"
314
315 self.metadata = ClassMetaData(metadata)
316
317 if alias is None:
318 self.metadata.append('anonymous')
319 alias = "%s.%s" % (klass.__module__, klass.__name__,)
320
321 self.klass = klass
322 self.alias = alias
323 self.attr_func = attr_func
324 self.attrs = attrs
325
326 if 'external' in self.metadata:
327
328 if not hasattr(klass, '__readamf__'):
329 raise AttributeError("An externalised class was specified, but"
330 " no __readamf__ attribute was found for class %s" % (
331 klass.__name__))
332
333 if not hasattr(klass, '__writeamf__'):
334 raise AttributeError("An externalised class was specified, but"
335 " no __writeamf__ attribute was found for class %s" % (
336 klass.__name__))
337
338 if not isinstance(klass.__readamf__, types.UnboundMethodType):
339 raise TypeError, "%s.__readamf__ must be callable" % (
340 klass.__name__)
341
342 if not isinstance(klass.__writeamf__, types.UnboundMethodType):
343 raise TypeError, "%s.__writeamf__ must be callable" % (
344 klass.__name__)
345
346 if 'dynamic' in self.metadata:
347 if attr_func is not None and not callable(attr_func):
348 raise TypeError, "attr_func must be callable"
349
350 if 'static' in self.metadata:
351 if attrs is None:
352 raise ValueError, "attrs keyword must be specified for static classes"
353
355 """
356 Creates an instance of the klass.
357
358 @return: Instance of C{self.klass}.
359 """
360 if hasattr(self.klass, '__setstate__') or hasattr(self.klass, '__getstate__'):
361 if type(self.klass) is types.TypeType:
362 return self.klass.__new__(self.klass)
363 elif type(self.klass) is types.ClassType:
364 return util.make_classic_instance(self.klass)
365
366 raise TypeError, 'invalid class type %r' % self.klass
367
368 return self.klass(*args, **kwargs)
369
372
374 return '<ClassAlias alias=%s klass=%s @ %s>' % (
375 self.alias, self.klass, id(self))
376
378 if isinstance(other, basestring):
379 return self.alias == other
380 elif isinstance(other, self.__class__):
381 return self.klass == other.klass
382 elif isinstance(other, (type, types.ClassType)):
383 return self.klass == other
384 else:
385 return False
386
389
390 - def getAttrs(self, obj, attrs=None, traverse=True):
391 if attrs is None:
392 attrs = []
393
394 did_something = False
395
396 if self.attrs is not None:
397 did_something = True
398 attrs += self.attrs
399
400 if 'dynamic' in self.metadata and self.attr_func is not None:
401 did_something = True
402 extra_attrs = self.attr_func(obj)
403
404 attrs += [key for key in extra_attrs if key not in attrs]
405
406 if traverse is True:
407 for base in util.get_mro(obj.__class__):
408 try:
409 alias = get_class_alias(base)
410 except UnknownClassAlias:
411 continue
412
413 x = alias.getAttrs(obj, attrs, False)
414
415 if x is not None:
416 attrs += x
417 did_something = True
418
419 if did_something is False:
420 return None
421
422 a = []
423
424 for x in attrs:
425 if x not in a:
426 a.append(x)
427
428 return a
429
431 """
432 Base AMF decoder.
433
434 @ivar context_class: The context for the decoding.
435 @type context_class: An instance of C{BaseDecoder.context_class}
436 @ivar type_map:
437 @type type_map: C{list}
438 @ivar stream: The underlying data stream.
439 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
440 """
441 context_class = BaseContext
442 type_map = {}
443
444 - def __init__(self, data=None, context=None):
445 """
446 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
447 @param data: Data stream.
448 @type context: L{Context<pyamf.amf0.Context>}
449 @param context: Context.
450 @raise TypeError: The C{context} parameter must be of
451 type L{Context<pyamf.amf0.Context>}.
452 """
453
454 if isinstance(data, util.BufferedByteStream):
455 self.stream = d