1"""This module provides code that allows one to pickle the state of a
2Python object to a dictionary.
3
4The motivation for this is simple.  The standard Python
5pickler/unpickler is best used to pickle simple objects and does not
6work too well for complex code.  Specifically, there are two major
7problems (1) the pickle file format is not easy to edit with a text
8editor and (2) when a pickle is unpickled, it creates all the
9necessary objects and sets the state of these objects.
10
11Issue (2) might not appear to be a problem.  However, often, the
12determination of the entire 'state' of an application requires the
13knowledge of the state of many objects that are not really in the
14users concern.  The user would ideally like to pickle just what he
15thinks is relevant.  Now, given that the user is not going to save the
16entire state of the application, the use of pickle is insufficient
17since the state is no longer completely known (or worth knowing).  The
18default `Unpickler` recreates the objects and the typical
19implementation of `__setstate__` is usually to simply update the
20object's `__dict__` attribute.  This is inadequate because the pickled
21information is taken out of the real context when it was saved.
22
23The `StatePickler` basically pickles the 'state' of an object into a
24large dictionary.  This pickled data may be easily unpickled and
25modified on the interpreter or edited with a text editor
26(`pprint.saferepr` is a friend).  The second problem is also
27eliminated.  When this state is unpickled using `StateUnpickler`, what
28you get is a special dictionary (a `State` instance).  This allows one
29to navigate the state just like the original object.  Its up to the
30user to create any new objects and set their states using this
31information.  This allows for a lot of flexibility while allowing one
32to save and set the state of (almost) any Python object.
33
34The `StateSetter` class helps set the state of a known instance.  When
35setting the state of an instance it checks to see if there is a
36`__set_pure_state__` method that in turn calls `StateSetter.set`
37appropriately.
38
39Additionally, there is support for versioning.  The class' version is
40obtain from the `__version__` class attribute.  This version along
41with the versions of the bases of a class is embedded into the
42metadata of the state and stored.  By using `version_registry.py` a
43user may register a handler for a particular class and module.  When
44the state of an object is set using `StateSetter.set_state`, then
45these handlers are called in reverse order of their MRO.  This gives
46the handler an opportunity to upgrade the state depending on its
47version.  Builtin classes are not scanned for versions.  If a class
48has no version, then by default it is assumed to be -1.
49
50
51Example::
52
53  >>> class A:
54  ...    def __init__(self):
55  ...        self.a = 'a'
56  ...
57  >>> a = A()
58  >>> a.a = 100
59  >>> import state_pickler
60  >>> s = state_pickler.dumps(a)               # Dump the state of `a`.
61  >>> state = state_pickler.loads_state(s)     # Get the state back.
62  >>> b = state_pickler.create_instance(state) # Create the object.
63  >>> state_pickler.set_state(b, state)        # Set the object's state.
64  >>> assert b.a == 100
65
66
67Features
68--------
69
70 - The output is a plain old dictionary so is easy to parse, edit etc.
71 - Handles references to avoid duplication.
72 - Gzips Numeric arrays when dumping them.
73 - Support for versioning.
74
75
76Caveats
77-------
78
79 - Does not pickle a whole bunch of stuff including code objects and
80   functions.
81 - The output is a pure dictionary and does not contain instances.  So
82   using this *as it is* in `__setstate__` will not work.  Instead
83   define a `__set_pure_state__` and use the `StateSetter` class or
84   the `set_state` function provided by this module.
85
86
87Notes
88-----
89
90  Browsing the code from XMarshaL_ and pickle.py proved useful for
91  ideas.  None of the code is taken from there though.
92
93.. _XMarshaL:  http://www.dezentral.de/soft/XMarshaL
94
95"""
96# Author: Prabhu Ramachandran <prabhu_r@users.sf.net>
97# Copyright (c) 2005-2015, Enthought, Inc.
98# License: BSD Style.
99
100# Standard library imports.
101import base64
102import sys
103import types
104import pickle
105import gzip
106from io import BytesIO, StringIO
107
108import numpy
109
110# Local imports.
111from . import version_registry
112from .file_path import FilePath
113
114PY_VER = sys.version_info[0]
115NumpyArrayType = type(numpy.array([]))
116
117
118def gzip_string(data):
119    """Given a string (`data`) this gzips the string and returns it.
120    """
121    s = BytesIO()
122    writer = gzip.GzipFile(mode='wb', fileobj=s)
123    writer.write(data)
124    writer.close()
125    s.seek(0)
126    return s.read()
127
128
129def gunzip_string(data):
130    """Given a gzipped string (`data`) this unzips the string and
131    returns it.
132    """
133    if PY_VER== 2 or (bytes is not str and type(data) is bytes):
134        s = BytesIO(data)
135    else:
136        s = StringIO(data)
137    writer = gzip.GzipFile(mode='rb', fileobj=s)
138    data = writer.read()
139    writer.close()
140    return data
141
142class StatePicklerError(Exception):
143    pass
144
145class StateUnpicklerError(Exception):
146    pass
147
148class StateSetterError(Exception):
149    pass
150
151######################################################################
152# `State` class
153######################################################################
154class State(dict):
155    """Used to encapsulate the state of an instance in a very
156    convenient form.  The '__metadata__' attribute/key is a dictionary
157    that has class specific details like the class name, module name
158    etc.
159    """
160    def __init__(self, **kw):
161        dict.__init__(self, **kw)
162        self.__dict__ = self
163
164######################################################################
165# `StateDict` class
166######################################################################
167class StateDict(dict):
168    """Used to encapsulate a dictionary stored in a `State` instance.
169    The has_instance attribute specifies if the dict has an instance
170    embedded in it.
171    """
172    def __init__(self, **kw):
173        dict.__init__(self, **kw)
174        self.has_instance = False
175
176######################################################################
177# `StateList` class
178######################################################################
179class StateList(list):
180    """Used to encapsulate a list stored in a `State` instance.  The
181    has_instance attribute specifies if the list has an instance
182    embedded in it.
183    """
184    def __init__(self, seq=None):
185        if seq:
186            list.__init__(self, seq)
187        else:
188            list.__init__(self)
189        self.has_instance = False
190
191######################################################################
192# `StateTuple` class
193######################################################################
194class StateTuple(tuple):
195    """Used to encapsulate a tuple stored in a `State` instance.  The
196    has_instance attribute specifies if the tuple has an instance
197    embedded in it.
198    """
199    def __new__(cls, seq=None):
200        if seq:
201            obj = super(StateTuple, cls).__new__(cls, tuple(seq))
202        else:
203            obj = super(StateTuple, cls).__new__(cls)
204        obj.has_instance = False
205        return obj
206
207
208######################################################################
209# `StatePickler` class
210######################################################################
211class StatePickler:
212    """Pickles the state of an object into a dictionary.  The
213    dictionary is itself either saved as a pickled file (`dump`) or
214    pickled string (`dumps`).  Alternatively, the `dump_state` method
215    will return the dictionary that is pickled.
216
217    The format of the state dict is quite strightfoward.  Basic types
218    (bool, int, long, float, complex, None, string and unicode) are
219    represented as they are.  Everything else is stored as a
220    dictionary containing metadata information on the object's type
221    etc. and also the actual object in the 'data' key.  For example::
222
223        >>> p = StatePickler()
224        >>> p.dump_state(1)
225        1
226        >>> l = [1,2.0, None, [1,2,3]]
227        >>> p.dump_state(l)
228        {'data': [1, 2.0, None, {'data': [1, 2, 3], 'type': 'list', 'id': 1}],
229         'id': 0,
230         'type': 'list'}
231
232    Classes are also represented similarly.  The state in this case is
233    obtained from the `__getstate__` method or from the `__dict__`.
234    Here is an example::
235
236        >>> class A:
237        ...     __version__ = 1  # State version
238        ...     def __init__(self):
239        ...         self.attribute = 1
240        ...
241        >>> a = A()
242        >>> p = StatePickler()
243        >>> p.dump_state(a)
244        {'class_name': 'A',
245         'data': {'data': {'attribute': 1}, 'type': 'dict', 'id': 2},
246         'id': 0,
247         'initargs': {'data': (), 'type': 'tuple', 'id': 1},
248         'module': '__main__',
249         'type': 'instance',
250         'version': [(('A', '__main__'), 1)]}
251
252    When pickling data, references are taken care of.  Numeric arrays
253    can be pickled and are stored as a gzipped base64 encoded string.
254
255    """
256    def __init__(self):
257        self._clear()
258        type_map = {bool: self._do_basic_type,
259                    complex: self._do_basic_type,
260                    float: self._do_basic_type,
261                    int: self._do_basic_type,
262                    type(None): self._do_basic_type,
263                    str: self._do_basic_type,
264                    bytes: self._do_basic_type,
265                    tuple: self._do_tuple,
266                    list: self._do_list,
267                    dict: self._do_dict,
268                    NumpyArrayType: self._do_numeric,
269                    State: self._do_state,
270                    }
271        if PY_VER == 2:
272            type_map[long] = self._do_basic_type
273            type_map[unicode] = self._do_basic_type
274        self.type_map = type_map
275
276    def dump(self, value, file):
277        """Pickles the state of the object (`value`) into the passed
278        file.
279        """
280        try:
281            # Store the file name we are writing to so we can munge
282            # file paths suitably.
283            self.file_name = file.name
284        except AttributeError:
285            pass
286        pickle.dump(self._do(value), file)
287
288    def dumps(self, value):
289        """Pickles the state of the object (`value`) and returns a
290        string.
291        """
292        return pickle.dumps(self._do(value))
293
294    def dump_state(self, value):
295        """Returns a dictionary or a basic type representing the
296        complete state of the object (`value`).
297
298        This value is pickled by the `dump` and `dumps` methods.
299        """
300        return self._do(value)
301
302    ######################################################################
303    # Non-public methods
304    ######################################################################
305    def _clear(self):
306        # Stores the file name of the file being used to dump the
307        # state.  This is used to change any embedded paths relative
308        # to the saved file.
309        self.file_name = ''
310        # Caches id's to handle references.
311        self.obj_cache = {}
312        # Misc cache to cache things that are not persistent.  For
313        # example, object.__getstate__()/__getinitargs__() usually
314        # returns a copy of a dict/tuple that could possibly be reused
315        # on another object's __getstate__.  Caching these prevents
316        # some wierd problems with the `id` of the object.
317        self._misc_cache = []
318
319    def _flush_traits(self, obj):
320        """Checks if the object has traits and ensures that the traits
321        are set in the `__dict__` so we can pickle it.
322        """
323        # Not needed with Traits3.
324        return
325
326    def _do(self, obj):
327        obj_type = type(obj)
328        key = self._get_id(obj)
329        if key in self.obj_cache:
330            return self._do_reference(obj)
331        elif obj_type in self.type_map:
332            return self.type_map[obj_type](obj)
333        elif isinstance(obj, tuple):
334            # Takes care of StateTuples.
335            return self._do_tuple(obj)
336        elif isinstance(obj, list):
337            # Takes care of TraitListObjects.
338            return self._do_list(obj)
339        elif isinstance(obj, dict):
340            # Takes care of TraitDictObjects.
341            return self._do_dict(obj)
342        elif hasattr(obj, '__dict__'):
343            return self._do_instance(obj)
344
345    def _get_id(self, value):
346        try:
347            key = hash(value)
348        except TypeError:
349            key = id(value)
350        return key
351
352    def _register(self, value):
353        key = self._get_id(value)
354        cache = self.obj_cache
355        idx = len(cache)
356        cache[key] = idx
357        return idx
358
359    def _do_basic_type(self, value):
360        return value
361
362    def _do_reference(self, value):
363        key = self._get_id(value)
364        idx = self.obj_cache[key]
365        return dict(type='reference', id=idx, data=None)
366
367    def _do_instance(self, value):
368        # Flush out the traits.
369        self._flush_traits(value)
370
371        # Setup the relative paths of FilePaths before dumping.
372        if self.file_name and isinstance(value, FilePath):
373            value.set_relative(self.file_name)
374
375        # Get the initargs.
376        args = ()
377        if hasattr(value, '__getinitargs__') and value.__getinitargs__:
378            args = value.__getinitargs__()
379
380        # Get the object state.
381        if hasattr(value, '__get_pure_state__'):
382            state = value.__get_pure_state__()
383        elif hasattr(value, '__getstate__'):
384            state = value.__getstate__()
385        else:
386            state = value.__dict__
387
388        state.pop('__traits_version__', None)
389
390        # Cache the args and state since they are likely to be gc'd.
391        self._misc_cache.extend([args, state])
392        # Register and process.
393        idx = self._register(value)
394        args_data = self._do(args)
395        data = self._do(state)
396
397        # Get the version of the object.
398        version = version_registry.get_version(value)
399        module = value.__class__.__module__
400        class_name = value.__class__.__name__
401
402        return dict(type='instance',
403                    module=module,
404                    class_name=class_name,
405                    version=version,
406                    id=idx,
407                    initargs=args_data,
408                    data=data)
409
410    def _do_state(self, value):
411        metadata = value.__metadata__
412        args = metadata.get('initargs')
413        state = dict(value)
414        state.pop('__metadata__')
415
416        self._misc_cache.extend([args, state])
417
418        idx = self._register(value)
419        args_data = self._do(args)
420        data = self._do(state)
421
422        return dict(type='instance',
423                    module=metadata['module'],
424                    class_name=metadata['class_name'],
425                    version=metadata['version'],
426                    id=idx,
427                    initargs=args_data,
428                    data=data)
429
430    def _do_tuple(self, value):
431        idx = self._register(value)
432        data = tuple([self._do(x) for x in value])
433        return dict(type='tuple', id=idx, data=data)
434
435    def _do_list(self, value):
436        idx = self._register(value)
437        data = [self._do(x) for x in value]
438        return dict(type='list', id=idx, data=data)
439
440    def _do_dict(self, value):
441        idx = self._register(value)
442        vals = [self._do(x) for x in value.values()]
443        data = dict(zip(value.keys(), vals))
444        return dict(type='dict', id=idx, data=data)
445
446    def _do_numeric(self, value):
447        idx = self._register(value)
448        if PY_VER > 2:
449            data = base64.encodebytes(gzip_string(numpy.ndarray.dumps(value)))
450        else:
451            data = base64.encodestring(gzip_string(numpy.ndarray.dumps(value)))
452        return dict(type='numeric', id=idx, data=data)
453
454
455
456######################################################################
457# `StateUnpickler` class
458######################################################################
459class StateUnpickler:
460    """Unpickles the state of an object saved using StatePickler.
461
462    Please note that unlike the standard Unpickler, no instances of
463    any user class are created.  The data for the state is obtained
464    from the file or string, reference objects are setup to refer to
465    the same state value and this state is returned in the form
466    usually in the form of a dictionary.  For example::
467
468        >>> class A:
469        ...     def __init__(self):
470        ...         self.attribute = 1
471        ...
472        >>> a = A()
473        >>> p = StatePickler()
474        >>> s = p.dumps(a)
475        >>> up = StateUnpickler()
476        >>> state = up.loads_state(s)
477        >>> state.__class__.__name__
478        'State'
479        >>> state.attribute
480        1
481        >>> state.__metadata__
482        {'class_name': 'A',
483         'has_instance': True,
484         'id': 0,
485         'initargs': (),
486         'module': '__main__',
487         'type': 'instance',
488         'version': [(('A', '__main__'), -1)]}
489
490    Note that the state is actually a `State` instance and is
491    navigable just like the original object.  The details of the
492    instance are stored in the `__metadata__` attribute.  This is
493    highly convenient since it is possible for someone to view and
494    modify the state very easily.
495    """
496
497    def __init__(self):
498        self._clear()
499        self.type_map = {'reference': self._do_reference,
500                         'instance': self._do_instance,
501                         'tuple': self._do_tuple,
502                         'list': self._do_list,
503                         'dict': self._do_dict,
504                         'numeric': self._do_numeric,
505                         }
506
507    def load_state(self, file):
508        """Returns the state of an object loaded from the pickled data
509        in the given file.
510        """
511        try:
512            self.file_name = file.name
513        except AttributeError:
514            pass
515        data = pickle.load(file)
516        result = self._process(data)
517        return result
518
519    def loads_state(self, string):
520        """Returns the state of an object loaded from the pickled data
521        in the given string.
522        """
523        data = pickle.loads(string)
524        result = self._process(data)
525        return result
526
527    ######################################################################
528    # Non-public methods
529    ######################################################################
530    def _clear(self):
531        # The file from which we are being loaded.
532        self.file_name = ''
533        # Cache of the objects.
534        self._obj_cache = {}
535        # Paths to the instances.
536        self._instances = []
537        # Caches the references.
538        self._refs = {}
539        # Numeric arrays.
540        self._numeric = {}
541
542    def _set_has_instance(self, obj, value):
543        if isinstance(obj, State):
544            obj.__metadata__['has_instance'] = value
545        elif isinstance(obj, (StateDict, StateList, StateTuple)):
546            obj.has_instance = value
547
548    def _process(self, data):
549        result = self._do(data)
550
551        # Setup all the Numeric arrays.  Do this first since
552        # references use this.
553        for key, (path, val) in self._numeric.items():
554            if isinstance(result, StateTuple):
555                result = list(result)
556                exec('result%s = val'%path)
557                result = StateTuple(result)
558            else:
559                exec('result%s = val'%path)
560
561        # Setup the references so they really are references.
562        for key, paths in self._refs.items():
563            for path in paths:
564                x = self._obj_cache[key]
565                if isinstance(result, StateTuple):
566                    result = list(result)
567                    exec('result%s = x'%path)
568                    result = StateTuple(result)
569                else:
570                    exec('result%s = x'%path)
571                # if the reference is to an instance append its path.
572                if isinstance(x, State):
573                    self._instances.append(path)
574
575        # Now setup the 'has_instance' attribute.  If 'has_instance'
576        # is True then the object contains an instance somewhere
577        # inside it.
578        for path in self._instances:
579            pth = path
580            while pth:
581                ns = {'result': result}
582                exec('val = result%s'%pth, ns, ns)
583                self._set_has_instance(ns['val'], True)
584                end = pth.rfind('[')
585                pth = pth[:end]
586            # Now make sure that the first element also has_instance.
587            self._set_has_instance(result, True)
588        return result
589
590    def _do(self, data, path=''):
591        if type(data) is dict:
592            return self.type_map[data['type']](data, path)
593        else:
594            return data
595
596    def _do_reference(self, value, path):
597        id = value['id']
598        if id in self._refs:
599            self._refs[id].append(path)
600        else:
601            self._refs[id] = [path]
602        return State(__metadata__=value)
603
604    def _handle_file_path(self, value):
605        if (value['class_name'] == 'FilePath') and \
606           ('file_path' in value['module']) and \
607           self.file_name:
608            data = value['data']['data']
609            fp = FilePath(data['rel_pth'])
610            fp.set_absolute(self.file_name)
611            data['abs_pth'] = fp.abs_pth
612
613    def _do_instance(self, value, path):
614        self._instances.append(path)
615        initargs = self._do(value['initargs'],
616                            path + '.__metadata__["initargs"]')
617        # Handle FilePaths.
618        self._handle_file_path(value)
619
620        d = self._do(value['data'], path)
621        md = dict(type='instance',
622                  module=value['module'],
623                  class_name=value['class_name'],
624                  version=value['version'],
625                  id=value['id'],
626                  initargs=initargs,
627                  has_instance=True)
628        result = State(**d)
629        result.__metadata__ = md
630        self._obj_cache[value['id']] = result
631        return result
632
633    def _do_tuple(self, value, path):
634        res = []
635        for i, x in enumerate(value['data']):
636            res.append(self._do(x, path + '[%d]'%i))
637        result = StateTuple(res)
638        self._obj_cache[value['id']] = result
639        return result
640
641    def _do_list(self, value, path):
642        result = StateList()
643        for i, x in enumerate(value['data']):
644            result.append(self._do(x, path + '[%d]'%i))
645        self._obj_cache[value['id']] = result
646        return result
647
648    def _do_dict(self, value, path):
649        result = StateDict()
650        for key, val in value['data'].items():
651            result[key] = self._do(val, path + '["%s"]'%key)
652        self._obj_cache[value['id']] = result
653        return result
654
655    def _do_numeric(self, value, path):
656        if PY_VER > 2:
657            data = value['data']
658            if isinstance(data, str):
659                data = value['data'].encode('utf-8')
660            junk = gunzip_string(base64.decodebytes(data))
661            result = pickle.loads(junk, encoding='bytes')
662        else:
663            junk = gunzip_string(value['data'].decode('base64'))
664            result = pickle.loads(junk)
665        self._numeric[value['id']] = (path, result)
666        self._obj_cache[value['id']] = result
667        return result
668
669
670######################################################################
671# `StateSetter` class
672######################################################################
673class StateSetter:
674    """This is a convenience class that helps a user set the
675    attributes of an object given its saved state.  For instances it
676    checks to see if a `__set_pure_state__` method exists and calls
677    that when it sets the state.
678    """
679    def __init__(self):
680        # Stores the ids of instances already done.
681        self._instance_ids = []
682        self.type_map = {State: self._do_instance,
683                         StateTuple: self._do_tuple,
684                         StateList: self._do_list,
685                         StateDict: self._do_dict,
686                         }
687
688    def set(self, obj, state, ignore=None, first=None, last=None):
689        """Sets the state of the object.
690
691        This is to be used as a means to simplify loading the state of
692        an object from its `__setstate__` method using the dictionary
693        describing its state.  Note that before the state is set, the
694        registered handlers for the particular class are called in
695        order to upgrade the version of the state to the latest
696        version.
697
698        Parameters
699        ----------
700
701        - obj : `object`
702
703          The object whose state is to be set.  If this is `None`
704          (default) then the object is created.
705
706        - state : `dict`
707
708          The dictionary representing the state of the object.
709
710        - ignore : `list(str)`
711
712          The list of attributes specified in this list are ignored
713          and the state of these attributes are not set (this excludes
714          the ones specified in `first` and `last`).  If one specifies
715          a '*' then all attributes are ignored except the ones
716          specified in `first` and `last`.
717
718        - first : `list(str)`
719
720          The list of attributes specified in this list are set first (in
721          order), before any other attributes are set.
722
723        - last : `list(str)`
724
725          The list of attributes specified in this list are set last (in
726          order), after all other attributes are set.
727
728        """
729        if (not isinstance(state, State)) and \
730               state.__metadata__['type'] != 'instance':
731            raise StateSetterError(
732                'Can only set the attributes of an instance.'
733            )
734
735        # Upgrade the state to the latest using the registry.
736        self._update_and_check_state(obj, state)
737
738        self._register(obj)
739
740        # This wierdness is needed since the state's own `keys` might
741        # be set to something else.
742        state_keys = list(dict.keys(state))
743        state_keys.remove('__metadata__')
744
745        if first is None:
746            first = []
747        if last is None:
748            last = []
749
750        # Remove all the ignored keys.
751        if ignore:
752            if '*' in ignore:
753                state_keys = first + last
754            else:
755                for name in ignore:
756                    try:
757                        state_keys.remove(name)
758                    except KeyError:
759                        pass
760
761        # Do the `first` attributes.
762        for key in first:
763            state_keys.remove(key)
764            self._do(obj, key, state[key])
765
766        # Remove the `last` attributes.
767        for key in last:
768            state_keys.remove(key)
769
770        # Set the remaining attributes.
771        for key in state_keys:
772            self._do(obj, key, state[key])
773
774        # Do the last ones in order.
775        for key in last:
776            self._do(obj, key, state[key])
777
778    ######################################################################
779    # Non-public methods.
780    ######################################################################
781    def _register(self, obj):
782        idx = id(obj)
783        if idx not in self._instance_ids:
784            self._instance_ids.append(idx)
785
786    def _is_registered(self, obj):
787        return (id(obj) in self._instance_ids)
788
789    def _has_instance(self, value):
790        """Given something (`value`) that is part of the state this
791        returns if the value has an instance embedded in it or not.
792        """
793        if isinstance(value, State):
794            return True
795        elif isinstance(value, (StateDict, StateList, StateTuple)):
796            return value.has_instance
797        return False
798
799    def _get_pure(self, value):
800        """Returns the Python representation of the object (usually a
801        list, tuple or dict) that has no instances embedded within it.
802        """
803        result = value
804        if self._has_instance(value):
805            raise StateSetterError(
806                'Value has an instance: %s'%value
807            )
808        if isinstance(value, (StateList, StateTuple)):
809            result = [self._get_pure(x) for x in value]
810            if isinstance(value, StateTuple):
811                result = tuple(result)
812        elif isinstance(value, StateDict):
813            result = {}
814            for k, v in value.items():
815                result[k] = self._get_pure(v)
816        return result
817
818    def _update_and_check_state(self, obj, state):
819        """Updates the state from the registry and then checks if the
820        object and state have same class.
821        """
822        # Upgrade this state object to the latest using the registry.
823        # This is done before testing because updating may change the
824        # class name/module.
825        version_registry.registry.update(state)
826
827        # Make sure object and state have the same class and module names.
828        metadata = state.__metadata__
829        cls = obj.__class__
830        if (metadata['class_name'] != cls.__name__):
831            raise StateSetterError(
832                'Instance (%s) and state (%s) do not have the same class'\
833                ' name!'%(cls.__name__, metadata['class_name'])
834            )
835        if (metadata['module'] != cls.__module__):
836            raise StateSetterError(
837                'Instance (%s) and state (%s) do not have the same module'\
838                ' name!'%(cls.__module__, metadata['module'])
839            )
840
841    def _do(self, obj, key, value):
842        try:
843            attr = getattr(obj, key)
844        except AttributeError:
845            raise StateSetterError(
846                'Object %s does not have an attribute called: %s'%(obj, key)
847            )
848
849        if isinstance(value, (State, StateDict, StateList, StateTuple)):
850            # Special handlers are needed.
851            if not self._has_instance(value):
852                result = self._get_pure(value)
853                setattr(obj, key, result)
854            elif isinstance(value, StateTuple):
855                setattr(obj, key, self._do_tuple(getattr(obj, key), value))
856            else:
857                self._do_object(getattr(obj, key), value)
858        else:
859            setattr(obj, key, value)
860
861    def _do_object(self, obj, state):
862        self.type_map[state.__class__](obj, state)
863
864    def _do_instance(self, obj, state):
865        if self._is_registered(obj):
866            return
867        else:
868            self._register(obj)
869
870        metadata = state.__metadata__
871        if hasattr(obj, '__set_pure_state__'):
872            self._update_and_check_state(obj, state)
873            obj.__set_pure_state__(state)
874        elif 'tvtk_classes' in metadata['module']:
875            self._update_and_check_state(obj, state)
876            tmp = self._get_pure(StateDict(**state))
877            del tmp['__metadata__']
878            obj.__setstate__(tmp)
879        else:
880            # No need to update or check since `set` does it for us.
881            self.set(obj, state)
882
883    def _do_tuple(self, obj, state):
884        if not self._has_instance(state):
885            return self._get_pure(state)
886        else:
887            result = list(obj)
888            self._do_list(result, state)
889            return tuple(result)
890
891    def _do_list(self, obj, state):
892        if len(obj) == len(state):
893            for i in range(len(obj)):
894                if not self._has_instance(state[i]):
895                    obj[i] = self._get_pure(state[i])
896                elif isinstance(state[i], tuple):
897                    obj[i] = self._do_tuple(state[i])
898                else:
899                    self._do_object(obj[i], state[i])
900        else:
901            raise StateSetterError(
902                'Cannot set state of list of incorrect size.'
903            )
904
905    def _do_dict(self, obj, state):
906        for key, value in state.items():
907            if not self._has_instance(value):
908                obj[key] = self._get_pure(value)
909            elif isinstance(value, tuple):
910                obj[key] = self._do_tuple(value)
911            else:
912                self._do_object(obj[key], value)
913
914
915######################################################################
916# Internal Utility functions.
917######################################################################
918def _get_file_read(f):
919    if hasattr(f, 'read'):
920        return f
921    else:
922        return open(f, 'rb')
923
924def _get_file_write(f):
925    if hasattr(f, 'write'):
926        return f
927    else:
928        return open(f, 'wb')
929
930
931######################################################################
932# Utility functions.
933######################################################################
934def dump(value, file):
935    """Pickles the state of the object (`value`) into the passed file
936    (or file name).
937    """
938    f = _get_file_write(file)
939    try:
940        StatePickler().dump(value, f)
941    finally:
942        f.flush()
943        if f is not file:
944            f.close()
945
946
947def dumps(value):
948    """Pickles the state of the object (`value`) and returns a string.
949    """
950    return StatePickler().dumps(value)
951
952
953def load_state(file):
954    """Returns the state of an object loaded from the pickled data in
955    the given file (or file name).
956    """
957    f = _get_file_read(file)
958    try:
959        state = StateUnpickler().load_state(f)
960    finally:
961        if f is not file:
962            f.close()
963    return state
964
965
966def loads_state(string):
967    """Returns the state of an object loaded from the pickled data
968    in the given string.
969    """
970    return StateUnpickler().loads_state(string)
971
972
973def get_state(obj):
974    """Returns the state of the object (usually as a dictionary).  The
975    returned state may be used directy to set the state of the object
976    via `set_state`.
977    """
978    s = dumps(obj)
979    return loads_state(s)
980
981
982def set_state(obj, state, ignore=None, first=None, last=None):
983    StateSetter().set(obj, state, ignore, first, last)
984set_state.__doc__ = StateSetter.set.__doc__
985
986
987def update_state(state):
988    """Given the state of an object, this updates the state to the
989    latest version using the handlers given in the version registry.
990    The state is modified in-place.
991    """
992    version_registry.registry.update(state)
993
994
995def create_instance(state):
996    """Create an instance from the state if possible.
997    """
998    if (not isinstance(state, State)) and \
999           ('class_name'  not in state.__metadata__):
1000        raise StateSetterError('No class information in state')
1001    metadata = state.__metadata__
1002    class_name = metadata.get('class_name')
1003    mod_name = metadata.get('module')
1004    if 'tvtk_classes' in mod_name:
1005        # FIXME: This sort of special-case is probably indicative of something
1006        # that needs more thought, plus it makes it tought to decide whether
1007        # this component depends on tvtk!
1008        from tvtk.api import tvtk
1009        return getattr(tvtk, class_name)()
1010
1011    initargs = metadata['initargs']
1012    if initargs.has_instance:
1013        raise StateUnpicklerError('Cannot unpickle non-trivial initargs')
1014
1015    __import__(mod_name, globals(), locals(), class_name)
1016    mod = sys.modules[mod_name]
1017    cls = getattr(mod, class_name)
1018    return cls(*initargs)
1019