serializable 1.3.4__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {serializable-1.3.4/serializable.egg-info → serializable-1.4.0}/PKG-INFO +1 -1
  2. {serializable-1.3.4 → serializable-1.4.0}/serializable/dataclass_serializable.py +15 -2
  3. {serializable-1.3.4 → serializable-1.4.0}/serializable/serializable.py +15 -2
  4. {serializable-1.3.4 → serializable-1.4.0}/serializable/version.py +1 -1
  5. {serializable-1.3.4 → serializable-1.4.0/serializable.egg-info}/PKG-INFO +1 -1
  6. {serializable-1.3.4 → serializable-1.4.0}/tests/test_dataclass_serializable.py +24 -0
  7. {serializable-1.3.4 → serializable-1.4.0}/tests/test_object.py +39 -0
  8. {serializable-1.3.4 → serializable-1.4.0}/LICENSE +0 -0
  9. {serializable-1.3.4 → serializable-1.4.0}/MANIFEST.in +0 -0
  10. {serializable-1.3.4 → serializable-1.4.0}/README.md +0 -0
  11. {serializable-1.3.4 → serializable-1.4.0}/pyproject.toml +0 -0
  12. {serializable-1.3.4 → serializable-1.4.0}/serializable/__init__.py +0 -0
  13. {serializable-1.3.4 → serializable-1.4.0}/serializable/helpers.py +0 -0
  14. {serializable-1.3.4 → serializable-1.4.0}/serializable/keyword_aliases.py +0 -0
  15. {serializable-1.3.4 → serializable-1.4.0}/serializable/primitive_types.py +0 -0
  16. {serializable-1.3.4 → serializable-1.4.0}/serializable/py.typed +0 -0
  17. {serializable-1.3.4 → serializable-1.4.0}/serializable.egg-info/SOURCES.txt +0 -0
  18. {serializable-1.3.4 → serializable-1.4.0}/serializable.egg-info/dependency_links.txt +0 -0
  19. {serializable-1.3.4 → serializable-1.4.0}/serializable.egg-info/requires.txt +0 -0
  20. {serializable-1.3.4 → serializable-1.4.0}/serializable.egg-info/top_level.txt +0 -0
  21. {serializable-1.3.4 → serializable-1.4.0}/setup.cfg +0 -0
  22. {serializable-1.3.4 → serializable-1.4.0}/tests/__init__.py +0 -0
  23. {serializable-1.3.4 → serializable-1.4.0}/tests/common.py +0 -0
  24. {serializable-1.3.4 → serializable-1.4.0}/tests/test_class.py +0 -0
  25. {serializable-1.3.4 → serializable-1.4.0}/tests/test_default_to_dict_implementation.py +0 -0
  26. {serializable-1.3.4 → serializable-1.4.0}/tests/test_function.py +0 -0
  27. {serializable-1.3.4 → serializable-1.4.0}/tests/test_keyword_aliases.py +0 -0
  28. {serializable-1.3.4 → serializable-1.4.0}/tests/test_namedtuple.py +0 -0
  29. {serializable-1.3.4 → serializable-1.4.0}/tests/test_nesting.py +0 -0
  30. {serializable-1.3.4 → serializable-1.4.0}/tests/test_pickle.py +0 -0
  31. {serializable-1.3.4 → serializable-1.4.0}/tests/test_primitive_values.py +0 -0
  32. {serializable-1.3.4 → serializable-1.4.0}/tests/test_set.py +0 -0
  33. {serializable-1.3.4 → serializable-1.4.0}/tests/test_tuple.py +0 -0
  34. {serializable-1.3.4 → serializable-1.4.0}/tests/test_typing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: serializable
3
- Version: 1.3.4
3
+ Version: 1.4.0
4
4
  Summary: Base class with serialization helpers for user-defined Python objects
5
5
  Author-email: Alex Rubinsteyn <alex.rubinsteyn@unc.edu>
6
6
  License: Apache License
@@ -82,8 +82,21 @@ class DataclassSerializable:
82
82
 
83
83
  @classmethod
84
84
  def from_json(cls: type[_T], json_string: str) -> _T:
85
- """Reconstruct an object from a string produced by ``to_json``."""
86
- return from_json(json_string)
85
+ """Reconstruct an instance of this class, or of a subclass, from a
86
+ string produced by ``to_json``. The class built is the one the JSON
87
+ names.
88
+
89
+ Raises ``ValueError`` if the JSON holds anything else, such as
90
+ another class or a plain dict. Use the module-level
91
+ ``serializable.from_json`` to load JSON whose type isn't known in
92
+ advance."""
93
+ obj = from_json(json_string)
94
+ if not isinstance(obj, cls):
95
+ raise ValueError(
96
+ f"{cls.__name__}.from_json: the JSON holds {type(obj).__name__}, "
97
+ f"not {cls.__name__}; use serializable.from_json to load any type"
98
+ )
99
+ return obj
87
100
 
88
101
  def __reduce__(self) -> tuple[Any, ...]:
89
102
  """Pickle via the same to_dict / from_dict path used for JSON so
@@ -100,9 +100,20 @@ class Serializable:
100
100
  @classmethod
101
101
  def from_json(cls: type[_T], json_string: str) -> _T:
102
102
  """
