Package pyamf
[hide private]
[frames] | no frames]

Source Code for Package pyamf

   1  # Copyright (c) 2007-2008 The PyAMF Project. 
   2  # See LICENSE for details. 
   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  #: PyAMF version number. 
  36  __version__ = (0, 3, 1) 
  37   
  38  #: Class mapping support. 
  39  CLASS_CACHE = {} 
  40  #: Class loaders. 
  41  CLASS_LOADERS = [] 
  42   
  43  #: Custom type map. 
  44  TYPE_MAP = {} 
  45   
  46  #: Maps error classes to string codes. 
  47  ERROR_CLASS_MAP = {} 
  48   
  49  #: Specifies that objects are serialized using AMF for ActionScript 1.0 and 2.0. 
  50  AMF0 = 0 
  51  #: Specifies that objects are serialized using AMF for ActionScript 3.0. 
  52  AMF3 = 3 
  53  #: Supported AMF encoding types. 
  54  ENCODING_TYPES = (AMF0, AMF3) 
  55   
56 -class ClientTypes:
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 #: Specifies a Flash Player 6.0 - 8.0 client. 66 Flash6 = 0 67 #: Specifies a FlashCom / Flash Media Server client. 68 FlashCom = 1 69 #: Specifies a Flash Player 9.0 client. 70 Flash9 = 3
71 72 #: List of AMF client typecodes. 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 #: Represents the C{undefined} value in a Flash client. 81 Undefined = object() 82
83 -class BaseError(Exception):
84 """ 85 Base AMF Error. 86 87 All AMF related errors should be subclassed from this class. 88 """
89
90 -class DecodeError(BaseError):
91 """ 92 Raised if there is an error in decoding an AMF data stream. 93 """
94
95 -class EOStream(BaseError):
96 """ 97 Raised if the data stream has come to a natural end. 98 """
99
100 -class ReferenceError(BaseError):
101 """ 102 Raised if an AMF data stream refers to a non-existent object 103 or string reference. 104 """
105
106 -class EncodeError(BaseError):
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
115 -class UnknownClassAlias(BaseError):
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):
128 self.clear()
129
130 - def clear(self):
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
190 -class ASObject(dict):
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 """
196 - def __init__(self, *args, **kwargs):
197 dict.__init__(self, *args, **kwargs)
198
199 - def __getattr__(self, k):
200 try: 201 return self[k] 202 except KeyError: 203 raise AttributeError, 'unknown attribute \'%s\'' % k
204
205 - def __setattr__(self, k, v):
206 self[k] = v
207
208 - def __repr__(self):
209 return dict.__repr__(self)
210
211 -class MixedArray(dict):
212 """ 213 Used to be able to specify the C{mixedarray} type. 214 """
215
216 -class ClassMetaData(list):
217 """ 218 I hold a list of tags relating to the class. The idea behind this is 219 to emulate the metadata tags you can supply to ActionScript, 220 e.g. static/dynamic. 221 """ 222 _allowed_tags = ( 223 ('static', 'dynamic', 'external'), 224 ('amf3', 'amf0'), 225 ('anonymous',), 226 ) 227
228 - def __init__(self, *args):
229 if len(args) == 1 and hasattr(args[0], '__iter__'): 230 for x in args[0]: 231 self.append(x) 232 else: 233 for x in args: 234 self.append(x)
235
236 - def _is_tag_allowed(self, x):
237 for y in self._allowed_tags: 238 if isinstance(y, (types.ListType, types.TupleType)): 239 if x in y: 240 return (True, y) 241 else: 242 if x == y: 243 return (True, None) 244 245 return (False, None)
246
247 - def append(self, x):
248 """ 249 Adds a tag to the metadata. 250 251 @param x: Tag. 252 253 @raise ValueError: Unknown tag. 254 """ 255 x = str(x).lower() 256 257 allowed, tags = self._is_tag_allowed(x) 258 259 if not allowed: 260 raise ValueError("Unknown tag %s" % x) 261 262 if tags is not None: 263 # check to see if a tag in the list is about to be clobbered if so, 264 # raise a warning 265 for y in tags: 266 if y in self: 267 if x != y: 268 import warnings 269 270 warnings.warn( 271 "Previously defined tag %s superceded by %s" % ( 272 y, x)) 273 274 list.pop(self, self.index(y)) 275 break 276 277 list.append(self, x)
278
279 - def __contains__(self, other):
280 return list.__contains__(self, str(other).lower())
281 282 # TODO nick: deal with slices 283
284 -class ClassAlias(object):
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 # class is declared as external, lets check 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
354 - def __call__(self, *args, **kwargs):
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: # new-style class 362 return self.klass.__new__(self.klass) 363 elif type(self.klass) is types.ClassType: # classic class 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
370 - def __str__(self):
371 return self.alias
372
373 - def __repr__(self):
374 return '<ClassAlias alias=%s klass=%s @ %s>' % ( 375 self.alias, self.klass, id(self))
376
377 - def __eq__(self, other):
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
387 - def __hash__(self):
388 return id(self)
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
430 -class BaseDecoder(object):
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 # coerce data to BufferedByteStream 454 if isinstance(data, util.BufferedByteStream): 455 self.stream = data 456 else: 457 self.stream = util.BufferedByteStream(data) 458 459 if context == None: 460 self.context = self.context_class() 461 elif isinstance(context, self.context_class): 462 self.context = context 463 else: 464 raise TypeError, "context must be of type %s.%s" % ( 465 self.context_class.__module__, self.context_class.__name__)
466
467 - def readType(self):
468 raise NotImplementedError
469
470 - def readElement(self):
471 """ 472 Reads an AMF3 element from the data stream. 473 474 @raise DecodeError: The ActionScript type is unsupported. 475 @raise EOStream: No more data left to decode. 476 """ 477 try: 478 type = self.readType() 479 except EOFError: 480 raise EOStream 481 482 try: 483 func = getattr(self, self.type_map[type]) 484 except KeyError, e: 485 raise DecodeError, "Unsupported ActionScript type 0x%02x" % type 486 487 return func()
488
489 - def __iter__(self):
490 try: 491 while 1: 492 yield self.readElement() 493 except EOFError: 494 raise StopIteration
495
496 -class CustomTypeFunc(object):
497 """ 498 Custom type mappings. 499 """
500 - def __init__(self, encoder, func):
501 self.encoder = encoder 502 self.func = func
503
504 - def __call__(self, data):
505 self.encoder.writeElement(self.func(data))
506
507 -class BaseEncoder(object):
508 """ 509 Base AMF encoder. 510 511 @ivar type_map: A list of types -> functions. The types is a list of 512 possible instances or functions to call (that return a C{bool}) to 513 determine the correct function to call to encode the data. 514 @type type_map: C{list} 515 @ivar context_class: Holds the class that will create context objects for 516 the implementing C{Encoder}. 517 @type context_class: C{type} or C{types.ClassType} 518 @ivar stream: The underlying data stream. 519 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 520 @ivar context: The context for the encoding. 521 @type context: An instance of C{BaseEncoder.context_class} 522 """ 523 context_class = BaseContext 524 type_map = [] 525
526 - def __init__(self, data=None, context=None):
527 """ 528 @type data: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 529 @param data: Data stream. 530 @type context: L{Context<pyamf.amf0.Context>} 531 @param context: Context. 532 @raise TypeError: The C{context} parameter must be of type 533 L{Context<pyamf.amf0.Context>}. 534 """ 535 # coerce data to BufferedByteStream 536 if isinstance(data, util.BufferedByteStream): 537 self.stream = data 538 else: 539 self.stream = util.BufferedByteStream(data) 540 541 if context == None: 542 self.context = self.context_class() 543 elif isinstance(context, self.context_class): 544 self.context = context 545 else: 546 raise TypeError, "context must be of type %s.%s" % ( 547 self.context_class.__module__, self.context_class.__name__) 548 549 self._write_elem_func_cache = {}
550
551 - def _getWriteElementFunc(self, data):
552 """ 553 Gets a function used to encode the data. 554 555 @type data: C{mixed} 556 @param data: Python data. 557 @rtype: callable or C{None}. 558 @return: The function used to encode data to the stream. 559 """ 560 for type_, func in TYPE_MAP.iteritems(): 561 try: 562 if isinstance(data, type_): 563 return CustomTypeFunc(self, func) 564 except TypeError: 565 if callable(type_) and type_(data): 566 return CustomTypeFunc(self, func) 567 568 for tlist, method in self.type_map: 569 for t in tlist: 570 try: 571 if isinstance(data, t): 572 return getattr(self, method) 573 except TypeError: 574 if callable(t) and t(data): 575 return getattr(self, method) 576 577 return None
578
579 - def _writeElementFunc(self, data):
580 """ 581 Gets a function used to encode the data. 582 583 @type data: C{mixed} 584 @param data: Python data. 585 @rtype: callable or C{None}. 586 @return: The function used to encode data to the stream. 587 """ 588 try: 589 key = data.__class__ 590 except AttributeError: 591 return self._getWriteElementFunc(data) 592 593 if key not in self._write_elem_func_cache: 594 self._write_elem_func_cache[key] = self._getWriteElementFunc(data) 595 596 return self._write_elem_func_cache[key]
597
598 - def writeElement(self, data):
599 """ 600 Writes the data. 601 602 @type data: C{mixed} 603 @param data: The data to be encoded to the data stream. 604 """ 605 raise NotImplementedError
606
607 -def register_class(klass, alias=None, attrs=None, attr_func=None, metadata=[]):
608 """ 609 Registers a class to be used in the data streaming. 610 611 @type alias: C{str} 612 @param alias: The alias of klass, i.e. C{org.example.Person}. 613 @param attrs: A list of attributes that will be encoded for the class. 614 @type attrs: C{list} or C{None} 615 @type attr_func: 616 @param attr_func: 617 @type metadata: 618 @param metadata: 619 @raise TypeError: The C{klass} is not callable. 620 @raise ValueError: The C{klass} or C{alias} is already registered. 621 @return: The registered L{ClassAlias}. 622 """ 623 if not callable(klass): 624 raise TypeError, "klass must be callable" 625 626 if klass in CLASS_CACHE: 627 raise ValueError, "klass %s already registered" % klass 628 629 if alias is not None and alias in CLASS_CACHE.keys(): 630 raise ValueError, "alias '%s' already registered" % alias 631 632 x = ClassAlias(klass, alias, attr_func=attr_func, attrs=attrs, 633 metadata=metadata) 634 635 if alias is None: 636 alias = "%s.%s" % (klass.__module__, klass.__name__,) 637 638 CLASS_CACHE[alias] = x 639 640 return x
641
642 -def unregister_class(alias):
643 """ 644 Deletes a class from the cache. 645 646 If C{alias} is a class, the matching alias is found. 647 648 @type alias: C{class} or C{str} 649 @param alias: Alias for class to delete. 650 @raise UnknownClassAlias: Unknown alias. 651 """ 652 if isinstance(alias, (type, types.ClassType)): 653 for s, a in CLASS_CACHE.iteritems(): 654 if a.klass == alias: 655 alias = s 656 break 657 try: 658 del CLASS_CACHE[alias] 659 except KeyError: 660 raise UnknownClassAlias, "Unknown alias %s" % alias
661
662 -def register_class_loader(loader):
663 """ 664 Registers a loader that is called to provide the C{Class} for a specific 665 alias. 666 667 The L{loader} is provided with one argument, the C{Class} alias. If the 668 loader succeeds in finding a suitable class then it should return that 669 class, otherwise it should return C{None}. 670 671 @type loader: C{callable} 672 @raise TypeError: The C{loader} is not callable. 673 @raise ValueError: The C{loader} is already registered. 674 """ 675 if not callable(loader): 676 raise TypeError, "loader must be callable" 677 678 if loader in CLASS_LOADERS: 679 raise ValueError, "loader has already been registered" 680 681 CLASS_LOADERS.append(loader)
682
683 -def unregister_class_loader(loader):
684 """ 685 Unregisters a class loader. 686 687 @type loader: C{callable} 688 @param loader: The object to be unregistered 689 690 @raise LookupError: The C{loader} was not registered. 691 """ 692 if loader not in CLASS_LOADERS: 693 raise LookupError, "loader not found" 694 695 del CLASS_LOADERS[CLASS_LOADERS.index(loader)]
696
697 -def get_module(mod_name):
698 """ 699 Load a module based on C{mod_name}. 700 701 @type mod_name: C{str} 702 @param mod_name: The module name. 703 @return: Module. 704 705 @raise ImportError: Unable to import empty module. 706 """ 707 if mod_name is '': 708 raise ImportError, "Unable to import empty module" 709 710 mod = __import__(mod_name) 711 components = mod_name.split('.') 712 713 for comp in components[1:]: 714 mod = getattr(mod, comp) 715 716 return mod
717
718 -def load_class(alias):
719 """ 720 Finds the class registered to the alias. 721 722 The search is done in order: 723 1. Checks if the class name has been registered via L{register_class}. 724 2. Checks all functions registered via L{register_class_loader}. 725 3. Attempts to load the class via standard module loading techniques. 726 727 @type alias: C{str} 728 @param alias: The class name. 729 @raise UnknownClassAlias: The C{alias} was not found. 730 @raise TypeError: Expecting class type or L{ClassAlias} from loader. 731 @return: Class registered to the alias. 732 """ 733 alias = str(alias) 734 735 # Try the CLASS_CACHE first 736 try: 737 return CLASS_CACHE[alias] 738 except KeyError: 739 pass 740 741 # Check each CLASS_LOADERS in turn 742 for loader in CLASS_LOADERS: 743 klass = loader(alias) 744 745 if klass is None: 746 continue 747 748 if isinstance(klass, (type, types.ClassType)): 749 return register_class(klass, alias) 750 elif isinstance(klass, ClassAlias): 751 CLASS_CACHE[str(alias)] = klass 752 else: 753 raise TypeError, "Expecting class type or ClassAlias from loader" 754 755 return klass 756 757 # XXX nick: Are there security concerns for loading classes this way? 758 mod_class = alias.split('.') 759 760 if mod_class: 761 module = '.'.join(mod_class[:-1]) 762 klass = mod_class[-1] 763 764 try: 765 module = get_module(module) 766 except ImportError, AttributeError: 767 # XXX What to do here? 768 pass 769 else: 770 klass = getattr(module, klass) 771 772 if callable(klass): 773 CLASS_CACHE[alias] = klass 774 775 return klass 776 777 # All available methods for finding the class have been exhausted 778 raise UnknownClassAlias, "Unknown alias %s" % alias
779
780 -def get_class_alias(klass):
781 """ 782 Finds the alias registered to the class. 783 784 @type klass: C{object} or class 785 @raise UnknownClassAlias: Class not found. 786 @raise TypeError: Expecting C{string} or C{class} type. 787 788 @rtype: L{ClassAlias} 789 @return: The class alias linked to the C{klass}. 790 """ 791 if not isinstance(klass, (type, types.ClassType, basestring)): 792 if isinstance(klass, types.InstanceType): 793 klass = klass.__class__ 794 elif isinstance(klass, types.ObjectType): 795 klass = type(klass) 796 797 if isinstance(klass, basestring): 798 for a, k in CLASS_CACHE.iteritems(): 799 if klass == a: 800 return k 801 else: 802 for a, k in CLASS_CACHE.iteritems(): 803 if klass == k.klass: 804 return k 805 806 if isinstance(klass, basestring): 807 return load_class(klass) 808 809 raise UnknownClassAlias, "Unknown alias %s" % klass
810
811 -def has_alias(obj):
812 """ 813 @rtype: C{bool} 814 @return: Alias is available. 815 """ 816 try: 817 alias = get_class_alias(obj) 818 return True 819 except UnknownClassAlias: 820 return False
821
822 -def decode(stream, encoding=AMF0, context=None):
823 """ 824 A generator function to decode a datastream. 825 826 @type stream: L{BufferedByteStream<pyamf.util.BufferedByteStream>} 827 @param stream: AMF data. 828 @type encoding: C{int} 829 @param encoding: AMF encoding type. 830 @type context: L{AMF0 Context<pyamf.amf0.Context>} or 831 L{AMF3 Context<pyamf.amf3.Context>} 832 @param context: Context. 833 @return: Each element in the stream. 834 """ 835 decoder = _get_decoder_class(encoding)(stream, context) 836 837 while 1: 838 try: 839 yield decoder.readElement() 840 except EOStream: 841 break
842
843 -def encode(*args, **kwargs):
844 """ 845 A helper function to encode an element. 846 847 @type element: C{mixed} 848 @keyword element: Python data. 849 @type encoding: C{int} 850 @keyword encoding: AMF encoding type. 851 @type context: L{amf0.Context<pyamf.amf0.Context>} or 852 L{amf3.Context<pyamf.amf3.Context>} 853 @keyword context: Context. 854 855 @rtype: C{StringIO} 856 @return: File-like object. 857 """ 858 encoding = kwargs.get('encoding', AMF0) 859 context = kwargs.get('context', None) 860 861 stream = util.BufferedByteStream() 862 encoder = _get_encoder_class(encoding)(stream, context) 863 864 for el in args: 865 encoder.writeElement(el) 866 867 stream.seek(0) 868 869 return stream
870
871 -def get_decoder(encoding, data=None, context=None):
872 return _get_decoder_class(encoding)(data=data, context=context)
873
874 -def _get_decoder_class(encoding):
875 """ 876 Get compatible decoder. 877 878 @type encoding: C{int} 879 @param encoding: AMF encoding version. 880 @raise ValueError: AMF encoding version is unknown. 881 882 @rtype: L{amf0.Decoder<pyamf.amf0.Decoder>} or 883 L{amf3.Decoder<pyamf.amf3.Decoder>} 884 @return: AMF0 or AMF3 decoder. 885 """ 886 if encoding == AMF0: 887 import amf0 888 889 return amf0.Decoder 890 elif encoding == AMF3: 891 import amf3 892 893 return amf3.Decoder 894 895 raise ValueError, "Unknown encoding %s" % encoding
896
897 -def get_encoder(encoding, data=None, context=None):
898 return _get_encoder_class(encoding)(data=data, context=context)
899
900 -def _get_encoder_class(encoding):
901 """ 902 Get compatible encoder. 903 904 @type encoding: C{int} 905 @param encoding: AMF encoding version. 906 @raise ValueError: AMF encoding version is unknown. 907 908 @rtype: L{amf0.Encoder<pyamf.amf0.Encoder>} or 909 L{amf3.Encoder<pyamf.amf3.Encoder>} 910 @return: AMF0 or AMF3 encoder. 911 """ 912 if encoding == AMF0: 913 import amf0 914 915 return amf0.Encoder 916 elif encoding == AMF3: 917 import amf3 918 919 return amf3.Encoder 920 921 raise ValueError, "Unknown encoding %s" % encoding
922
923 -def get_context(encoding):
924 return _get_context_class(encoding)()
925
926 -def _get_context_class(encoding):
927 """ 928 Gets a compatible context class. 929 930 @type encoding: C{int} 931 @param encoding: AMF encoding version 932 @raise ValueError: AMF encoding version is unknown. 933 934 @rtype: L{amf0.Context<pyamf.amf0.Context>} or 935 L{amf3.Context<pyamf.amf3.Context>} 936 @return: AMF0 or AMF3 context class. 937 """ 938 if encoding == AMF0: 939 import amf0 940 941 return amf0.Context 942 elif encoding == AMF3: 943 import amf3 944 945 return amf3.Context 946 947 raise ValueError, "Unknown encoding %s" % encoding
948
949 -def flex_loader(alias):
950 """ 951 Loader for L{Flex<pyamf.flex>} framework compatibility classes. 952 953 @raise UnknownClassAlias: Trying to load unknown Flex compatibility class. 954 """ 955 if not alias.startswith('flex.'): 956 return 957 958 try: 959 if alias.startswith('flex.messaging.messages'): 960 import pyamf.flex.messaging 961 elif alias.startswith('flex.messaging.io'): 962 import pyamf.flex 963 elif alias.startswith('flex.data.messages'): 964 import pyamf.flex.data 965 966 return CLASS_CACHE[alias] 967 except KeyError: 968 raise UnknownClassAlias, alias
969 970 register_class_loader(flex_loader) 971
972 -def add_type(type_, func=None):
973 """ 974 Adds a custom type to L{TYPE_MAP}. A custom type allows fine grain control 975 of what to encode to an AMF data stream. 976 977 @raise TypeError: Unable to add as a custom type (expected a class or callable). 978 @raise KeyError: Type already exists. 979 """ 980 def _check_type(type_): 981 if not (isinstance(type_, (type, types.ClassType)) or callable(type_)): 982 raise TypeError, "Unable to add '%r' as a custom type (expected a class or callable)" % type_
983 984 if isinstance(type_, list): 985 type_ = tuple(type_) 986 987 if type_ in TYPE_MAP: 988 raise KeyError, 'Type %r already exists' % type_ 989 990 if isinstance(type_, types.TupleType): 991 for x in type_: 992 _check_type(x) 993 else: 994 _check_type(type_) 995 996 TYPE_MAP[type_] = func 997
998 -def get_type(type_):
999 """ 1000 Gets the declaration for the corresponding custom type. 1001 1002 @raise KeyError: Unknown type. 1003 """ 1004 if isinstance(type_, list): 1005 type_ = tuple(type_) 1006 1007 for (k, v) in TYPE_MAP.iteritems(): 1008 if k == type_: 1009 return v 1010 1011 raise KeyError, "Unknown type %r" % type_
1012
1013 -def remove_type(type_):
1014 """ 1015 Removes the custom type declaration. 1016 1017 @return: Custom type declaration. 1018 """ 1019 declaration = get_type(type_) 1020 1021 del TYPE_MAP[type_] 1022 1023 return declaration
1024
1025 -def add_error_class(klass, code):
1026 """ 1027 Maps an exception class to a string code. Used to map remoting C{onStatus} 1028 objects to an exception class so that an exception can be built to 1029 represent that error:: 1030 1031 class AuthenticationError(Exception): 1032 pass 1033 1034 An example: C{add_error_class(AuthenticationError, 'Auth.Failed')} 1035 1036 @type code: C{str} 1037 1038 @raise TypeError: C{klass} must be a C{class} type. 1039 @raise TypeError: Error classes must subclass the C{__builtin__.Exception} class. 1040 @raise ValueError: Code is already registered. 1041 """ 1042 if not isinstance(code, basestring): 1043 code = str(code) 1044 1045 if not isinstance(klass, (type, types.ClassType)): 1046 raise TypeError, "klass must be a class type" 1047 1048 mro = util.get_mro(klass) 1049 1050 if not Exception in util.get_mro(klass): 1051 raise TypeError, 'error classes must subclass the __builtin__.Exception class' 1052 1053 if code in ERROR_CLASS_MAP.keys(): 1054 raise ValueError, 'Code %s is already registered' % code 1055 1056 ERROR_CLASS_MAP[code] = klass
1057
1058 -def remove_error_class(klass):
1059 """ 1060 Removes a class from C{ERROR_CLASS_MAP}. 1061 1062 @raise ValueError: Code is not registered. 1063 @raise ValueError: Class is not registered. 1064 @raise TypeError: Invalid type, expected C{class} or C{string}. 1065 """ 1066 if isinstance(klass, basestring): 1067 if not klass in ERROR_CLASS_MAP.keys(): 1068 raise ValueError, 'Code %s is not registered' % klass 1069 elif isinstance(klass, (type, types.ClassType)): 1070 classes = ERROR_CLASS_MAP.values() 1071 if not klass in classes: 1072 raise ValueError, 'Class %s is not registered' % klass 1073 1074 klass = ERROR_CLASS_MAP.keys()[classes.index(klass)] 1075 else: 1076 raise TypeError, "Invalid type, expected class or string" 1077 1078 del ERROR_CLASS_MAP[klass]
1079 1080 register_adapters() 1081