serializable 1.2.0__tar.gz → 1.2.1__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 (31) hide show
  1. {serializable-1.2.0 → serializable-1.2.1}/PKG-INFO +5 -1
  2. {serializable-1.2.0 → serializable-1.2.1}/pyproject.toml +13 -0
  3. {serializable-1.2.0 → serializable-1.2.1}/serializable/dataclass_serializable.py +10 -6
  4. {serializable-1.2.0 → serializable-1.2.1}/serializable/helpers.py +13 -9
  5. serializable-1.2.1/serializable/py.typed +0 -0
  6. {serializable-1.2.0 → serializable-1.2.1}/serializable/serializable.py +21 -15
  7. {serializable-1.2.0 → serializable-1.2.1}/serializable/version.py +1 -1
  8. {serializable-1.2.0 → serializable-1.2.1}/serializable.egg-info/PKG-INFO +5 -1
  9. {serializable-1.2.0 → serializable-1.2.1}/serializable.egg-info/SOURCES.txt +3 -1
  10. {serializable-1.2.0 → serializable-1.2.1}/serializable.egg-info/requires.txt +1 -0
  11. serializable-1.2.1/tests/test_typing.py +48 -0
  12. {serializable-1.2.0 → serializable-1.2.1}/LICENSE +0 -0
  13. {serializable-1.2.0 → serializable-1.2.1}/README.md +0 -0
  14. {serializable-1.2.0 → serializable-1.2.1}/serializable/__init__.py +0 -0
  15. {serializable-1.2.0 → serializable-1.2.1}/serializable/primitive_types.py +0 -0
  16. {serializable-1.2.0 → serializable-1.2.1}/serializable.egg-info/dependency_links.txt +0 -0
  17. {serializable-1.2.0 → serializable-1.2.1}/serializable.egg-info/top_level.txt +0 -0
  18. {serializable-1.2.0 → serializable-1.2.1}/setup.cfg +0 -0
  19. {serializable-1.2.0 → serializable-1.2.1}/setup.py +0 -0
  20. {serializable-1.2.0 → serializable-1.2.1}/tests/test_class.py +0 -0
  21. {serializable-1.2.0 → serializable-1.2.1}/tests/test_dataclass_serializable.py +0 -0
  22. {serializable-1.2.0 → serializable-1.2.1}/tests/test_default_to_dict_implementation.py +0 -0
  23. {serializable-1.2.0 → serializable-1.2.1}/tests/test_function.py +0 -0
  24. {serializable-1.2.0 → serializable-1.2.1}/tests/test_keyword_aliases.py +0 -0
  25. {serializable-1.2.0 → serializable-1.2.1}/tests/test_namedtuple.py +0 -0
  26. {serializable-1.2.0 → serializable-1.2.1}/tests/test_nesting.py +0 -0
  27. {serializable-1.2.0 → serializable-1.2.1}/tests/test_object.py +0 -0
  28. {serializable-1.2.0 → serializable-1.2.1}/tests/test_pickle.py +0 -0
  29. {serializable-1.2.0 → serializable-1.2.1}/tests/test_primitive_values.py +0 -0
  30. {serializable-1.2.0 → serializable-1.2.1}/tests/test_set.py +0 -0
  31. {serializable-1.2.0 → serializable-1.2.1}/tests/test_tuple.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: serializable
3
- Version: 1.2.0
3
+ Version: 1.2.1
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,6 +217,9 @@ 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
@@ -226,6 +229,7 @@ Provides-Extra: dev
226
229
  Requires-Dist: pytest; extra == "dev"
227
230
  Requires-Dist: pytest-cov; extra == "dev"
228
231
  Requires-Dist: ruff; extra == "dev"
232
+ Requires-Dist: mypy; extra == "dev"
229
233
  Requires-Dist: coveralls; extra == "dev"
230
234
  Requires-Dist: build; extra == "dev"
231
235
  Requires-Dist: twine; extra == "dev"
@@ -22,6 +22,9 @@ 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 = [
@@ -34,6 +37,7 @@ dev = [
34
37
  "pytest",
35
38
  "pytest-cov",
36
39
  "ruff",
40
+ "mypy",
37
41
  "coveralls",
38
42
  "build",
39
43
  "twine",
@@ -49,6 +53,9 @@ version = {attr = "serializable.version.__version__"}
49
53
  [tool.setuptools.packages.find]
50
54
  include = ["serializable*"]
51
55
 
56
+ [tool.setuptools.package-data]
57
+ serializable = ["py.typed"]
58
+
52
59
  [tool.ruff]
53
60
  target-version = "py39"
54
61
  line-length = 100
@@ -81,6 +88,7 @@ ignore = [
81
88
  "B905", # zip() without explicit strict
82
89
  "SIM108", # use ternary operator instead of if-else
83
90
  "UP007", # use X | Y for type unions (need Python 3.10+)
91
+ "UP045", # use X | None for Optional (need Python 3.10+)
84
92
  ]
85
93
 
86
94
  [tool.ruff.lint.per-file-ignores]
@@ -96,6 +104,11 @@ ignore = [
96
104
  [tool.ruff.lint.isort]
97
105
  known-first-party = ["serializable"]
98
106
 
107
+ [tool.mypy]
108
+ # Annotations are never evaluated at runtime (`from __future__ import annotations`),
109
+ # so checking them as 3.10 also covers 3.9, which mypy 2 can no longer target.
110
+ python_version = "3.10"
111
+
99
112
  [tool.pytest.ini_options]
100
113
  testpaths = ["tests"]
101
114
  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 | None]] = {}
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
- return {f.name: getattr(self, f.name) for f in fields(self)}
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: dict[str, Any]):
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,18 @@ 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
+
18
20
  from functools import lru_cache
