Package pyamf :: Module amf0
[hide private]
[frames] | no frames]

Source Code for Module pyamf.amf0

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (c) 2007-2009 The PyAMF Project. 
  4  # See LICENSE.txt for details. 
  5   
  6  """ 
  7  AMF0 implementation. 
  8   
  9  C{AMF0} supports the basic data types used for the NetConnection, NetStream, 
 10  LocalConnection, SharedObjects and other classes in the Adobe Flash Player. 
 11   
 12  @see: U{Official AMF0 Specification in English (external) 
 13  <http://opensource.adobe.com/wiki/download/attachments/1114283/amf0_spec_121207.pdf>} 
 14  @see: U{Official AMF0 Specification in Japanese (external) 
 15  <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf0_spec_121207.pdf>} 
 16  @see: U{AMF documentation on OSFlash (external) 
 17  <http://osflash.org/documentation/amf>} 
 18   
 19  @since: 0.1 
 20  """ 
 21   
 22  import datetime 
 23  import types 
 24  import copy 
 25   
 26  import pyamf 
 27  from pyamf import util 
 28   
 29   
 30  #: Represented as 9 bytes: 1 byte for C{0×00} and 8 bytes a double 
 31  #: representing the value of the number. 
 32  TYPE_NUMBER      = '\x00' 
 33  #: Represented as 2 bytes: 1 byte for C{0×01} and a second, C{0×00} 
 34  #: for C{False}, C{0×01} for C{True}. 
 35  TYPE_BOOL        = '\x01' 
 36  #: Represented as 3 bytes + len(String): 1 byte C{0×02}, then a UTF8 string, 
 37  #: including the top two bytes representing string length as a C{int}. 
 38  TYPE_STRING      = '\x02' 
 39  #: Represented as 1 byte, C{0×03}, then pairs of UTF8 string, the key, and 
 40  #: an AMF element, ended by three bytes, C{0×00} C{0×00} C{0×09}. 
 41  TYPE_OBJECT      = '\x03' 
 42  #: MovieClip does not seem to be supported by Remoting. 
 43  #: It may be used by other AMF clients such as SharedObjects. 
 44  TYPE_MOVIECLIP   = '\x04' 
 45  #: 1 single byte, C{0×05} indicates null. 
 46  TYPE_NULL        = '\x05' 
 47  #: 1 single byte, C{0×06} indicates null. 
 48  TYPE_UNDEFINED   = '\x06' 
 49  #: When an ActionScript object refers to itself, such C{this.self = this}, 
 50  #: or when objects are repeated within the same scope (for example, as the 
 51  #: two parameters of the same function called), a code of C{0×07} and an 
 52  #: C{int}, the reference number, are written. 
 53  TYPE_REFERENCE   = '\x07' 
 54  #: A MixedArray is indicated by code C{0×08}, then a Long representing the 
 55  #: highest numeric index in the array, or 0 if there are none or they are 
 56  #: all negative. After that follow the elements in key : value pairs. 
 57  TYPE_MIXEDARRAY  = '\x08' 
 58  #: @see: L{TYPE_OBJECT} 
 59  TYPE_OBJECTTERM  = '\x09' 
 60  #: An array is indicated by C{0x0A}, then a Long for array length, then the 
 61  #: array elements themselves. Arrays are always sparse; values for 
 62  #: inexistant keys are set to null (C{0×06}) to maintain sparsity. 
 63  TYPE_ARRAY       = '\x0A' 
 64  #: Date is represented as C{00x0B}, then a double, then an C{int}. The double 
 65  #: represents the number of milliseconds since 01/01/1970. The C{int} represents 
 66  #: the timezone offset in minutes between GMT. Note for the latter than values 
 67  #: greater than 720 (12 hours) are represented as M{2^16} - the value. Thus GMT+1 
 68  #: is 60 while GMT-5 is 65236. 
 69  TYPE_DATE        = '\x0B' 
 70  #: LongString is reserved for strings larger then M{2^16} characters long. It 
 71  #: is represented as C{00x0C} then a LongUTF. 
 72  TYPE_LONGSTRING  = '\x0C' 
 73  #: Trying to send values which don’t make sense, such as prototypes, functions, 
 74  #: built-in objects, etc. will be indicated by a single C{00x0D} byte. 
 75  TYPE_UNSUPPORTED = '\x0D' 
 76  #: Remoting Server -> Client only. 
 77  #: @see: L{RecordSet} 
 78  #: @see: U{RecordSet structure on OSFlash (external) 
 79  #: <http://osflash.org/documentation/amf/recordset>} 
 80  TYPE_RECORDSET   = '\x0E' 
 81  #: The XML element is indicated by C{00x0F} and followed by a LongUTF containing 
 82  #: the string representation of the XML object. The receiving gateway may which 
 83  #: to wrap this string inside a language-specific standard XML object, or simply 
 84  #: pass as a string. 
 85  TYPE_XML         = '\x0F' 
 86  #: A typed object is indicated by C{0×10}, then a UTF string indicating class 
 87  #: name, and then the same structure as a normal C{0×03} Object. The receiving 
 88  #: gateway may use a mapping scheme, or send back as a vanilla object or 
 89  #: associative array. 
 90  TYPE_TYPEDOBJECT = '\x10' 
 91  #: An AMF message sent from an AVM+ client such as the Flash Player 9 may break 
 92  #: out into L{AMF3<pyamf.amf3>} mode. In this case the next byte will be the 
 93  #: AMF3 type code and the data will be in AMF3 format until the decoded object 
 94  #: reaches it’s logical conclusion (for example, an object has no more keys). 
 95  TYPE_AMF3        = '\x11' 
 96   
 97   
