|
|
|
|
@ -1,28 +1,62 @@
|
|
|
|
|
"""
|
|
|
|
|
Preserve, for when pickling is just a bit too intense
|
|
|
|
|
|
|
|
|
|
Preserve is a simple framework to painlessly convert your python objects and
|
|
|
|
|
data structures into a nested structure containing only primitive types.
|
|
|
|
|
|
|
|
|
|
While intended for use in serialisation, Preserve stops short of the actual
|
|
|
|
|
serialisation bit - you can then use whatever method you prefer to dump
|
|
|
|
|
the nested primitive structure to JSON, TOML, or some other message packing
|
|
|
|
|
or storage system - most of which handle all primitive types by default.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from enum import Enum, auto
|
|
|
|
|
import inspect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RestoreMethod(Enum):
|
|
|
|
|
DIRECT = auto()
|
|
|
|
|
INIT = auto()
|
|
|
|
|
CLASS_METHOD = auto()
|
|
|
|
|
|
|
|
|
|
#preserve, for when pickling is just a bit too intense
|
|
|
|
|
|
|
|
|
|
# The class key is a reserved dict key used to flag that the dict should be unpacked back out to a class instance
|
|
|
|
|
class_key = "<_jam>"
|
|
|
|
|
# The Preserve module stores some state from init to keep a list of valid packable classes
|
|
|
|
|
"""
|
|
|
|
|
Contains the reserved dict key used to indicate that the dict it is in
|
|
|
|
|
should be restored to a class instance, not just treated as a dict.
|
|
|
|
|
|
|
|
|
|
Set to ``<_jam>`` by default. If changed externally, must be set before
|
|
|
|
|
any ``@preservable`` decorators
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
preservables = {}
|
|
|
|
|
"""
|
|
|
|
|
A dict of classes marked as ``@preservable``, used to restore them back
|
|
|
|
|
to class instances.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# Decorator to mark class as packable and keep track of associated names and classes. When packed, the
|
|
|
|
|
# special key string "<packable>" indicates what class the current dict should be unpacked to
|
|
|
|
|
|
|
|
|
|
# name argument is the string that will identify this class in a packed dict
|
|
|
|
|
def preservable(cls, restore_method=RestoreMethod.DIRECT, name=None):
|
|
|
|
|
"""
|
|
|
|
|
Decorator to mark class as preservable and keep track of associated names
|
|
|
|
|
and classes.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
restore_method: One of the available preserve.RestoreMethod values.
|
|
|
|
|
Sets the method used for restoring this class. Defaults to
|
|
|
|
|
``DIRECT``, skipping the ``__init__`` method and setting all
|
|
|
|
|
attributes as they were.
|
|
|
|
|
name: The string used to indentify this class in the preserved dict.
|
|
|
|
|
Must be unique among all ``@preservable`` classes. Defaults to the
|
|
|
|
|
class name if left as None.
|
|
|
|
|
"""
|
|
|
|
|
if name is None:
|
|
|
|
|
cls._preserve_name = cls.__name__
|
|
|
|
|
else:
|
|
|
|
|
if isinstance(name, str):
|
|
|
|
|
raise Exception("preservable name must be a string")
|
|
|
|
|
raise Exception("Preservable name must be a string")
|
|
|
|
|
cls._preserve_name = name
|
|
|
|
|
cls._restore_method = restore_method
|
|
|
|
|
|
|
|
|
|
@ -31,15 +65,27 @@ def preservable(cls, restore_method=RestoreMethod.DIRECT, name=None):
|
|
|
|
|
preservables[cls._preserve_name] = cls
|
|
|
|
|
|
|
|
|
|
def _preserve(self):
|
|
|
|
|
dict_jam=_preserve_dict(vars(self))
|
|
|
|
|
dict_jam[class_key]=self._preserve_name
|
|
|
|
|
dict_jam = _preserve_dict(vars(self))
|
|
|
|
|
dict_jam[class_key] = self._preserve_name
|
|
|
|
|
return dict_jam
|
|
|
|
|
|
|
|
|
|
cls.preserve=_preserve
|
|
|
|
|
cls.preserve = _preserve
|
|
|
|
|
return cls
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def preserve(target_obj):
|
|
|
|
|
"""
|
|
|
|
|
Preserve ``target_obj``, running through its contents recursively.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
target_obj: The object to be preserved. This object and all its nested
|
|
|
|
|
contents must either be primitive types or objects of a
|
|
|
|
|
``@preservable`` class.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
The preserved data structure - a nested structure containing only primitive
|
|
|
|
|
types.
|
|
|
|
|
"""
|
|
|
|
|
# If it's a primitive, store it. If it's a dict or list, recursively preserve that.
|
|
|
|
|
# If it's an instance of another preservable class, call its .preserve() method.
|
|
|
|
|
if isinstance(target_obj, (str, int, float, bool, type(None))):
|
|
|
|
|
@ -56,17 +102,28 @@ def preserve(target_obj):
|
|
|
|
|
else:
|
|
|
|
|
raise Exception("Object "+str(target_obj)+" is not preservable")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _preserve_dict(target_dict):
|
|
|
|
|
dict_jam = {}
|
|
|
|
|
for k,val in target_dict.items():
|
|
|
|
|
if not isinstance(k,str):
|
|
|
|
|
for k, val in target_dict.items():
|
|
|
|
|
if not isinstance(k, str):
|
|
|
|
|
raise Exception("Non-string dictionary keys are not preservable")
|
|
|
|
|
if k == class_key:
|
|
|
|
|
raise Exception("Key "+class_key+" is reserved for internal use")
|
|
|
|
|
dict_jam[k]=preserve(val)
|
|
|
|
|
dict_jam[k] = preserve(val)
|
|
|
|
|
return dict_jam
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def restore(obj_jam):
|
|
|
|
|
"""
|
|
|
|
|
Restore the result of ``preserve()`` back into its original form. Will
|
|
|
|
|
recursively scan the data structure and restore any
|
|
|
|
|
``@preservable`` classes according to their ``restore_method``.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
obj_jam: The data structure to restore, usually the result of a
|
|
|
|
|
``preserve()`` call.
|
|
|
|
|
"""
|
|
|
|
|
if isinstance(obj_jam, (str, int, float, bool, type(None))):
|
|
|
|
|
return obj_jam
|
|
|
|
|
elif isinstance(obj_jam, dict):
|
|
|
|
|
@ -79,24 +136,25 @@ def restore(obj_jam):
|
|
|
|
|
else:
|
|
|
|
|
raise Exception("Object "+str(obj_jam)+" is not restorable")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _restore_dict(dict_jam):
|
|
|
|
|
restored_dict = {}
|
|
|
|
|
for k,val in dict_jam.items():
|
|
|
|
|
if not isinstance(k,str):
|
|
|
|
|
for k, val in dict_jam.items():
|
|
|
|
|
if not isinstance(k, str):
|
|
|
|
|
raise Exception("Non-string dictionary keys are not restorable")
|
|
|
|
|
if k != class_key:
|
|
|
|
|
restored_dict[k]=restore(val)
|
|
|
|
|
restored_dict[k] = restore(val)
|
|
|
|
|
|
|
|
|
|
# Check if this is an object that needs to be restored back to a class instance
|
|
|
|
|
if class_key in dict_jam:
|
|
|
|
|
if dict_jam[class_key] not in preservables:
|
|
|
|
|
raise Exception("Class "+dict_jam[class_key]+" has not been decorated as preservable")
|
|
|
|
|
f_class=preservables[dict_jam[class_key]]
|
|
|
|
|
f_class = preservables[dict_jam[class_key]]
|
|
|
|
|
# If DIRECT, skip __init__ and set attributes back directly
|
|
|
|
|
if f_class._restore_method == RestoreMethod.DIRECT:
|
|
|
|
|
restored_instance = f_class.__new__(f_class)
|
|
|
|
|
restored_instance.__dict__.update(restored_dict)
|
|
|
|
|
#if INIT, pass all attributes as keywords to __init__ method
|
|
|
|
|
# if INIT, pass all attributes as keywords to __init__ method
|
|
|
|
|
elif f_class._restore_method == RestoreMethod.INIT:
|
|
|
|
|
restored_instance = f_class(**restored_dict)
|
|
|
|
|
# IF CLASS_METHOD, pass all attributes as keyword aguments to classmethod "unpack()"
|
|
|
|
|
@ -106,8 +164,9 @@ def _restore_dict(dict_jam):
|
|
|
|
|
else:
|
|
|
|
|
raise Exception("Class "+str(f_class)+" does not have classmethod 'restore()'")
|
|
|
|
|
else:
|
|
|
|
|
raise Exception("Class _restore_method "+str(f_class._restore_method)+" is not supported")
|
|
|
|
|
raise Exception("Class _restore_method " +
|
|
|
|
|
str(f_class._restore_method)+" is not supported")
|
|
|
|
|
|
|
|
|
|
return restored_instance
|
|
|
|
|
else:
|
|
|
|
|
return restored_dict
|
|
|
|
|
return restored_dict
|
|
|
|
|
|