19
21
  from types import BuiltinFunctionType, FunctionType
22
+ from typing import Any, Union
20
23
 
21
- import simplejson as json
24
+ import simplejson as json # type: ignore[import-untyped]
22
25
 
23
26
  from .primitive_types import PRIMITIVE_TYPES
24
27
 
25
28
 
26
- def init_arg_names(obj):
29
+ def init_arg_names(obj: Any) -> tuple[str, ...]:
27
30
  """
28
31
  Names of the positional arguments to the __init__ method of this
29
32
  object's class (excluding self).
@@ -31,7 +34,8 @@ def init_arg_names(obj):
31
34
  Keyword-only arguments are deliberately excluded, so a class can accept
32
35
  constructor options which aren't part of its serialized state.
33
36
  """
34
- return _class_init_arg_names(type(obj))
37
+ # mypy doesn't consider classes Hashable
38
+ return _class_init_arg_names(type(obj)) # type: ignore[arg-type]
35
39
 
36
40
 
37
41
  # Cached per class since this is called on every to_dict/__eq__/__hash__ of
@@ -53,7 +57,7 @@ def _class_init_arg_names(cls):
53
57
  return init_code.co_varnames[1 : init_code.co_argcount]
54
58
 
55
59
 
56
- def simple_object_to_dict(self):
60
+ def simple_object_to_dict(self: object) -> dict[str, Any]:
57
61
  return {name: getattr(self, name) for name in init_arg_names(self)}
58
62
 
59
63
 
@@ -246,7 +250,7 @@ def list_to_serializable_repr(x):
246
250
  return [to_serializable_repr(element) for element in x]
247
251
 
248
252
 
249
- def to_dict(obj):
253
+ def to_dict(obj: Any) -> dict[str, Any]:
250
254
  """
251
255
  If value isn't a primitive scalar or collection then it needs to
252
256
  either implement to_dict (instances of Serializable) or have member
@@ -262,7 +266,7 @@ def to_dict(obj):
262
266
  raise ValueError(f"Cannot convert {obj} : {type(obj)} to dictionary") from exc
263
267
 
264
268
 
265
- def to_serializable_repr(x):
269
+ def to_serializable_repr(x: Any) -> Any:
266
270
  """
267
271
  Convert an instance of Serializable or a primitive collection containing
268
272
  such instances into serializable types.
@@ -288,7 +292,7 @@ def to_serializable_repr(x):
288
292
  return state_dictionary
289
293
 
290
294
 
291
- def from_serializable_repr(x):
295
+ def from_serializable_repr(x: Any) -> Any:
292
296
  """
293
297
  Inverse of to_serializable_repr. Does not modify `x`.
294
298
  """
@@ -302,7 +306,7 @@ def from_serializable_repr(x):
302
306
  raise TypeError(f"Cannot convert {x} : {type(x)} from serializable representation to object")
303
307
 
304
308
 
305
- def to_json(x):
309
+ def to_json(x: Any) -> str:
306
310
  """
307
311
  Returns JSON representation of a given Serializable instance or
308
312
  other primitive object.
@@ -310,7 +314,7 @@ def to_json(x):
310
314
  return json.dumps(to_serializable_repr(x))
311
315
 
312
316
 
313
- def from_json(json_string):
317
+ def from_json(json_string: Union[str, bytes]) -> Any:
314
318
  """
315
319
  Inverse of to_json. Objects are reconstructed bottom-up while parsing,
316
320
  equivalent to (but faster than) from_serializable_repr(json.loads(...)).
File without changes
@@ -11,10 +11,16 @@
11
11
  # limitations under the License.
12
12
 
13
13
 
14
- from typing import ClassVar
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,4 +1,4 @@
1
- __version__ = "1.2.0"
1
+ __version__ = "1.2.1"
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.2.0
3
+ Version: 1.2.1
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,6 +217,9 @@ 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
@@ -226,6 +229,7 @@ Provides-Extra: dev
226
229
  Requires-Dist: pytest; extra == "dev"
227
230
  Requires-Dist: pytest-cov; extra == "dev"
228
231
  Requires-Dist: ruff; extra == "dev"
232
+ Requires-Dist: mypy; extra == "dev"
229
233
  Requires-Dist: coveralls; extra == "dev"
230
234
  Requires-Dist: build; extra == "dev"
231
235
  Requires-Dist: twine; extra == "dev"
@@ -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
@@ -5,6 +5,7 @@ simplejson>=3.0.0
5
5
  pytest
6
6
  pytest-cov
7
7
  ruff
8
+ mypy
8
9
  coveralls
9
10
  build
10
11
  twine
@@ -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