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

Source Code for Module pyamf.amf0

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