Source code for hypergol.base_data

import json
import base64
import pickle

from hypergol.repr import Repr


class NoIdException(Exception):
    pass


[docs]class BaseData(Repr): """ Base class for all domain objects. Extends the Repr convenience base class that provides printing facilities. Provides to_data and from_data serialisation interfaces. Provides get_id, get_hash_id interfaces. Provides test capabilities for all of the above so changes to the derived classes can be checked quickly. """ def __eq__(self, other): if not isinstance(self, type(other)): return False try: for k in set(self.__dict__.keys()) | set(self.__dict__.keys()): if not self.__dict__.__eq__(other.__dict__[k]): return False return True except KeyError: return False
[docs] def get_id(self): """Returns the class's id if exists""" raise NoIdException(f"{self.__class__.__name__} doesn't have an id")
[docs] def get_hash_id(self): """Returns the class's hash id if exists, defaults to get_id()""" return self.get_id()
[docs] def to_data(self): """Converts class to a dictionary, usually overridden""" return self.__dict__.copy()
[docs] @staticmethod def to_string(data): return base64.b64encode(pickle.dumps(data)).decode('utf-8')
[docs] @staticmethod def from_string(data): return pickle.loads(base64.b64decode(data.encode('utf-8')))
[docs] @classmethod def from_data(cls, data): """Creates a class from data, usually overridden Parameters ---------- data : dictionary form of data representing the object Usually the result of a previous to_data() call. """ return cls(**data)
[docs] def test_get_hash_id(self): """Tests if the derived class correctly returns a tuple for an id""" try: classId = self.get_hash_id() # pylint: disable=assignment-from-no-return except NoIdException: return True if not isinstance(classId, tuple): raise ValueError(f'Return of get_id must be a tuple instead of {type(classId)}') return True
[docs] def test_to_data(self): """Tests if the output of the derived class's to_data() function can be converted to a string by ``json.dumps()``""" originalData = self.__dict__.copy() data = self.to_data() for k, v in self.__dict__.items(): if v != originalData[k]: raise AssertionError(f'{self.__class__.__name__}.to_data() changes the instance itself: {k}: {v} != {originalData[k]}') try: _ = json.dumps(data) except TypeError as ex: raise TypeError(f'{self.__class__.__name__} JSON serde test failed: {ex}') return True
[docs] def test_from_data(self): """Tests if a roundtrip of ``self.from_data(self.to_data())`` modifies the class""" selfCopy = self.from_data(self.to_data()) if not isinstance(self, type(selfCopy)): raise AssertionError(f'{self.__class__.__name__}.from_data() does not return the correct type: {self.__class__.__name__} vs {selfCopy.__class__.__name__}, from_data() return value should be "cls(**data)"') for k, v in selfCopy.__dict__.items(): if v != self.__dict__[k]: if str(k) == str(v): raise AssertionError(f'{self.__class__.__name__}.from_data() returns keys as values: {k}: {v} != {self.__dict__[k]}, from_data() return value should be "cls(**data)"') raise AssertionError(f'{self.__class__.__name__}.from_data() does not deserialise: {k}: {v} != {self.__dict__[k]}') return True