serializable 1.2.0__tar.gz → 1.3.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.
- {serializable-1.2.0 → serializable-1.3.0}/PKG-INFO +8 -2
- {serializable-1.2.0 → serializable-1.3.0}/README.md +3 -0
- {serializable-1.2.0 → serializable-1.3.0}/pyproject.toml +13 -1
- {serializable-1.2.0 → serializable-1.3.0}/serializable/dataclass_serializable.py +10 -6
- {serializable-1.2.0 → serializable-1.3.0}/serializable/helpers.py +20 -11
- serializable-1.3.0/serializable/py.typed +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/serializable/serializable.py +21 -15
- {serializable-1.2.0 → serializable-1.3.0}/serializable/version.py +1 -1
- {serializable-1.2.0 → serializable-1.3.0}/serializable.egg-info/PKG-INFO +8 -2
- {serializable-1.2.0 → serializable-1.3.0}/serializable.egg-info/SOURCES.txt +3 -1
- {serializable-1.2.0 → serializable-1.3.0}/serializable.egg-info/requires.txt +1 -1
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_primitive_values.py +19 -1
- serializable-1.3.0/tests/test_typing.py +48 -0
- {serializable-1.2.0 → serializable-1.3.0}/LICENSE +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/serializable/__init__.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/serializable/primitive_types.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/serializable.egg-info/dependency_links.txt +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/serializable.egg-info/top_level.txt +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/setup.cfg +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/setup.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_class.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_dataclass_serializable.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_default_to_dict_implementation.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_function.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_keyword_aliases.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_namedtuple.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_nesting.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_object.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_pickle.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_set.py +0 -0
- {serializable-1.2.0 → serializable-1.3.0}/tests/test_tuple.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: serializable
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -217,15 +217,18 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
217
217
|
Classifier: Programming Language :: Python :: 3.10
|
|
218
218
|
Classifier: Programming Language :: Python :: 3.11
|
|
219
219
|
Classifier: Programming Language :: Python :: 3.12
|
|
220
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
221
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
222
|
+
Classifier: Typing :: Typed
|
|
220
223
|
Requires-Python: >=3.9
|
|
221
224
|
Description-Content-Type: text/markdown
|
|
222
225
|
License-File: LICENSE
|
|
223
226
|
Requires-Dist: typechecks>=0.0.2
|
|
224
|
-
Requires-Dist: simplejson>=3.0.0
|
|
225
227
|
Provides-Extra: dev
|
|
226
228
|
Requires-Dist: pytest; extra == "dev"
|
|
227
229
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
228
230
|
Requires-Dist: ruff; extra == "dev"
|
|
231
|
+
Requires-Dist: mypy; extra == "dev"
|
|
229
232
|
Requires-Dist: coveralls; extra == "dev"
|
|
230
233
|
Requires-Dist: build; extra == "dev"
|
|
231
234
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -288,6 +291,9 @@ If you change the keyword arguments to a class which derives from `Serializable`
|
|
|
288
291
|
The format records Python classes and modules, which must remain importable when
|
|
289
292
|
loading data. Keys starting with two underscores are reserved. Nested Python
|
|
290
293
|
values require the serialization helpers rather than ordinary JSON encoding.
|
|
294
|
+
`to_json` raises `ValueError` for NaN or infinite floats, which standard JSON
|
|
295
|
+
can't represent; `from_json` still reads the `NaN` / `Infinity` literals which
|
|
296
|
+
older versions wrote.
|
|
291
297
|
|
|
292
298
|
## Documentation
|
|
293
299
|
|
|
@@ -55,6 +55,9 @@ If you change the keyword arguments to a class which derives from `Serializable`
|
|
|
55
55
|
The format records Python classes and modules, which must remain importable when
|
|
56
56
|
loading data. Keys starting with two underscores are reserved. Nested Python
|
|
57
57
|
values require the serialization helpers rather than ordinary JSON encoding.
|
|
58
|
+
`to_json` raises `ValueError` for NaN or infinite floats, which standard JSON
|
|
59
|
+
can't represent; `from_json` still reads the `NaN` / `Infinity` literals which
|
|
60
|
+
older versions wrote.
|
|
58
61
|
|
|
59
62
|
## Documentation
|
|
60
63
|
|
|
@@ -22,11 +22,13 @@ classifiers = [
|
|
|
22
22
|
"Programming Language :: Python :: 3.10",
|
|
23
23
|
"Programming Language :: Python :: 3.11",
|
|
24
24
|
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Typing :: Typed",
|
|
25
28
|
]
|
|
26
29
|
requires-python = ">=3.9"
|
|
27
30
|
dependencies = [
|
|
28
31
|
"typechecks>=0.0.2",
|
|
29
|
-
"simplejson>=3.0.0",
|
|
30
32
|
]
|
|
31
33
|
|
|
32
34
|
[project.optional-dependencies]
|
|
@@ -34,6 +36,7 @@ dev = [
|
|
|
34
36
|
"pytest",
|
|
35
37
|
"pytest-cov",
|
|
36
38
|
"ruff",
|
|
39
|
+
"mypy",
|
|
37
40
|
"coveralls",
|
|
38
41
|
"build",
|
|
39
42
|
"twine",
|
|
@@ -49,6 +52,9 @@ version = {attr = "serializable.version.__version__"}
|
|
|
49
52
|
[tool.setuptools.packages.find]
|
|
50
53
|
include = ["serializable*"]
|
|
51
54
|
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
serializable = ["py.typed"]
|
|
57
|
+
|
|
52
58
|
[tool.ruff]
|
|
53
59
|
target-version = "py39"
|
|
54
60
|
line-length = 100
|
|
@@ -81,6 +87,7 @@ ignore = [
|
|
|
81
87
|
"B905", # zip() without explicit strict
|
|
82
88
|
"SIM108", # use ternary operator instead of if-else
|
|
83
89
|
"UP007", # use X | Y for type unions (need Python 3.10+)
|
|
90
|
+
"UP045", # use X | None for Optional (need Python 3.10+)
|
|
84
91
|
]
|
|
85
92
|
|
|
86
93
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -96,6 +103,11 @@ ignore = [
|
|
|
96
103
|
[tool.ruff.lint.isort]
|
|
97
104
|
known-first-party = ["serializable"]
|
|
98
105
|
|
|
106
|
+
[tool.mypy]
|
|
107
|
+
# Annotations are never evaluated at runtime (`from __future__ import annotations`),
|
|
108
|
+
# so checking them as 3.10 also covers 3.9, which mypy 2 can no longer target.
|
|
109
|
+
python_version = "3.10"
|
|
110
|
+
|
|
99
111
|
[tool.pytest.ini_options]
|
|
100
112
|
testpaths = ["tests"]
|
|
101
113
|
python_files = "test_*.py"
|
|
@@ -40,11 +40,14 @@ Example::
|
|
|
40
40
|
|
|
41
41
|
from __future__ import annotations
|
|
42
42
|
|
|
43
|
+
from collections.abc import Mapping
|
|
43
44
|
from dataclasses import fields
|
|
44
|
-
from typing import Any, ClassVar
|
|
45
|
+
from typing import Any, ClassVar, Optional, TypeVar
|
|
45
46
|
|
|
46
47
|
from .helpers import from_json, to_json
|
|
47
48
|
|
|
49
|
+
_T = TypeVar("_T", bound="DataclassSerializable")
|
|
50
|
+
|
|
48
51
|
|
|
49
52
|
class DataclassSerializable:
|
|
50
53
|
"""Mixin providing ``to_dict`` / ``from_dict`` / ``to_json`` / ``from_json``
|
|
@@ -57,16 +60,17 @@ class DataclassSerializable:
|
|
|
57
60
|
``Serializable``.
|
|
58
61
|
"""
|
|
59
62
|
|
|
60
|
-
_SERIALIZABLE_KEYWORD_ALIASES: ClassVar[dict[str, str
|
|
63
|
+
_SERIALIZABLE_KEYWORD_ALIASES: ClassVar[dict[str, Optional[str]]] = {}
|
|
61
64
|
|
|
62
65
|
def to_dict(self) -> dict[str, Any]:
|
|
63
66
|
"""Return a dict mapping each dataclass field name to its current
|
|
64
67
|
value. Keys match the ``__init__`` keyword arguments, so
|
|
65
68
|
``cls(**obj.to_dict())`` reconstructs an equal instance."""
|
|
66
|
-
|
|
69
|
+
# subclasses are dataclasses, the mixin itself isn't
|
|
70
|
+
return {f.name: getattr(self, f.name) for f in fields(self)} # type: ignore[arg-type]
|
|
67
71
|
|
|
68
72
|
@classmethod
|
|
69
|
-
def from_dict(cls, state_dict:
|
|
73
|
+
def from_dict(cls: type[_T], state_dict: Mapping[str, Any]) -> _T:
|
|
70
74
|
"""Reconstruct an instance from a ``to_dict``-shaped dictionary,
|
|
71
75
|
applying ``_SERIALIZABLE_KEYWORD_ALIASES`` for backwards compat."""
|
|
72
76
|
kwargs = dict(state_dict)
|
|
@@ -83,10 +87,10 @@ class DataclassSerializable:
|
|
|
83
87
|
return to_json(self)
|
|
84
88
|
|
|
85
89
|
@classmethod
|
|
86
|
-
def from_json(cls, json_string: str):
|
|
90
|
+
def from_json(cls: type[_T], json_string: str) -> _T:
|
|
87
91
|
return from_json(json_string)
|
|
88
92
|
|
|
89
|
-
def __reduce__(self):
|
|
93
|
+
def __reduce__(self) -> tuple[Any, ...]:
|
|
90
94
|
"""Pickle via the same to_dict / from_dict path used for JSON so
|
|
91
95
|
pickled objects round-trip even when field order or internal
|
|
92
96
|
representation changes between releases. Pickles written before
|
|
@@ -15,15 +15,17 @@ Helper functions for deconstructing classes, functions, and user-defined
|
|
|
15
15
|
objects into serializable types.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
18
21
|
from functools import lru_cache
|
|
19
22
|
from types import BuiltinFunctionType, FunctionType
|
|
20
|
-
|
|
21
|
-
import simplejson as json
|
|
23
|
+
from typing import Any, Union
|
|
22
24
|
|
|
23
25
|
from .primitive_types import PRIMITIVE_TYPES
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
def init_arg_names(obj):
|
|
28
|
+
def init_arg_names(obj: Any) -> tuple[str, ...]:
|
|
27
29
|
"""
|
|
28
30
|
Names of the positional arguments to the __init__ method of this
|
|
29
31
|
object's class (excluding self).
|
|
@@ -31,7 +33,8 @@ def init_arg_names(obj):
|
|
|
31
33
|
Keyword-only arguments are deliberately excluded, so a class can accept
|
|
32
34
|
constructor options which aren't part of its serialized state.
|
|
33
35
|
"""
|
|
34
|
-
|
|
36
|
+
# mypy doesn't consider classes Hashable
|
|
37
|
+
return _class_init_arg_names(type(obj)) # type: ignore[arg-type]
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
# Cached per class since this is called on every to_dict/__eq__/__hash__ of
|
|
@@ -53,7 +56,7 @@ def _class_init_arg_names(cls):
|
|
|
53
56
|
return init_code.co_varnames[1 : init_code.co_argcount]
|
|
54
57
|
|
|
55
58
|
|
|
56
|
-
def simple_object_to_dict(self):
|
|
59
|
+
def simple_object_to_dict(self: object) -> dict[str, Any]:
|
|
57
60
|
return {name: getattr(self, name) for name in init_arg_names(self)}
|
|
58
61
|
|
|
59
62
|
|
|
@@ -246,7 +249,7 @@ def list_to_serializable_repr(x):
|
|
|
246
249
|
return [to_serializable_repr(element) for element in x]
|
|
247
250
|
|
|
248
251
|
|
|
249
|
-
def to_dict(obj):
|
|
252
|
+
def to_dict(obj: Any) -> dict[str, Any]:
|
|
250
253
|
"""
|
|
251
254
|
If value isn't a primitive scalar or collection then it needs to
|
|
252
255
|
either implement to_dict (instances of Serializable) or have member
|
|
@@ -262,7 +265,7 @@ def to_dict(obj):
|
|
|
262
265
|
raise ValueError(f"Cannot convert {obj} : {type(obj)} to dictionary") from exc
|
|
263
266
|
|
|
264
267
|
|
|
265
|
-
def to_serializable_repr(x):
|
|
268
|
+
def to_serializable_repr(x: Any) -> Any:
|
|
266
269
|
"""
|
|
267
270
|
Convert an instance of Serializable or a primitive collection containing
|
|
268
271
|
such instances into serializable types.
|
|
@@ -288,7 +291,7 @@ def to_serializable_repr(x):
|
|
|
288
291
|
return state_dictionary
|
|
289
292
|
|
|
290
293
|
|
|
291
|
-
def from_serializable_repr(x):
|
|
294
|
+
def from_serializable_repr(x: Any) -> Any:
|
|
292
295
|
"""
|
|
293
296
|
Inverse of to_serializable_repr. Does not modify `x`.
|
|
294
297
|
"""
|
|
@@ -302,17 +305,23 @@ def from_serializable_repr(x):
|
|
|
302
305
|
raise TypeError(f"Cannot convert {x} : {type(x)} from serializable representation to object")
|
|
303
306
|
|
|
304
307
|
|
|
305
|
-
def to_json(x):
|
|
308
|
+
def to_json(x: Any) -> str:
|
|
306
309
|
"""
|
|
307
310
|
Returns JSON representation of a given Serializable instance or
|
|
308
311
|
other primitive object.
|
|
312
|
+
|
|
313
|
+
Raises ValueError for NaN or infinite floats, which have no standard
|
|
314
|
+
JSON representation.
|
|
309
315
|
"""
|
|
310
|
-
return json.dumps(to_serializable_repr(x))
|
|
316
|
+
return json.dumps(to_serializable_repr(x), allow_nan=False)
|
|
311
317
|
|
|
312
318
|
|
|
313
|
-
def from_json(json_string):
|
|
319
|
+
def from_json(json_string: Union[str, bytes]) -> Any:
|
|
314
320
|
"""
|
|
315
321
|
Inverse of to_json. Objects are reconstructed bottom-up while parsing,
|
|
316
322
|
equivalent to (but faster than) from_serializable_repr(json.loads(...)).
|
|
323
|
+
|
|
324
|
+
Accepts the NaN and Infinity literals which older versions of
|
|
325
|
+
serializable wrote.
|
|
317
326
|
"""
|
|
318
327
|
return json.loads(json_string, object_hook=_from_reconstructed_dict)
|
|
File without changes
|
|
@@ -11,10 +11,16 @@
|
|
|
11
11
|
# limitations under the License.
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from typing import Any, ClassVar, Optional, TypeVar, Union
|
|
15
19
|
|
|
16
20
|
from .helpers import from_json, simple_object_to_dict, to_json
|
|
17
21
|
|
|
22
|
+
_T = TypeVar("_T", bound="Serializable")
|
|
23
|
+
|
|
18
24
|
|
|
19
25
|
class Serializable:
|
|
20
26
|
"""
|
|
@@ -24,17 +30,17 @@ class Serializable:
|
|
|
24
30
|
Relies on the keys of to_dict() matching the arguments to __init__.
|
|
25
31
|
"""
|
|
26
32
|
|
|
27
|
-
def __str__(self):
|
|
33
|
+
def __str__(self) -> str:
|
|
28
34
|
fields = ", ".join(f"{k}={v}" for (k, v) in self.to_dict().items())
|
|
29
35
|
return f"{self.__class__.__name__}({fields})"
|
|
30
36
|
|
|
31
|
-
def __repr__(self):
|
|
37
|
+
def __repr__(self) -> str:
|
|
32
38
|
return str(self)
|
|
33
39
|
|
|
34
|
-
def __eq__(self, other):
|
|
40
|
+
def __eq__(self, other) -> bool:
|
|
35
41
|
return self.__class__ is other.__class__ and self.to_dict() == other.to_dict()
|
|
36
42
|
|
|
37
|
-
def to_dict(self):
|
|
43
|
+
def to_dict(self) -> dict[str, Any]:
|
|
38
44
|
"""
|
|
39
45
|
Returns a dictionary which can be used to reconstruct an instance
|
|
40
46
|
of a derived class (typically by matching args to __init__). The values
|
|
@@ -49,11 +55,11 @@ class Serializable:
|
|
|
49
55
|
"""
|
|
50
56
|
return simple_object_to_dict(self)
|
|
51
57
|
|
|
52
|
-
def __hash__(self):
|
|
58
|
+
def __hash__(self) -> int:
|
|
53
59
|
return hash(tuple(sorted(self.to_dict().items())))
|
|
54
60
|
|
|
55
61
|
@classmethod
|
|
56
|
-
def _reconstruct_nested_objects(cls, state_dict):
|
|
62
|
+
def _reconstruct_nested_objects(cls, state_dict: dict[str, Any]) -> dict[str, Any]:
|
|
57
63
|
"""
|
|
58
64
|
Nested serializable objects will be represented as dictionaries so we
|
|
59
65
|
allow manual reconstruction of those objects in this method.
|
|
@@ -64,10 +70,10 @@ class Serializable:
|
|
|
64
70
|
|
|
65
71
|
# dictionary mapping old keywords to either new names or
|
|
66
72
|
# None if the keyword has been removed from a class
|
|
67
|
-
_SERIALIZABLE_KEYWORD_ALIASES: ClassVar[dict] = {}
|
|
73
|
+
_SERIALIZABLE_KEYWORD_ALIASES: ClassVar[dict[str, Optional[str]]] = {}
|
|
68
74
|
|
|
69
75
|
@classmethod
|
|
70
|
-
def _update_kwargs(cls, kwargs):
|
|
76
|
+
def _update_kwargs(cls, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
71
77
|
"""
|
|
72
78
|
Rename any old keyword arguments to preserve backwards compatibility
|
|
73
79
|
"""
|
|
@@ -83,7 +89,7 @@ class Serializable:
|
|
|
83
89
|
return kwargs
|
|
84
90
|
|
|
85
91
|
@classmethod
|
|
86
|
-
def from_dict(cls, state_dict):
|
|
92
|
+
def from_dict(cls: type[_T], state_dict: Mapping[str, Any]) -> _T:
|
|
87
93
|
"""
|
|
88
94
|
Given a dictionary of flattened fields (result of calling to_dict()),
|
|
89
95
|
returns an instance. Does not modify `state_dict`.
|
|
@@ -91,20 +97,20 @@ class Serializable:
|
|
|
91
97
|
state_dict = cls._reconstruct_nested_objects(dict(state_dict))
|
|
92
98
|
return cls(**cls._update_kwargs(state_dict))
|
|
93
99
|
|
|
94
|
-
def to_json(self):
|
|
100
|
+
def to_json(self) -> str:
|
|
95
101
|
"""
|
|
96
102
|
Returns a string containing a JSON representation of this object.
|
|
97
103
|
"""
|
|
98
104
|
return to_json(self)
|
|
99
105
|
|
|
100
106
|
@classmethod
|
|
101
|
-
def from_json(cls, json_string):
|
|
107
|
+
def from_json(cls: type[_T], json_string: str) -> _T:
|
|
102
108
|
"""
|
|
103
109
|
Reconstruct an instance from a JSON string.
|
|
104
110
|
"""
|
|
105
111
|
return from_json(json_string)
|
|
106
112
|
|
|
107
|
-
def write_json_file(self, path):
|
|
113
|
+
def write_json_file(self, path: Union[str, os.PathLike[str]]) -> None:
|
|
108
114
|
"""
|
|
109
115
|
Serialize this object to JSON and write it to the text file at `path`.
|
|
110
116
|
"""
|
|
@@ -112,7 +118,7 @@ class Serializable:
|
|
|
112
118
|
f.write(self.to_json())
|
|
113
119
|
|
|
114
120
|
@classmethod
|
|
115
|
-
def read_json_file(cls, path):
|
|
121
|
+
def read_json_file(cls: type[_T], path: Union[str, os.PathLike[str]]) -> _T:
|
|
116
122
|
"""
|
|
117
123
|
Construct an instance of this class from the JSON file at `path`.
|
|
118
124
|
"""
|
|
@@ -120,7 +126,7 @@ class Serializable:
|
|
|
120
126
|
json_string = f.read()
|
|
121
127
|
return cls.from_json(json_string)
|
|
122
128
|
|
|
123
|
-
def __reduce__(self):
|
|
129
|
+
def __reduce__(self) -> tuple[Any, ...]:
|
|
124
130
|
"""
|
|
125
131
|
Overriding this method directs the default pickler to reconstruct
|
|
126
132
|
this object using our from_dict method, so pickles survive the same
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: serializable
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -217,15 +217,18 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
217
217
|
Classifier: Programming Language :: Python :: 3.10
|
|
218
218
|
Classifier: Programming Language :: Python :: 3.11
|
|
219
219
|
Classifier: Programming Language :: Python :: 3.12
|
|
220
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
221
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
222
|
+
Classifier: Typing :: Typed
|
|
220
223
|
Requires-Python: >=3.9
|
|
221
224
|
Description-Content-Type: text/markdown
|
|
222
225
|
License-File: LICENSE
|
|
223
226
|
Requires-Dist: typechecks>=0.0.2
|
|
224
|
-
Requires-Dist: simplejson>=3.0.0
|
|
225
227
|
Provides-Extra: dev
|
|
226
228
|
Requires-Dist: pytest; extra == "dev"
|
|
227
229
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
228
230
|
Requires-Dist: ruff; extra == "dev"
|
|
231
|
+
Requires-Dist: mypy; extra == "dev"
|
|
229
232
|
Requires-Dist: coveralls; extra == "dev"
|
|
230
233
|
Requires-Dist: build; extra == "dev"
|
|
231
234
|
Requires-Dist: twine; extra == "dev"
|
|
@@ -288,6 +291,9 @@ If you change the keyword arguments to a class which derives from `Serializable`
|
|
|
288
291
|
The format records Python classes and modules, which must remain importable when
|
|
289
292
|
loading data. Keys starting with two underscores are reserved. Nested Python
|
|
290
293
|
values require the serialization helpers rather than ordinary JSON encoding.
|
|
294
|
+
`to_json` raises `ValueError` for NaN or infinite floats, which standard JSON
|
|
295
|
+
can't represent; `from_json` still reads the `NaN` / `Infinity` literals which
|
|
296
|
+
older versions wrote.
|
|
291
297
|
|
|
292
298
|
## Documentation
|
|
293
299
|
|
|
@@ -6,6 +6,7 @@ serializable/__init__.py
|
|
|
6
6
|
serializable/dataclass_serializable.py
|
|
7
7
|
serializable/helpers.py
|
|
8
8
|
serializable/primitive_types.py
|
|
9
|
+
serializable/py.typed
|
|
9
10
|
serializable/serializable.py
|
|
10
11
|
serializable/version.py
|
|
11
12
|
serializable.egg-info/PKG-INFO
|
|
@@ -24,4 +25,5 @@ tests/test_object.py
|
|
|
24
25
|
tests/test_pickle.py
|
|
25
26
|
tests/test_primitive_values.py
|
|
26
27
|
tests/test_set.py
|
|
27
|
-
tests/test_tuple.py
|
|
28
|
+
tests/test_tuple.py
|
|
29
|
+
tests/test_typing.py
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
# See the License for the specific language governing permissions and
|
|
11
11
|
# limitations under the License.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from serializable import from_json, from_serializable_repr, to_json, to_serializable_repr
|
|
14
18
|
|
|
15
19
|
from .common import eq_
|
|
16
20
|
|
|
@@ -29,3 +33,17 @@ def test_bool():
|
|
|
29
33
|
|
|
30
34
|
def test_str():
|
|
31
35
|
eq_("waffles", from_serializable_repr(to_serializable_repr("waffles")))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("value", [math.nan, math.inf, -math.inf])
|
|
39
|
+
def test_to_json_rejects_non_finite_floats(value):
|
|
40
|
+
# NaN and infinities have no standard JSON representation
|
|
41
|
+
with pytest.raises(ValueError, match="Out of range float values"):
|
|
42
|
+
to_json([value])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_from_json_accepts_non_finite_literals():
|
|
46
|
+
# serializable used to write these (with simplejson < 3.19)
|
|
47
|
+
values = from_json("[NaN, Infinity, -Infinity]")
|
|
48
|
+
assert math.isnan(values[0])
|
|
49
|
+
eq_(values[1:], [math.inf, -math.inf])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
import inspect
|
|
14
|
+
import typing
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
|
|
19
|
+
from serializable import DataclassSerializable, Serializable, helpers
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Point(Serializable):
|
|
23
|
+
def __init__(self, x):
|
|
24
|
+
self.x = x
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class DataclassPoint(DataclassSerializable):
|
|
29
|
+
x: int
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def annotated_objects():
|
|
33
|
+
yield Point
|
|
34
|
+
yield DataclassPoint
|
|
35
|
+
for cls in (Serializable, DataclassSerializable):
|
|
36
|
+
for _, method in inspect.getmembers(cls, callable):
|
|
37
|
+
if (getattr(method, "__module__", None) or "").startswith("serializable"):
|
|
38
|
+
yield method
|
|
39
|
+
for _, function in inspect.getmembers(helpers, inspect.isfunction):
|
|
40
|
+
if function.__module__ == helpers.__name__:
|
|
41
|
+
yield function
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize("obj", list(annotated_objects()), ids=lambda obj: obj.__qualname__)
|
|
45
|
+
def test_annotations_evaluate_at_runtime(obj):
|
|
46
|
+
# annotations must stay evaluable on the oldest supported Python (no
|
|
47
|
+
# `X | Y` unions before 3.10) for tools which call get_type_hints
|
|
48
|
+
typing.get_type_hints(obj)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|