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 = 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
468 raise NotImplementedError
469
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
490 try:
491 while 1:
492 yield self.readElement()
493 except EOFError:
494 raise StopIteration
495
497 """
498 Custom type mappings.
499 """
501 self.encoder = encoder
502 self.func = func
503
506
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
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
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
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
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
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
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
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
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
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
736 try:
737 return CLASS_CACHE[alias]
738 except KeyError:
739 pass
740
741
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
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
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
778 raise UnknownClassAlias, "Unknown alias %s" % alias
779
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
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
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
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
873
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
899
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
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
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
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
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
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
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