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