20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 | class PythonObjectJson(object):
"""Base Python Object with JSON serialization and deserialization compatibility."""
def __init__(self, **kwargs):
"""Instantiate the PythonObjectJson class with all keyword arguments.
Args:
kwargs (dict): Key/value pairs to be passed to the PythonObjectJson class.
"""
vars(self).update(kwargs)
def __str__(self):
return self.to_json_str()
def __repr__(self):
return (
f"{derive_custom_object_key(self.__class__, as_lower=False)}"
f"({','.join([f'{k}={v}' for k, v in vars(self).items()])})"
)
def __eq__(self, other):
return type(self) is type(other) and vars(self) == vars(other)
def _base_subclasses(self) -> Dict[str, Type]:
"""Create dict with snakecase keys derived from custom object type camelcase class names.
Returns:
dict[str, Type]: Dictionary with snakecase strings of all subclasses of PythonObjectJson as keys and
subclasses as values.
"""
# retrieve all class subclasses after base class
return {derive_custom_object_key(cls): cls for cls in self.__class__.__mro__[-2].__subclasses__()}
def serialize(self) -> Dict[str, Any]:
"""Class method to serialize the class instance into a serializable dictionary.
Returns:
dict[str, Any]: Serializable dictionary.
"""
return serialize(self, list(self._base_subclasses().values()))
def to_json_str(self) -> str:
"""Serialize the class object to a JSON string.
Returns:
str: JSON string derived from the serializable version of the class object.
"""
return json.dumps(self.serialize(), ensure_ascii=False, indent=2)
def from_json_str(self, json_str: str) -> None:
"""Load the class object from a JSON string.
Args:
json_str (str): JSON string to be deserialized into the class object.
Returns:
None
"""
loaded_class_instance = deserialize(json.loads(json_str), self._base_subclasses())
# update the class instance attributes with the attributes from the loaded class instance
vars(self).update(**vars(loaded_class_instance))
def save_to_json_file(self, json_file_path: Path) -> None:
"""Save the class object to a JSON file.
Args:
json_file_path (Path): Target JSON file path to which the class object will be saved.
Returns:
None
"""
if not json_file_path.exists():
json_file_path.parent.mkdir(parents=True, exist_ok=True)
with open(json_file_path, "w", encoding="utf-8") as json_file_out:
# TODO: fix incorrect file input type warning for json.dump from PyCharm bug https://youtrack.jetbrains.com/issue/PY-73050/openfile.txt-r-return-type-should-be-inferred-as-TextIOWrapper-instead-of-TextIO
# noinspection PyTypeChecker
json.dump(self.serialize(), json_file_out, ensure_ascii=False, indent=2)
def load_from_json_file(self, json_file_path: Path) -> None:
"""Load the class object from a JSON file.
Args:
json_file_path (Path): Target JSON file path from which the class object will be loaded.
Returns:
None
"""
if not json_file_path.exists():
raise FileNotFoundError(f"File {json_file_path} does not exist. Unable to load saved data.")
with open(json_file_path, "r", encoding="utf-8") as json_file_in:
loaded_class_instance = deserialize(json.load(json_file_in), self._base_subclasses())
vars(self).update(**vars(loaded_class_instance))
|