From 4e08b32d10e43e159e02d640c8f9b592de6037a5 Mon Sep 17 00:00:00 2001 From: novirium Date: Fri, 13 Dec 2019 21:26:20 +0800 Subject: [PATCH] Spin library out from Shepherd --- freezedry.py | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 freezedry.py diff --git a/freezedry.py b/freezedry.py new file mode 100644 index 0000000..89aaa35 --- /dev/null +++ b/freezedry.py @@ -0,0 +1,114 @@ +from enum import Enum, auto +import inspect + +class RehydrateMethod(Enum): + DIRECT = auto() + INIT = auto() + CLASS_METHOD = auto() + +#freezedry, 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 = "" +# The Pack module stores some state from init to keep a list of valid packable classes +freezedryables = {} + +# Decorator to mark class as packable and keep track of associated names and classes. When packed, the +# special key string "" 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 freezedryable(cls, rehydrate_method=RehydrateMethod.DIRECT, name=None): + if name is None: + cls._freezedry_name = cls.__name__ + else: + if isinstance(name, str): + raise Exception("freezedryable name must be a string") + cls._freezedry_name = name + cls._rehydrate_method = rehydrate_method + + if cls._freezedry_name in freezedryables: + raise Exception("Duplicate freezedryable class name "+cls._freezedry_name) + freezedryables[cls._freezedry_name] = cls + + def _freezedry(self): + dried_dict=_freezedry_dict(vars(self)) + dried_dict[class_key]=self._freezedry_name + return dried_dict + + cls.freezedry=_freezedry + #setattr(cls, "freezedry", freezedry) + return cls + + +def freezedry(hydrated_obj): + # If it's a primitive, store it. If it's a dict or list, recursively freezedry that. + # If it's an instance of another freezedryable class, call its .freezedry() method. + if isinstance(hydrated_obj, (str, int, float, bool, type(None))): + return hydrated_obj + elif isinstance(hydrated_obj, dict): + return _freezedry_dict(hydrated_obj) + elif isinstance(hydrated_obj, list): + dried_list = [] + for val in hydrated_obj: + dried_list.append(freezedry(val)) + return dried_list + elif hasattr(hydrated_obj, "_freezedry_name"): + return hydrated_obj.freezedry() + else: + raise Exception("Object "+str(hydrated_obj)+" is not freezedryable") + +def _freezedry_dict(hydrated_dict): + dried_dict = {} + for k,val in hydrated_dict.items(): + if not isinstance(k,str): + raise Exception("Non-string dictionary keys are not freezedryable") + if k == class_key: + raise Exception("Key "+class_key+" is reserved for internal freezedry use") + dried_dict[k]=freezedry(val) + return dried_dict + +def rehydrate(dried_obj): + if isinstance(dried_obj, (str, int, float, bool, type(None))): + return dried_obj + elif isinstance(dried_obj, dict): + return _rehydrate_dict(dried_obj) + elif isinstance(dried_obj, list): + hydrated_list = [] + for val in dried_obj: + hydrated_list.append(rehydrate(val)) + return hydrated_list + else: + raise Exception("Object "+str(dried_obj)+" is not rehydrateable") + +def _rehydrate_dict(dried_dict): + hydrated_dict = {} + for k,val in dried_dict.items(): + if not isinstance(k,str): + raise Exception("Non-string dictionary keys are not rehydrateable") + if k != class_key: + hydrated_dict[k]=rehydrate(val) + + # Check if this is an object that needs to be unpacked back to an instance + if class_key in dried_dict: + if dried_dict[class_key] not in freezedryables: + raise Exception("Class "+dried_dict[class_key]+" has not been decorated as freezedryable") + f_class=freezedryables[dried_dict[class_key]] + # If DIRECT, skip __init__ and set attributes back directly + if f_class._rehydrate_method == RehydrateMethod.DIRECT: + hydrated_instance = f_class.__new__(f_class) + hydrated_instance.__dict__.update(hydrated_dict) + #if INIT, pass all attributes as keywords to __init__ method + elif f_class._rehydrate_method == RehydrateMethod.INIT: + hydrated_instance = f_class(**hydrated_dict) + # IF CLASS_METHOD, pass all attributes as keyword aguments to classmethod "unpack()" + elif f_class._rehydrate_method == RehydrateMethod.CLASS_METHOD: + if inspect.ismethod(getattr(f_class, "rehydrate", None)): + hydrated_instance = f_class.rehydrate(**hydrated_dict) + else: + raise Exception("Class "+str(f_class)+" does not have classmethod 'rehydrate()'") + else: + raise Exception("Class _rehydrate_method "+str(f_class._rehydrate_method)+" is not supported") + + return hydrated_instance + else: + return hydrated_dict \ No newline at end of file