# pylint: disable=redefined-outer-name, attribute-defined-outside-init import pytest import preserve @pytest.fixture def primitive_data(): return {'key1': ["item1", {"d2k1": "val1", "d2k2": 2}, {"d3k1": "val1", "d3k2": True}, "test str"]} def obj_attrs_and_type_equal(obj1, obj2): # Checks that both the __dict__ on each object and the __class__ on each object # are equal return (vars(type(obj1)) == vars(type(obj2))) and (vars(obj1) == vars(obj2)) def test_primitive_data(primitive_data): # preserve/restore shouldn't modify something that's already all primitives preserved_data = preserve.preserve(primitive_data) assert preserved_data == primitive_data assert (preserve.restore(preserved_data)) == primitive_data class PlainClass: def __init__(self): self.attr_int = 734 self.attr_string = "I'm a test string" self.attr_float = 42.085 self.attr_dict = {"key1": 1, "key2": "val2"} self.attr_list = ["item1", 2, 3, 4] self.attr_bool = True @preserve.preservable class PreservableClass(PlainClass): pass def test_data_with_preservable(primitive_data): # Preservable class within a data structure primitive_data["preservable"] = PreservableClass() restored_data = preserve.restore(preserve.preserve(primitive_data)) assert restored_data['key1'] == primitive_data['key1'] assert restored_data["preservable"] != primitive_data["preservable"] assert obj_attrs_and_type_equal(restored_data["preservable"], primitive_data["preservable"]) @preserve.preservable(exclude_attrs=['attr_int']) class PreservableClass_Args1(PlainClass): pass @preserve.preservable(include_attrs=['attr_float'], name='other_name') class PreservableClass_Args2(PlainClass): pass def test_preservable_args(): restored = preserve.restore(preserve.preserve(PreservableClass_Args1())) assert not hasattr(restored, 'attr_int') preserved = preserve.preserve(PreservableClass_Args2()) restored = preserve.restore(preserved) assert preserved[preserve.escape_key] == 'other_name' assert len(vars(restored)) == 1 assert restored.attr_float == 42.085 @preserve.preservable class PreservableClass_AttrArgs(PlainClass): _preserve_include_attrs = ['attr_string'] pass @preserve.preservable class PreservableClass_AttrArgs2(PreservableClass_AttrArgs): pass def test_preservable_class_attr_args(): restored = preserve.restore(preserve.preserve(PreservableClass_AttrArgs())) assert len(vars(restored)) == 1 assert restored.attr_string == "I'm a test string" # The class attribute args should propogate to the subclass restored = preserve.restore(preserve.preserve(PreservableClass_AttrArgs2())) assert len(vars(restored)) == 1 assert restored.attr_string == "I'm a test string" assert type(restored) == PreservableClass_AttrArgs2 @preserve.preservable class PreservableClass_RestoreInit(PlainClass): def __restore_init__(self): self.attr_int2 = 987 def test_restore_init(): restored = preserve.restore(preserve.preserve(PreservableClass_RestoreInit())) assert restored.attr_int2 == 987 @preserve.preservable class PreservableClass_PickleProtocol(PlainClass): def __setstate__(self, state): self.only_attr = state def __getstate__(self): return "abcd" def test_pickle_protocol(): restored = preserve.restore(preserve.preserve(PreservableClass_PickleProtocol())) assert len(vars(restored)) == 1 assert restored.only_attr == "abcd" def test_non_dict_preservable_state(): preserved = preserve.preserve(PreservableClass_PickleProtocol()) assert preserved[preserve.escaped_state_key] == "abcd" def test_common_preservable_tuple(primitive_data): primitive_data["tuple"] = (1, 2, 3) with pytest.raises(Exception, match="is not preservable"): preserve.restore(preserve.preserve(primitive_data)) preserve.register(preserve.common.tuple) restored = preserve.restore(preserve.preserve(primitive_data)) assert restored["tuple"] == (1, 2, 3) def test_subclass_of_preservable(): class PreservableClassSubclass(PreservableClass): pass # A subclass _shouldn't_ be preservable by default, as if it was restore() would # return an instance of the parent class. with pytest.raises(Exception, match="is not preservable"): preserve.restore(preserve.preserve(PreservableClassSubclass())) def test_subclass_of_raw_preservable(): class RawPreservableSubclass(dict): pass # A subclass _shouldn't_ be preservable by default, as if it was restore() would # return an instance of the parent class. with pytest.raises(Exception, match="is not preservable"): preserve.restore(preserve.preserve(RawPreservableSubclass())) def test_plain_class(): # plain class is not preservable with pytest.raises(Exception, match="is not preservable"): preserve.preserve(PlainClass()) def test_preservable_class(): # Preservable class should restore to a new instance with same content obj = PreservableClass() restored_obj = preserve.restore(preserve.preserve(obj)) assert restored_obj != obj assert obj_attrs_and_type_equal(restored_obj, obj) def test_attr_plain_class(): # Class with non-preservable class as an attribute should fail obj = PreservableClass() obj.attr_obj = PlainClass() with pytest.raises(Exception, match="is not preservable"): preserve.preserve(obj) def test_escape_key(): # Should be able to change the default class key before decorators # and have preserve/restore work old_escape_key = preserve.escape_key preserve.class_key = "A different key" @preserve.preservable class TestClass(PlainClass): pass obj = TestClass() restored_obj = preserve.restore(preserve.preserve(obj)) assert restored_obj != obj assert obj_attrs_and_type_equal(restored_obj, obj) preserve.escape_key = old_escape_key def test_escape_key_in_data(): # Can't use the class key as dict key being preserved with pytest.raises(Exception, match="reserved as an internal escape key"): preserve.preserve({preserve.escape_key: 1}) with pytest.raises(Exception, match="reserved as an internal escape key"): preserve.preserve({preserve.escaped_state_key: 1}) def test_unrestorable(primitive_data): # Shouldn't be able to restore from data containing non-primitive types preserved_data = preserve.preserve(primitive_data) preserved_data["unrestorable"] = (1, 2, 3) with pytest.raises(Exception, match="is not restorable"): preserve.restore(preserved_data) preserved_data["unrestorable"] = PlainClass() with pytest.raises(Exception, match="is not restorable"): preserve.restore(preserved_data) def test_str_dict_keys(primitive_data): # Non-string dict keys aren't restorable or preservable preserved_data = preserve.preserve(primitive_data) primitive_data[2] = "has a non-string key" with pytest.raises(Exception, match="Non-string dictionary keys are not preservable"): preserve.preserve(primitive_data) preserved_data[2] = "has a non-string key" with pytest.raises(Exception, match="Non-string dictionary keys are not restorable"): preserve.restore(preserved_data)