98 -class Context(pyamf.BaseContext):
99 """ 100 I hold the AMF0 context for en/decoding streams. 101 102 AMF0 object references start at index 1. 103 104 @ivar amf3_objs: A list of objects that have been decoded in 105 L{AMF3<pyamf.amf3>}. 106 @type amf3_objs: L{util.IndexedCollection} 107 """ 108
109 - def __init__(self, **kwargs):
110 self.amf3_objs = [] 111 112 pyamf.BaseContext.__init__(self, **kwargs)
113
114 - def clear(self):
115 """ 116 Clears the context. 117 """ 118 pyamf.BaseContext.clear(self) 119 120 self.amf3_objs = [] 121 122 if hasattr(self, 'amf3_context'): 123 self.amf3_context.clear()
124
125 - def hasAMF3ObjectReference(self, obj):
126 """ 127 Gets a reference for an object. 128 129 @raise ReferenceError: Unknown AMF3 object reference. 130 """ 131 return obj in self.amf3_objs 132 o = self.amf3_objs.getReferenceTo(obj) 133 134 if o is None and self.exceptions: 135 raise pyamf.ReferenceError( 136 'Unknown AMF3 reference for (%r)' % (obj,)) 137 138 return o
139
140 - def addAMF3Object(self, obj):
141 """ 142 Adds an AMF3 reference to C{obj}. 143 144 @type obj: C{mixed} 145 @param obj: The object to add to the context. 146 @rtype: C{int} 147 @return: Reference to C{obj}. 148 """ 149 return self.amf3_objs.append(obj)
150
151 - def __copy__(self):
152 cpy = self.__class__(exceptions=self.exceptions) 153 cpy.amf3_objs = copy.copy(self.amf3_objs) 154 155 return cpy
156 157
158 -class Decoder(pyamf.BaseDecoder):
159 """ 160 Decodes an AMF0 stream. 161 """ 162 163 context_class = Context 164 165 # XXX nick: Do we need to support TYPE_MOVIECLIP here? 166 type_map = { 167 TYPE_NUMBER: 'readNumber', 168 TYPE_BOOL: 'readBoolean', 169 TYPE_STRING: 'readString', 170 TYPE_OBJECT: 'readObject', 171 TYPE_NULL: 'readNull', 172 TYPE_UNDEFINED: 'readUndefined', 173 TYPE_REFERENCE: 'readReference', 174 TYPE_MIXEDARRAY: 'readMixedArray', 175 TYPE_ARRAY: 'readList', 176 TYPE_DATE: 'readDate', 177 TYPE_LONGSTRING: 'readLongString', 178 # TODO: do we need a special value here? 179 TYPE_UNSUPPORTED:'readNull', 180 TYPE_XML: 'readXML', 181 TYPE_TYPEDOBJECT:'readTypedObject', 182 TYPE_AMF3: 'readAMF3' 183 } 184
185 - def readNumber(self):
186 """ 187 Reads a ActionScript C{Number} value. 188 189 In ActionScript 1 and 2 the C{NumberASTypes} type represents all numbers, 190 both floats and integers. 191 192 @rtype: C{int} or C{float} 193 """ 194 return _check_for_int(self.stream.read_double())
195
196 - def readBoolean(self):
197 """ 198 Reads a ActionScript C{Boolean} value. 199 200 @rtype: C{bool} 201 @return: Boolean. 202 """ 203 return bool(self.stream.read_uchar())
204
205 - def readNull(self):
206 """ 207 Reads a ActionScript C{null} value. 208 209 @return: C{None} 210 @rtype: C{None} 211 """ 212 return None
213
214 - def readUndefined(self):
215 """ 216 Reads an ActionScript C{undefined} value. 217 218 @return: L{Undefined<pyamf.Undefined>} 219 """ 220 return pyamf.Undefined
221
222 - def readMixedArray(self):
223 """ 224 Read mixed array. 225 226 @rtype: C{dict} 227 @return: C{dict} read from the stream 228 """ 229 len = self.stream.read_ulong() 230 obj = pyamf.MixedArray() 231 self.context.addObject(obj) 232 self._readObject(obj) 233 ikeys = [] 234 235 for key in obj.keys(): 236 try: 237 ikey = int(key) 238 ikeys.append((key, ikey)) 239 obj[ikey] = obj[key] 240 del obj[key] 241 except ValueError: 242 # XXX: do we want to ignore this? 243 pass 244 245 ikeys.sort() 246 247 return obj
248
249 - def readList(self):
250 """ 251 Read a C{list} from the data stream. 252 253 @rtype: C{list} 254 @return: C{list} 255 """ 256 obj = [] 257 self.context.addObject(obj) 258 len = self.stream.read_ulong() 259 260 for i in xrange(len): 261 obj.append(self.readElement()) 262 263 return obj
264
265 - def readTypedObject(self):
266 """ 267 Reads an ActionScript object from the stream and attempts to 268 'cast' it. 269 270 @see: L{load_class<pyamf.load_class>} 271 """ 272 classname = self.readString() 273 alias = None 274 275 try: 276 alias = pyamf.get_class_alias(classname) 277 278 ret = alias.createInstance(codec=self) 279 except pyamf.UnknownClassAlias: 280 if self.strict: 281 raise 282 283 ret = pyamf.TypedObject(classname) 284 285 self.context.addObject(ret) 286 self._readObject(ret, alias) 287 288 return ret
289
290 - def readAMF3(self):
291 """ 292 Read AMF3 elements from the data stream. 293 294 @rtype: C{mixed} 295 @return: The AMF3 element read from the stream 296 """ 297 if not hasattr(self.context, 'amf3_context'): 298 self.context.amf3_context = pyamf.get_context(pyamf.AMF3, exceptions=False) 299 300 if not hasattr(self.context, 'amf3_decoder'): 301 self.context.amf3_decoder = pyamf.get_decoder( 302 pyamf.AMF3, self.stream, self.context.amf3_context) 303 304 decoder = self.context.amf3_decoder 305 element = decoder.readElement() 306 self.context.addAMF3Object(element) 307 308 return element
309
310 - def readString(self):
311 """ 312 Reads a string from the data stream. 313 314 @rtype: C{str} 315 @return: string 316 """ 317 len = self.stream.read_ushort() 318 return self.stream.read_utf8_string(len)
319
320 - def _readObject(self, obj, alias=None):
321 obj_attrs = dict() 322 323 key = self.readString().encode('utf8') 324 325 while self.stream.peek() != TYPE_OBJECTTERM: 326 obj_attrs[key] = self.readElement() 327 key = self.readString().encode('utf8') 328 329 # discard the end marker (TYPE_OBJECTTERM) 330 self.stream.read(1) 331 332 if alias: 333 alias.applyAttributes(obj, obj_attrs, codec=self) 334 else: 335 util.set_attrs(obj, obj_attrs)
336
337 - def readObject(self):
338 """ 339 Reads an object from the data stream. 340 341 @rtype: L{ASObject<pyamf.ASObject>} 342 """ 343 obj = pyamf.ASObject() 344 self.context.addObject(obj) 345 346 self._readObject(obj) 347 348 return obj
349
350 - def readReference(self):
351 """ 352 Reads a reference from the data stream. 353 354 @raise pyamf.ReferenceError: Unknown reference. 355 """ 356 idx = self.stream.read_ushort() 357 358 o = self.context.getObject(idx) 359 360 if o is None: 361 raise pyamf.ReferenceError('Unknown reference %d' % (idx,)) 362 363 return o
364
365 - def readDate(self):
366 """ 367 Reads a UTC date from the data stream. Client and servers are 368 responsible for applying their own timezones. 369 370 Date: C{0x0B T7 T6} .. C{T0 Z1 Z2 T7} to C{T0} form a 64 bit 371 Big Endian number that specifies the number of nanoseconds 372 that have passed since 1/1/1970 0:00 to the specified time. 373 This format is UTC 1970. C{Z1} and C{Z0} for a 16 bit Big 374 Endian number indicating the indicated time's timezone in 375 minutes. 376 """ 377 ms = self.stream.read_double() / 1000.0 378 tz = self.stream.read_short() 379 380 # Timezones are ignored 381 d = util.get_datetime(ms) 382 383 if self.timezone_offset: 384 d = d + self.timezone_offset 385 386 self.context.addObject(d) 387 388 return d
389
390 - def readLongString(self):
391 """ 392 Read UTF8 string. 393 """ 394 len = self.stream.read_ulong() 395 396 return self.stream.read_utf8_string(len)
397
398 - def readXML(self):
399 """ 400 Read XML. 401 """ 402 data = self.readLongString() 403 xml = util.ET.fromstring(data) 404 self.context.addObject(xml) 405 406 return xml
407 408
409 -class Encoder(pyamf.BaseEncoder):
410 """ 411 Encodes an AMF0 stream. 412 413 @ivar use_amf3: A flag to determine whether this encoder knows about AMF3. 414 @type use_amf3: C{bool} 415 """ 416 417 context_class = Context 418 419 type_map = [ 420 ((types.BuiltinFunctionType, types.BuiltinMethodType, 421 types.FunctionType, types.GeneratorType, types.ModuleType, 422 types.LambdaType, types.MethodType), "writeFunc"), 423 ((types.NoneType,), "writeNull"), 424 ((bool,), "writeBoolean"), 425 ((int,long,float), "writeNumber"), 426 ((types.StringTypes,), "writeString"), 427 ((pyamf.ASObject,), "writeObject"), 428 ((pyamf.MixedArray,), "writeMixedArray"), 429 ((types.ListType, types.TupleType,), "writeArray"), 430 ((datetime.date, datetime.datetime, datetime.time), "writeDate"), 431 ((util.is_ET_element,), "writeXML"), 432 ((lambda x: x is pyamf.Undefined,), "writeUndefined"), 433 ((types.ClassType, types.TypeType), "writeClass"), 434 ((types.InstanceType,types.ObjectType,), "writeObject"), 435 ] 436
437 - def __init__(self, *args, **kwargs):
438 self.use_amf3 = kwargs.pop('use_amf3', False) 439 440 pyamf.BaseEncoder.__init__(self, *args, **kwargs)
441
442 - def writeType(self, t):
443 """ 444 Writes the type to the stream. 445 446 @type t: C{str} 447 @param t: ActionScript type. 448 449 @raise pyamf.EncodeError: AMF0 type is not recognized. 450 """ 451 self.stream.write(t)
452
453 - def writeUndefined(self, data):
454 """ 455 Writes the L{undefined<TYPE_UNDEFINED>} data type to the stream. 456 457 @param data: The C{undefined} data to be encoded to the AMF0 data 458 stream. 459 @type data: C{undefined} data 460 """ 461 self.writeType(TYPE_UNDEFINED)
462
463 - def writeClass(self, *args, **kwargs):
464 """ 465 Classes cannot be serialised. 466 """ 467 raise pyamf.EncodeError("Class objects cannot be serialised")
468
469 - def writeFunc(self, *args, **kwargs):
470 """ 471 Functions cannot be serialised. 472 """ 473 raise pyamf.EncodeError("Callables cannot be serialised")
474
475 - def writeUnsupported(self, data):
476 """ 477 Writes L{unsupported<TYPE_UNSUPPORTED>} data type to the 478 stream. 479 480 @param data: The C{unsupported} data to be encoded to the AMF0 481 data stream. 482 @type data: C{unsupported} data 483 """ 484 self.writeType(TYPE_UNSUPPORTED)
485
486 - def _writeElementFunc(self, data):
487 """ 488 Gets a function based on the type of data. 489 490 @see: L{pyamf.BaseEncoder._writeElementFunc} 491 """ 492 # There is a very specific use case that we must check for. 493 # In the context there is an array of amf3_objs that contain 494 # references to objects that are to be encoded in amf3. 495 496 if self.use_amf3 and self.context.hasAMF3ObjectReference(data): 497 return self.writeAMF3 498 499 return pyamf.BaseEncoder._writeElementFunc(self, data)
500
501 - def writeElement(self, data):
502 """ 503 Writes the data. 504 505 @type data: C{mixed} 506 @param data: The data to be encoded to the AMF0 data stream. 507 @raise EncodeError: Cannot find encoder func. 508 """ 509 func = self._writeElementFunc(data) 510 511 if func is None: 512 raise pyamf.EncodeError("Cannot find encoder func for %r" % (data,)) 513 514 func(data)
515
516 - def writeNull(self, n):
517 """ 518 Write null type to data stream. 519 520 @type n: C{None} 521 @param n: Is ignored. 522 """ 523 self.writeType(TYPE_NULL)
524
525 - def writeArray(self, a):
526 """ 527 Write array to the stream. 528 529 @type a: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 530 @param a: The array data to be encoded to the AMF0 data stream. 531 """ 532 alias = self.context.getClassAlias(a.__class__) 533 534 if alias.external: 535 # a is a subclassed list with a registered alias - push to the 536 # correct method 537 self.writeObject(a) 538 539 return 540 541 if self.writeReference(a) is not None: 542 return 543 544 self.context.addObject(a) 545 546 self.writeType(TYPE_ARRAY) 547 self.stream.write_ulong(len(a)) 548 549 for data in a: 550 self.writeElement(data)
551
552 - def writeNumber(self, n):
553 """ 554 Write number to the data stream. 555 556 @type n: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 557 @param n: The number data to be encoded to the AMF0 data stream. 558 """ 559 self.writeType(TYPE_NUMBER) 560 self.stream.write_double(float(n))
561
562 - def writeBoolean(self, b):
563 """ 564 Write boolean to the data stream. 565 566 @type b: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 567 @param b: The boolean data to be encoded to the AMF0 data stream. 568 """ 569 self.writeType(TYPE_BOOL) 570 571 if b: 572 self.stream.write_uchar(1) 573 else: 574 self.stream.write_uchar(0)
575
576 - def _writeString(self, s):
577 l = len(s) 578 579 if l > 0xffff: 580 self.stream.write_ulong(l) 581 else: 582 self.stream.write_ushort(l) 583 584 self.stream.write(s)
585
586 - def writeString(self, s, writeType=True):
587 """ 588 Write string to the data stream. 589 590 @type s: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 591 @param s: The string data to be encoded to the AMF0 data stream. 592 @type writeType: C{bool} 593 @param writeType: Write data type. 594 """ 595 t = type(s) 596 597 if t is str: 598 pass 599 elif isinstance(s, unicode): 600 s = s.encode('utf8') 601 elif not isinstance(s, basestring): 602 s = unicode(s).encode('utf8') 603 604 if writeType: 605 if len(s) > 0xffff: 606 self.writeType(TYPE_LONGSTRING) 607 else: 608 self.writeType(TYPE_STRING) 609 610 self._writeString(s)
611
612 - def writeReference(self, o):
613 """ 614 Write reference to the data stream. 615 616 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 617 @param o: The reference data to be encoded to the AMF0 data 618 stream. 619 """ 620 idx = self.context.getObjectReference(o) 621 622 if idx is None: 623 return None 624 625 self.writeType(TYPE_REFERENCE) 626 self.stream.write_ushort(idx) 627 628 return idx
629
630 - def _writeDict(self, o):
631 """ 632 Write C{dict} to the data stream. 633 634 @type o: C{iterable} 635 @param o: The C{dict} data to be encoded to the AMF0 data 636 stream. 637 """ 638 for key, val in o.iteritems(): 639 self.writeString(key, False) 640 self.writeElement(val)
641
642 - def writeMixedArray(self, o):
643 """ 644 Write mixed array to the data stream. 645 646 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 647 @param o: The mixed array data to be encoded to the AMF0 648 data stream. 649 """ 650 if self.writeReference(o) is not None: 651 return 652 653 self.context.addObject(o) 654 self.writeType(TYPE_MIXEDARRAY) 655 656 # TODO: optimise this 657 # work out the highest integer index 658 try: 659 # list comprehensions to save the day 660 max_index = max([y[0] for y in o.items() 661 if isinstance(y[0], (int, long))]) 662 663 if max_index < 0: 664 max_index = 0 665 except ValueError: 666 max_index = 0 667 668 self.stream.write_ulong(max_index) 669 670 self._writeDict(o) 671 self._writeEndObject()
672
673 - def _writeEndObject(self):
674 self.stream.write('\x00\x00') 675 self.writeType(TYPE_OBJECTTERM)
676
677 - def writeObject(self, o):
678 """ 679 Write object to the stream. 680 681 @type o: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 682 @param o: The object data to be encoded to the AMF0 data stream. 683 """ 684 if self.use_amf3: 685 self.writeAMF3(o) 686 687 return 688 689 if self.writeReference(o) is not None: 690 return 691 692 self.context.addObject(o) 693 alias = self.context.getClassAlias(o.__class__) 694 695 alias.compile() 696 697 if alias.amf3: 698 self.writeAMF3(o) 699 700 return 701 702 if alias.anonymous: 703 self.writeType(TYPE_OBJECT) 704 else: 705 self.writeType(TYPE_TYPEDOBJECT) 706 self.writeString(alias.alias, False) 707 708 sa, da = alias.getEncodableAttributes(o, codec=self) 709 710 if sa: 711 for key in alias.static_attrs: 712 self.writeString(key, False) 713 self.writeElement(sa[key]) 714 715 if da: 716 for key, value in da.iteritems(): 717 self.writeString(key, False) 718 self.writeElement(value) 719 720 self._writeEndObject()
721
722 - def writeDate(self, d):
723 """ 724 Writes a date to the data stream. 725 726 @type d: Instance of C{datetime.datetime} 727 @param d: The date to be encoded to the AMF0 data stream. 728 """ 729 if isinstance(d, datetime.time): 730 raise pyamf.EncodeError('A datetime.time instance was found but ' 731 'AMF0 has no way to encode time objects. Please use ' 732 'datetime.datetime instead (got:%r)' % (d,)) 733 734 # According to the Red5 implementation of AMF0, dates references are 735 # created, but not used. 736 if self.timezone_offset is not None: 737 d -= self.timezone_offset 738 739 secs = util.get_timestamp(d) 740 tz = 0 741 742 self.writeType(TYPE_DATE) 743 self.stream.write_double(secs * 1000.0) 744 self.stream.write_short(tz)
745
746 - def writeXML(self, e):
747 """ 748 Write XML to the data stream. 749 750 @type e: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 751 @param e: The XML data to be encoded to the AMF0 data stream. 752 """ 753 if self.use_amf3 is True: 754 self.writeAMF3(e) 755 756 return 757 758 self.writeType(TYPE_XML) 759 760 data = util.ET.tostring(e, 'utf-8') 761 self.stream.write_ulong(len(data)) 762 self.stream.write(data)
763
764 - def writeAMF3(self, data):
765 """ 766 Writes an element to the datastream in L{AMF3<pyamf.amf3>} format. 767 768 @type data: C{mixed} 769 @param data: The data to be encoded to the AMF0 data stream. 770 """ 771 if not hasattr(self.context, 'amf3_context'): 772 self.context.amf3_context = pyamf.get_context(pyamf.AMF3, exceptions=False) 773 774 if not hasattr(self.context, 'amf3_encoder'): 775 self.context.amf3_encoder = pyamf.get_encoder( 776 pyamf.AMF3, self.stream, self.context.amf3_context) 777 778 self.context.addAMF3Object(data) 779 encoder = self.context.amf3_encoder 780 781 self.writeType(TYPE_AMF3) 782 encoder.writeElement(data)
783 784
785 -def decode(*args, **kwargs):
786 """ 787 A helper function to decode an AMF0 datastream. 788 """ 789 decoder = Decoder(*args, **kwargs) 790 791 while 1: 792 try: 793 yield decoder.readElement() 794 except pyamf.EOStream: 795 break
796 797
798 -def encode(*args, **kwargs):
799 """ 800 A helper function to encode an element into the AMF0 format. 801 802 @type element: C{mixed} 803 @keyword element: The element to encode 804 @type context: L{Context<pyamf.amf0.Context>} 805 @keyword context: AMF0 C{Context} to use for the encoding. This holds 806 previously referenced objects etc. 807 @rtype: C{StringIO} 808 @return: The encoded stream. 809 """ 810 encoder = Encoder(**kwargs) 811 812 for element in args: 813 encoder.writeElement(element) 814 815 return encoder.stream
816 817
818 -class RecordSet(object):
819 """ 820 I represent the C{RecordSet} class used in Adobe Flash Remoting to hold 821 (amongst other things) SQL records. 822 823 @ivar columns: The columns to send. 824 @type columns: List of strings. 825 @ivar items: The C{RecordSet} data. 826 @type items: List of lists, the order of the data corresponds to the order 827 of the columns. 828 @ivar service: Service linked to the C{RecordSet}. 829 @type service: 830 @ivar id: The id of the C{RecordSet}. 831 @type id: C{str} 832 833 @see: U{RecordSet on OSFlash (external) 834 <http://osflash.org/documentation/amf/recordset>} 835 """ 836
837 - class __amf__:
838 alias = 'RecordSet' 839 static = ('serverInfo',) 840 dynamic = False
841
842 - def __init__(self, columns=[], items=[], service=None, id=None):
843 self.columns = columns 844 self.items = items 845 self.service = service 846 self.id = id
847
848 - def _get_server_info(self):
849 ret = pyamf.ASObject(totalCount=len(self.items), cursor=1, version=1, 850 initialData=self.items, columnNames=self.columns) 851 852 if self.service is not None: 853 ret.update({'serviceName': str(self.service['name'])}) 854 855 if self.id is not None: 856 ret.update({'id':str(self.id)}) 857 858 return ret
859
860 - def _set_server_info(self, val):
861 self.columns = val['columnNames'] 862 self.items = val['initialData'] 863 864 try: 865 # TODO nick: find relevant service and link in here. 866 self.service = dict(name=val['serviceName']) 867 except KeyError: 868 self.service = None 869 870 try: 871 self.id = val['id'] 872 except KeyError: 873 self.id = None
874 875 serverInfo = property(_get_server_info, _set_server_info) 876
877 - def __repr__(self):
878 ret = '<%s.%s object' % (self.__module__, self.__class__.__name__) 879 880 if self.id is not None: 881 ret += ' id=%s' % self.id 882 883 if self.service is not None: 884 ret += ' service=%s' % self.service 885 886 ret += ' at 0x%x>' % id(self) 887 888 return ret
889 890 pyamf.register_class(RecordSet) 891 892
893 -def _check_for_int(x):
894 """ 895 This is a compatibility function that takes a C{float} and converts it to an 896 C{int} if the values are equal. 897 """ 898 try: 899 y = int(x) 900 except (OverflowError, ValueError): 901 pass 902 else: 903 # There is no way in AMF0 to distinguish between integers and floats 904 if x == x and y == x: 905 return y 906 907 return x
908 909 # check for some Python 2.3 problems with floats 910 try: 911 float('nan') 912 except ValueError: 913 pass 914 else: 915 if float('nan') == 0:
916 - def check_nan(func):
917 def f2(x): 918 if str(x).lower().find('nan') >= 0: 919 return x 920 921 return f2.func(x)
922 f2.func = func 923 924 return f2 925 926 _check_for_int = check_nan(_check_for_int) 927