def mutate(self,obj): if isinstance(obj,UnicodeType): # unicode strings are required to be placed in the body # (by our encoding scheme) self.in_body = 1 else: # XXX really should check getInBody(), but we'd have # to do isinstance() for each type ... maybe do later self.in_body = 0 if isInstanceLike(obj) or hasPickleFuncs(obj): # obj has data items (list,dict,tuple) *AND* attributes. # mutate to an oldstyle object, turning the data items into # a special attribute (eg. __items__, __entries__). # # also, if obj defines the special pickling functions, we treat # it as an instance so we don't have to duplicate all the # protocol logic here. return XMLP_Mutated(newinst_to_oldinst(obj)) else: # obj has only data items (list,dict,tuple,etc.) # convert to the raw datatype and remember the # module.class of obj for unpickling. (o,t) = newdata_to_olddata(obj) return XMLP_Mutated(o,t)
def mutate(self, obj): if isinstance(obj, UnicodeType): # unicode strings are required to be placed in the body # (by our encoding scheme) self.in_body = 1 else: # XXX really should check getInBody(), but we'd have # to do isinstance() for each type ... maybe do later self.in_body = 0 if isInstanceLike(obj) or hasPickleFuncs(obj): # obj has data items (list,dict,tuple) *AND* attributes. # mutate to an oldstyle object, turning the data items into # a special attribute (eg. __items__, __entries__). # # also, if obj defines the special pickling functions, we treat # it as an instance so we don't have to duplicate all the # protocol logic here. return XMLP_Mutated(newinst_to_oldinst(obj)) else: # obj has only data items (list,dict,tuple,etc.) # convert to the raw datatype and remember the # module.class of obj for unpickling. (o, t) = newdata_to_olddata(obj) return XMLP_Mutated(o, t)
def __init__(self, py_obj=None): if py_obj is not None: #if type(py_obj) is InstanceType: if isInstanceLike(py_obj): self.to_pickle = py_obj else: raise XMLPicklingError, \ "XML_Pickler must be initialized with Instance (or None)"
def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): tag_body = [] (mtag, thing, in_body, mextra) = try_mutate(orig_thing, None, getInBody(type(orig_thing)), None) if type(thing) is NoneType: start_tag += "%s />\n" % (_family_type('none', 'None', None, None)) close_tag = '' # looks like bool cannot be used as a base class, so if thing # is a bool it will always be BooleanType, and either True or False elif py_version >= '2.3' and type(thing) is BooleanType: if thing is True: typestr = 'True' else: # must be False typestr = 'False' if in_body: start_tag +='%s>%s' % \ (_family_type('uniq',typestr,mtag,mextra), '') close_tag = close_tag.lstrip() else: start_tag +='%s value="%s" />\n' % \ (_family_type('uniq',typestr,mtag,mextra), '') close_tag = '' # ClassType will get caught by isInstanceLike(), which is not # what we want (also check for new-style class objects) elif isinstance(thing, ClassType) or isNewStyleClass(thing): module = thing.__module__ if module: extra = 'module="%s" class="%s"' % (module, thing.__name__) else: extra = 'class="%s"' % _klass(thing.__name__) start_tag +='%s %s/>\n' % \ (_family_type('lang','class',mtag,mextra),extra) close_tag = '' # have to check for instance-like next since ints, etc., can be # instance-like in Python 2.2+. if it's really an object, we don't # want to fall through to the regular int,float,etc. code, since # that would skip the special handling in pickle_instance(). elif isInstanceLike(thing): module = _module(thing) if module: extra = 'module="%s" class="%s"' % (module, _klass(thing)) else: extra = 'class="%s"' % _klass(thing) start_tag, do_copy = \ _tag_compound(start_tag,_family_type('obj','PyObject',mtag,mextra), orig_thing, deepcopy, extra) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: pickle_instance(thing, tag_body, level + 1, deepcopy) else: close_tag = '' elif isinstance_any(thing, (IntType, LongType, FloatType, ComplexType)): #thing_str = repr(thing) thing_str = ntoa(thing) if in_body: # we don't call safe_content() here since numerics won't # contain special XML chars. # the unpickler can either call unsafe_content() or not, # it won't matter start_tag +='%s>%s' % \ (_family_type('atom','numeric',mtag,mextra), thing_str) close_tag = close_tag.lstrip() else: start_tag +='%s value="%s" />\n' % \ (_family_type('atom','numeric',mtag,mextra),thing_str) close_tag = '' elif isinstance_any(thing, (StringType, UnicodeType)): if in_body: start_tag +='%s>%s' % \ (_family_type('atom','string',mtag,mextra), safe_content(thing)) close_tag = close_tag.lstrip() else: start_tag +='%s value="%s" />\n' % \ (_family_type('atom','string',mtag,mextra), safe_string(thing)) close_tag = '' # General notes: # 1. When we make references, set type to referenced object # type -- we don't need type when unpickling, but it may be useful # to someone reading the XML file # 2. For containers, we have to stick the container into visited{} # before pickling subitems, in case it contains self-references # (we CANNOT just move the visited{} update to the top of this # function, since that would screw up every _family_type() call) elif type(thing) is TupleType: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('seq','tuple',mtag,mextra), orig_thing,deepcopy) if do_copy: for item in thing: tag_body.append(_item_tag(item, level + 1, deepcopy)) else: close_tag = '' elif type(thing) is ListType: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('seq','list',mtag,mextra), orig_thing,deepcopy) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: for item in thing: tag_body.append(_item_tag(item, level + 1, deepcopy)) else: close_tag = '' elif type(thing) in [DictType]: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('map','dict',mtag,mextra), orig_thing,deepcopy) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: for key, val in thing.items(): tag_body.append(_entry_tag(key, val, level + 1, deepcopy)) else: close_tag = '' elif type(thing) in [FunctionType, BuiltinFunctionType]: info = get_function_info(thing) # use module/class tags -- not perfect semantically, but better # that creating new attr names start_tag +='%s module="%s" class="%s"/>\n' % \ (_family_type('lang','function',mtag,mextra), info[0],info[1]) close_tag = '' else: # try using pickled value as the XML value tag. # rationale: it won't be (easily) editable, but at least # you'll get valid XML even if your classes happen to # contain a few "foreign" types, and you don't feel like # writing a helper object (see gnosis.xml.pickle.ext for # how to do that) try: # we can't lookup the helper by type, since rawpickle can pickle # any pickleable class, so lookup by tag (unmutator) instead # (mutator & unmutator are always the same object) # always put rawpickles in the element body mutator = get_unmutator('rawpickle', None) thing = safe_content(mutator.mutate(thing).obj) start_tag += '%s>%s' % (_family_type('atom', None, 'rawpickle', None), thing) close_tag = close_tag.lstrip() except: raise XMLPicklingError, "non-handled type %s" % type(thing) # need to keep a ref to the object for two reasons - # 1. we can ref it later instead of copying it into the XML stream # 2. need to keep temporary objects around so their ids don't get reused # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we # never look at them if not deepcopy: visited[id(orig_thing)] = orig_thing return start_tag + ''.join(tag_body) + close_tag
def _pickle_toplevel_obj(xml_list, py_obj, deepcopy): "handle the top object -- add XML header, etc." # Store the ref id to the pickling object (if not deepcopying) global visited visited = {} if not deepcopy: id_ = id(py_obj) visited[id_] = py_obj # note -- setting family="obj" lets us know that a mutator was used on # the object. Otherwise, it's tricky to unpickle both <PyObject ...> # and <.. type="PyObject" ..> with the same code. Having family="obj" makes # it clear that we should slurp in a 'typeless' object and unmutate it. # note 2 -- need to add type= to <PyObject> when using mutators. # this is b/c a mutated object can still have a class= and # module= that we need to read before unmutating (i.e. the mutator # mutated into a PyObject) famtype = '' # unless we have to, don't add family= and type= #if type(py_obj) is not InstanceType: if not isInstanceLike(py_obj): # use our wrapper-mutator to pickle builtin types. # get by name since mutator classtype is None mutator = get_unmutator('builtin_wrapper', None) # wrapper must not have an id (otherwise the wrapper AND the # wrapper-obj would appear to have the same id) if not deepcopy: del visited[id_] id_ = None py_obj = mutator.mutate(py_obj).obj famtype += 'family="obj" type="%s" ' % mutator.tag # don't show module for wrapped types module = None # refs get funny here, but since this is a special case, and we're # only pickling a single object, turning deepcopy off is easiest #deepcopy = 1 else: if can_mutate(py_obj): (mtype, py_obj, in_body, extra) = mutate(py_obj) # sanity check until/if we eventually support these # at the toplevel if in_body or extra: raise XMLPicklingError, \ "Sorry, mutators can't set in_body and/or extra at the toplevel." famtype += 'family="obj" type="%s" ' % mtype module = _module(py_obj) klass_tag = _klass(py_obj) # Generate the XML string if module: extra = '%smodule="%s" class="%s"' % (famtype, module, klass_tag) else: extra = '%s class="%s"' % (famtype, klass_tag) xml_list.append('<?xml version="1.0"?>\n' + '<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">\n') if deepcopy: xml_list.append('<PyObject %s>\n' % (extra)) elif id_ is not None: xml_list.append('<PyObject %s id="%s">\n' % (extra, id_)) else: xml_list.append('<PyObject %s>\n' % (extra)) pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy) xml_list.append('</PyObject>\n') # returns None if xml_list is a fileobj, but caller should # know that (or not care) return xml_list.getvalue()
def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): tag_body = [] (mtag,thing,in_body,mextra) = try_mutate(orig_thing,None, getInBody(type(orig_thing)), None) if type(thing) is NoneType: start_tag = start_tag + "%s />\n" % (_family_type('none','None',None,None)) close_tag = '' # bool cannot be used as a base class (see sanity check above) so if thing # is a bool it will always be BooleanType, and either True or False elif Have_BoolClass and type(thing) is BooleanType: if thing is True: typestr = 'True' else: # must be False typestr = 'False' if in_body: start_tag = start_tag + '%s>%s' % \ (_family_type('uniq',typestr,mtag,mextra), '') close_tag = close_tag.lstrip() else: start_tag = start_tag + '%s value="%s" />\n' % \ (_family_type('uniq',typestr,mtag,mextra), '') close_tag = '' # ClassType will get caught by isInstanceLike(), which is not # what we want. There are two cases here - the first catches # old-style classes, the second catches new-style classes. elif isinstance(thing,ClassType) or isNewStyleClass(thing): module = thing.__module__ if module: extra = 'module="%s" class="%s"' % (module, thing.__name__) else: extra = 'class="%s"' % _klass(thing.__name__) start_tag = start_tag + '%s %s/>\n' % \ (_family_type('lang','class',mtag,mextra),extra) close_tag = '' # have to check for instance-like next since ints, etc., can be # instance-like in Python 2.2+. if it's really an object, we don't # want to fall through to the regular int,float,etc. code, since # that would skip the special handling in pickle_instance(). elif isInstanceLike(thing): module = _module(thing) if module: extra = 'module="%s" class="%s"' % (module, _klass(thing)) else: extra = 'class="%s"' % _klass(thing) start_tag, do_copy = \ _tag_compound(start_tag,_family_type('obj','PyObject',mtag,mextra), orig_thing, deepcopy, extra) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: pickle_instance(thing, tag_body, level+1, deepcopy) else: close_tag = '' elif isinstance_any(thing, (IntType, LongType, FloatType, ComplexType)): #thing_str = repr(thing) thing_str = ntoa(thing) if in_body: # we don't call safe_content() here since numerics won't # contain special XML chars. # the unpickler can either call unsafe_content() or not, # it won't matter start_tag = start_tag + '%s>%s' % \ (_family_type('atom','numeric',mtag,mextra), thing_str) close_tag = close_tag.lstrip() else: start_tag = start_tag + '%s value="%s" />\n' % \ (_family_type('atom','numeric',mtag,mextra),thing_str) close_tag = '' elif isinstance_any(thing, (StringType,UnicodeType)): #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # special check for now - this will be fixed in the next major # gnosis release, so I don't care that the code is inline & gross # for now #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX if isinstance(thing,UnicodeType): # can't pickle unicode containing the special "escape" sequence # we use for putting strings in the XML body (they'll be unpickled # as strings, not unicode, if we do!) if thing[0:2] == u'\xbb\xbb' and thing[-2:] == u'\xab\xab': raise Exception("Unpickleable Unicode value. To be fixed in next major Gnosis release.") # see if it contains any XML-illegal values if not is_legal_xml(thing): raise Exception("Unpickleable Unicode value. To be fixed in next major Gnosis release.") if isinstance(thing,StringType) and getInBody(StringType): # technically, this will crash safe_content(), but I prefer to # have the test here for clarity try: # safe_content assumes it can always convert the string # to unicode, which isn't true (eg. pickle a UTF-8 value) u = unicode(thing) except: raise Exception("Unpickleable string value (%s). To be fixed in next major Gnosis release." % repr(thing)) #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # End of temporary hack code #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX if in_body: start_tag = start_tag + '%s>%s' % \ (_family_type('atom','string',mtag,mextra), safe_content(thing)) close_tag = close_tag.lstrip() else: start_tag = start_tag + '%s value="%s" />\n' % \ (_family_type('atom','string',mtag,mextra), safe_string(thing)) close_tag = '' # General notes: # 1. When we make references, set type to referenced object # type -- we don't need type when unpickling, but it may be useful # to someone reading the XML file # 2. For containers, we have to stick the container into visited{} # before pickling subitems, in case it contains self-references # (we CANNOT just move the visited{} update to the top of this # function, since that would screw up every _family_type() call) elif type(thing) is TupleType: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('seq','tuple',mtag,mextra), orig_thing,deepcopy) if do_copy: for item in thing: tag_body.append(_item_tag(item, level+1, deepcopy)) else: close_tag = '' elif type(thing) is ListType: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('seq','list',mtag,mextra), orig_thing,deepcopy) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: for item in thing: tag_body.append(_item_tag(item, level+1, deepcopy)) else: close_tag = '' elif type(thing) in [DictType]: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('map','dict',mtag,mextra), orig_thing,deepcopy) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: for key, val in thing.items(): tag_body.append(_entry_tag(key, val, level+1, deepcopy)) else: close_tag = '' elif type(thing) in [FunctionType,BuiltinFunctionType]: info = get_function_info(thing) # use module/class tags -- not perfect semantically, but better # that creating new attr names start_tag = start_tag + '%s module="%s" class="%s"/>\n' % \ (_family_type('lang','function',mtag,mextra), info[0],info[1]) close_tag = '' else: # try using pickled value as the XML value tag. # rationale: it won't be (easily) editable, but at least # you'll get valid XML even if your classes happen to # contain a few "foreign" types, and you don't feel like # writing a helper object (see gnosis.xml.pickle.ext for # how to do that) try: # we can't lookup the helper by type, since rawpickle can pickle # any pickleable class, so lookup by tag (unmutator) instead # (mutator & unmutator are always the same object) # always put rawpickles in the element body mutator = get_unmutator('rawpickle',None) thing = safe_content(mutator.mutate(thing).obj) start_tag = start_tag + '%s>%s' % (_family_type('atom',None,'rawpickle',None), thing) close_tag = close_tag.lstrip() except: raise XMLPicklingError, "non-handled type %s" % type(thing) # need to keep a ref to the object for two reasons - # 1. we can ref it later instead of copying it into the XML stream # 2. need to keep temporary objects around so their ids don't get reused # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we # never look at them if not deepcopy: visited[id(orig_thing)] = orig_thing return start_tag + ''.join(tag_body) + close_tag
def _pickle_toplevel_obj(xml_list, py_obj, deepcopy): "handle the top object -- add XML header, etc." # Store the ref id to the pickling object (if not deepcopying) global visited visited = {} if not deepcopy: id_ = id(py_obj) visited[id_] = py_obj # note -- setting family="obj" lets us know that a mutator was used on # the object. Otherwise, it's tricky to unpickle both <PyObject ...> # and <.. type="PyObject" ..> with the same code. Having family="obj" makes # it clear that we should slurp in a 'typeless' object and unmutate it. # note 2 -- need to add type= to <PyObject> when using mutators. # this is b/c a mutated object can still have a class= and # module= that we need to read before unmutating (i.e. the mutator # mutated into a PyObject) famtype = '' # unless we have to, don't add family= and type= #if type(py_obj) is not InstanceType: if not isInstanceLike(py_obj): # use our wrapper-mutator to pickle builtin types. # get by name since mutator classtype is None mutator = get_unmutator('builtin_wrapper',None) # wrapper must not have an id (otherwise the wrapper AND the # wrapper-obj would appear to have the same id) if not deepcopy: del visited[id_] id_ = None py_obj = mutator.mutate(py_obj).obj famtype = famtype + 'family="obj" type="%s" ' % mutator.tag # don't show module for wrapped types module = None # refs get funny here, but since this is a special case, and we're # only pickling a single object, turning deepcopy off is easiest #deepcopy = 1 else: if can_mutate(py_obj): (mtype,py_obj,in_body,extra) = mutate(py_obj) # sanity check until/if we eventually support these # at the toplevel if in_body or extra: raise XMLPicklingError, \ "Sorry, mutators can't set in_body and/or extra at the toplevel." famtype = famtype + 'family="obj" type="%s" ' % mtype module = _module(py_obj) klass_tag = _klass(py_obj) # Generate the XML string if module: extra = '%smodule="%s" class="%s"' % (famtype,module,klass_tag) else: extra = '%s class="%s"' % (famtype,klass_tag) xml_list.append('<?xml version="1.0"?>\n'+ '<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">\n') if deepcopy: xml_list.append('<PyObject %s>\n' % (extra)) elif id_ is not None: xml_list.append('<PyObject %s id="%s">\n' % (extra, id_)) else: xml_list.append('<PyObject %s>\n' % (extra)) pickle_instance(py_obj, xml_list, level=0, deepcopy=deepcopy) xml_list.append('</PyObject>\n') # returns None if xml_list is a fileobj, but caller should # know that (or not care) return xml_list.getvalue()
def _tag_completer(start_tag, orig_thing, close_tag, level, deepcopy): tag_body = [] (mtag, thing, in_body, mextra) = try_mutate(orig_thing, None, getInBody(type(orig_thing)), None) if type(thing) is type(None): start_tag = start_tag + "%s />\n" % (_family_type( 'none', 'None', None, None)) close_tag = '' # bool cannot be used as a base class (see sanity check above) so if thing # is a bool it will always be BooleanType, and either True or False elif Have_BoolClass and type(thing) is bool: if thing is True: typestr = 'True' else: # must be False typestr = 'False' if in_body: start_tag = start_tag + '%s>%s' % \ (_family_type('uniq',typestr,mtag,mextra), '') close_tag = close_tag.lstrip() else: start_tag = start_tag + '%s value="%s" />\n' % \ (_family_type('uniq',typestr,mtag,mextra), '') close_tag = '' # ClassType will get caught by isInstanceLike(), which is not # what we want. There are two cases here - the first catches # old-style classes, the second catches new-style classes. elif isinstance(thing, type) or isNewStyleClass(thing): module = thing.__module__ if module: extra = 'module="%s" class="%s"' % (module, thing.__name__) else: extra = 'class="%s"' % _klass(thing.__name__) start_tag = start_tag + '%s %s/>\n' % \ (_family_type('lang','class',mtag,mextra),extra) close_tag = '' # have to check for instance-like next since ints, etc., can be # instance-like in Python 2.2+. if it's really an object, we don't # want to fall through to the regular int,float,etc. code, since # that would skip the special handling in pickle_instance(). elif isInstanceLike(thing): module = _module(thing) if module: extra = 'module="%s" class="%s"' % (module, _klass(thing)) else: extra = 'class="%s"' % _klass(thing) start_tag, do_copy = \ _tag_compound(start_tag,_family_type('obj','PyObject',mtag,mextra), orig_thing, deepcopy, extra) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: pickle_instance(thing, tag_body, level + 1, deepcopy) else: close_tag = '' elif isinstance_any(thing, (int, int, float, complex)): #thing_str = repr(thing) thing_str = ntoa(thing) if in_body: # we don't call safe_content() here since numerics won't # contain special XML chars. # the unpickler can either call unsafe_content() or not, # it won't matter start_tag = start_tag + '%s>%s' % \ (_family_type('atom','numeric',mtag,mextra), thing_str) close_tag = close_tag.lstrip() else: start_tag = start_tag + '%s value="%s" />\n' % \ (_family_type('atom','numeric',mtag,mextra),thing_str) close_tag = '' elif isinstance_any(thing, (bytes, str)): #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # special check for now - this will be fixed in the next major # gnosis release, so I don't care that the code is inline & gross # for now #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX if isinstance(thing, str): # can't pickle unicode containing the special "escape" sequence # we use for putting strings in the XML body (they'll be unpickled # as strings, not unicode, if we do!) if thing[0:2] == '\xbb\xbb' and thing[-2:] == '\xab\xab': raise Exception( "Unpickleable Unicode value. To be fixed in next major Gnosis release." ) # see if it contains any XML-illegal values if not is_legal_xml(thing): raise Exception( "Unpickleable Unicode value. To be fixed in next major Gnosis release." ) if isinstance(thing, bytes) and getInBody(bytes): # technically, this will crash safe_content(), but I prefer to # have the test here for clarity try: # safe_content assumes it can always convert the string # to unicode, which isn't true (eg. pickle a UTF-8 value) u = str(thing) except: raise Exception( "Unpickleable string value (%s). To be fixed in next major Gnosis release." % repr(thing)) #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # End of temporary hack code #XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX if in_body: start_tag = start_tag + '%s>%s' % \ (_family_type('atom','string',mtag,mextra), safe_content(thing)) close_tag = close_tag.lstrip() else: start_tag = start_tag + '%s value="%s" />\n' % \ (_family_type('atom','string',mtag,mextra), safe_string(thing)) close_tag = '' # General notes: # 1. When we make references, set type to referenced object # type -- we don't need type when unpickling, but it may be useful # to someone reading the XML file # 2. For containers, we have to stick the container into visited{} # before pickling subitems, in case it contains self-references # (we CANNOT just move the visited{} update to the top of this # function, since that would screw up every _family_type() call) elif type(thing) is tuple: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('seq','tuple',mtag,mextra), orig_thing,deepcopy) if do_copy: for item in thing: tag_body.append(_item_tag(item, level + 1, deepcopy)) else: close_tag = '' elif type(thing) is list: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('seq','list',mtag,mextra), orig_thing,deepcopy) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: for item in thing: tag_body.append(_item_tag(item, level + 1, deepcopy)) else: close_tag = '' elif type(thing) in [dict]: start_tag, do_copy = \ _tag_compound(start_tag,_family_type('map','dict',mtag,mextra), orig_thing,deepcopy) # need to remember we've seen container before pickling subitems visited[id(orig_thing)] = orig_thing if do_copy: for key, val in list(thing.items()): tag_body.append(_entry_tag(key, val, level + 1, deepcopy)) else: close_tag = '' elif type(thing) in [FunctionType, BuiltinFunctionType]: info = get_function_info(thing) # use module/class tags -- not perfect semantically, but better # that creating new attr names start_tag = start_tag + '%s module="%s" class="%s"/>\n' % \ (_family_type('lang','function',mtag,mextra), info[0],info[1]) close_tag = '' else: # try using pickled value as the XML value tag. # rationale: it won't be (easily) editable, but at least # you'll get valid XML even if your classes happen to # contain a few "foreign" types, and you don't feel like # writing a helper object (see gnosis.xml.pickle.ext for # how to do that) try: # we can't lookup the helper by type, since rawpickle can pickle # any pickleable class, so lookup by tag (unmutator) instead # (mutator & unmutator are always the same object) # always put rawpickles in the element body mutator = get_unmutator('rawpickle', None) thing = safe_content(mutator.mutate(thing).obj) start_tag = start_tag + '%s>%s' % (_family_type( 'atom', None, 'rawpickle', None), thing) close_tag = close_tag.lstrip() except: raise XMLPicklingError("non-handled type %s" % type(thing)) # need to keep a ref to the object for two reasons - # 1. we can ref it later instead of copying it into the XML stream # 2. need to keep temporary objects around so their ids don't get reused # if DEEPCOPY, we can skip this -- reusing ids is not an issue if we # never look at them if not deepcopy: visited[id(orig_thing)] = orig_thing return start_tag + ''.join(tag_body) + close_tag