103
- Reconstruct an instance from a JSON string.
103
+ Reconstruct an instance of this class, or of a subclass, from a JSON
104
+ string written by to_json. The class built is the one the JSON names.
105
+
106
+ Raises ValueError if the JSON holds anything else, such as another
107
+ class or a plain dict. Use the module-level serializable.from_json to
108
+ load JSON whose type isn't known in advance.
104
109
  """
105
- return from_json(json_string)
110
+ obj = from_json(json_string)
111
+ if not isinstance(obj, cls):
112
+ raise ValueError(
113
+ f"{cls.__name__}.from_json: the JSON holds {type(obj).__name__}, "
114
+ f"not {cls.__name__}; use serializable.from_json to load any type"
115
+ )
116
+ return obj
106
117
 
107
118
  def write_json_file(self, path: Union[str, os.PathLike[str]]) -> None:
108
119
  """
@@ -115,6 +126,8 @@ class Serializable:
115
126
  def read_json_file(cls: type[_T], path: Union[str, os.PathLike[str]]) -> _T:
116
127
  """
117
128
  Construct an instance of this class from the JSON file at `path`.
129
+
130
+ Raises ValueError, as from_json does, if the file holds another type.
118
131
  """
119
132
  with open(path) as f:
120
133
  json_string = f.read()
@@ -1,4 +1,4 @@
1
- __version__ = "1.3.4"
1
+ __version__ = "1.4.0"
2
2
 
3
3
 
4
4
  def print_version():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: serializable
3
- Version: 1.3.4
3
+ Version: 1.4.0
4
4
  Summary: Base class with serialization helpers for user-defined Python objects
5
5
  Author-email: Alex Rubinsteyn <alex.rubinsteyn@unc.edu>
6
6
  License: Apache License
@@ -182,3 +182,27 @@ def test_interop_with_legacy_serializable():
182
182
  def test_nested_dataclass_serializable_roundtrip():
183
183
  o = Outer(name="parent", inner=Inner(n=7))
184
184
  eq_(Outer.from_json(o.to_json()), o)
185
+
186
+
187
+ @dataclass
188
+ class LabeledPoint(Point):
189
+ label: str = ""
190
+
191
+
192
+ def test_from_json_returns_subclass_named_in_json():
193
+ p = LabeledPoint(1.0, 2.0, label="a")
194
+ restored = Point.from_json(p.to_json())
195
+ eq_(type(restored), LabeledPoint)
196
+ eq_(restored, p)
197
+
198
+
199
+ def test_from_json_rejects_other_class():
200
+ person_json = Person(name="Ada", age=36).to_json()
201
+ with pytest.raises(ValueError, match="holds Person, not Point"):
202
+ Point.from_json(person_json)
203
+ eq_(from_json(person_json), Person(name="Ada", age=36))
204
+
205
+
206
+ def test_from_json_rejects_json_without_class():
207
+ with pytest.raises(ValueError, match="holds dict, not Point"):
208
+ Point.from_json('{"x": 1.0, "y": 2.0}')
@@ -12,9 +12,13 @@
12
12
 
13
13
  import pickle
14
14
 
15
+ import pytest
16
+
15
17
  from serializable import (
16
18
  Serializable,
19
+ from_json,
17
20
  from_serializable_repr,
21
+ to_json,
18
22
  to_serializable_repr,
19
23
  )
20
24
 
@@ -30,6 +34,15 @@ class A(Serializable):
30
34
  return {"x": self.x, "y": self.y}
31
35
 
32
36
 
37
+ class B(A):
38
+ pass
39
+
40
+
41
+ class Other(Serializable):
42
+ def __init__(self, x):
43
+ self.x = x
44
+
45
+
33
46
  instance = A(1, 2)
34
47
 
35
48
 
@@ -50,3 +63,29 @@ def test_object_json_file(tmp_path):
50
63
  path = tmp_path / "instance.json"
51
64
  instance.write_json_file(path)
52
65
  eq_(instance, A.read_json_file(path))
66
+
67
+
68
+ def test_from_json_returns_subclass_named_in_json():
69
+ derived = B(1, 2)
70
+ restored = A.from_json(derived.to_json())
71
+ eq_(type(restored), B)
72
+ eq_(restored, derived)
73
+
74
+
75
+ def test_from_json_rejects_other_class():
76
+ other_json = Other(1).to_json()
77
+ with pytest.raises(ValueError, match="holds Other, not A"):
78
+ A.from_json(other_json)
79
+ eq_(from_json(other_json), Other(1))
80
+
81
+
82
+ def test_from_json_rejects_json_without_class():
83
+ with pytest.raises(ValueError, match="holds dict, not A"):
84
+ A.from_json(to_json({"x": 1, "y": 2}))
85
+
86
+
87
+ def test_read_json_file_rejects_other_class(tmp_path):
88
+ path = tmp_path / "other.json"
89
+ Other(1).write_json_file(path)
90
+ with pytest.raises(ValueError, match="holds Other, not A"):
91
+ A.read_json_file(path)
File without changes
File without changes
File without changes
File without changes