dataclass-extensions 0.2.6__tar.gz → 0.2.8__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 (19) hide show
  1. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/PKG-INFO +15 -7
  2. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/README.md +14 -6
  3. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/decode.py +11 -0
  4. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/registrable.py +13 -19
  5. dataclass_extensions-0.2.8/src/dataclass_extensions/version.py +1 -0
  6. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions.egg-info/PKG-INFO +15 -7
  7. dataclass_extensions-0.2.6/src/dataclass_extensions/version.py +0 -1
  8. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/LICENSE +0 -0
  9. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/pyproject.toml +0 -0
  10. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/setup.cfg +0 -0
  11. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/__init__.py +0 -0
  12. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/encode.py +0 -0
  13. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/py.typed +0 -0
  14. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/types.py +0 -0
  15. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions/utils.py +0 -0
  16. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions.egg-info/SOURCES.txt +0 -0
  17. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions.egg-info/dependency_links.txt +0 -0
  18. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions.egg-info/requires.txt +0 -0
  19. {dataclass_extensions-0.2.6 → dataclass_extensions-0.2.8}/src/dataclass_extensions.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataclass-extensions
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Additional functionality for Python dataclasses
5
5
  Author-email: Pete Walsh <epwalsh10@gmail.com>
6
6
  License: Apache License
@@ -263,7 +263,6 @@ assert decode(FruitBasket, encode(basket)) == basket
263
263
 
264
264
  You can also define how to encode/decode non-dataclass types:
265
265
 
266
-
267
266
  ```python
268
267
  from dataclasses import dataclass
269
268
  from dataclass_extensions import decode, encode
@@ -280,11 +279,12 @@ class Bar:
280
279
  encode.register_encoder(lambda foo: {"x": foo.x}, Foo)
281
280
  decode.register_decoder(lambda d: Foo(d["x"]), Foo)
282
281
 
283
- encode(Bar(foo=Foo(10))) # {'foo': {'x': 10}}
284
- decode(Bar, {"foo": {"x": 10}})
282
+ bar = Bar(foo=Foo(10))
283
+ assert encode(bar) == {"foo": {"x": 10}}
284
+ assert decode(Bar, encode(bar)) == bar
285
285
  ```
286
286
 
287
- ### Registrable subclasses
287
+ ### Polymorphism through registrable subclasses
288
288
 
289
289
  ```python
290
290
  from dataclasses import dataclass
@@ -307,6 +307,7 @@ class Banana(Fruit):
307
307
  class Apple(Fruit):
308
308
  calories: int = 150
309
309
  price: float = 1.50
310
+ variety: str = "Granny Smith"
310
311
 
311
312
  @dataclass
312
313
  class FruitBasket:
@@ -314,7 +315,14 @@ class FruitBasket:
314
315
  count: int
315
316
 
316
317
  basket = FruitBasket(fruit=Apple(), count=2)
317
- # note the additional "type" field, which corresponds to the registered name of the subclass
318
- assert encode(basket) == {"fruit": {"calories": 150, "price": 1.5, "type": "apple"}, "count": 2}
318
+ assert encode(basket) == {
319
+ "fruit": {
320
+ "type": "apple", # corresponds to the registered name
321
+ "calories": 150,
322
+ "price": 1.5,
323
+ "variety": "Granny Smith",
324
+ },
325
+ "count": 2,
326
+ }
319
327
  assert decode(FruitBasket, encode(basket)) == basket
320
328
  ```
@@ -37,7 +37,6 @@ assert decode(FruitBasket, encode(basket)) == basket
37
37
 
38
38
  You can also define how to encode/decode non-dataclass types:
39
39
 
40
-
41
40
  ```python
42
41
  from dataclasses import dataclass
43
42
  from dataclass_extensions import decode, encode
@@ -54,11 +53,12 @@ class Bar:
54
53
  encode.register_encoder(lambda foo: {"x": foo.x}, Foo)
55
54
  decode.register_decoder(lambda d: Foo(d["x"]), Foo)
56
55
 
57
- encode(Bar(foo=Foo(10))) # {'foo': {'x': 10}}
58
- decode(Bar, {"foo": {"x": 10}})
56
+ bar = Bar(foo=Foo(10))
57
+ assert encode(bar) == {"foo": {"x": 10}}
58
+ assert decode(Bar, encode(bar)) == bar
59
59
  ```
60
60
 
61
- ### Registrable subclasses
61
+ ### Polymorphism through registrable subclasses
62
62
 
63
63
  ```python
64
64
  from dataclasses import dataclass
@@ -81,6 +81,7 @@ class Banana(Fruit):
81
81
  class Apple(Fruit):
82
82
  calories: int = 150
83
83
  price: float = 1.50
84
+ variety: str = "Granny Smith"
84
85
 
85
86
  @dataclass
86
87
  class FruitBasket:
@@ -88,7 +89,14 @@ class FruitBasket:
88
89
  count: int
89
90
 
90
91
  basket = FruitBasket(fruit=Apple(), count=2)
91
- # note the additional "type" field, which corresponds to the registered name of the subclass
92
- assert encode(basket) == {"fruit": {"calories": 150, "price": 1.5, "type": "apple"}, "count": 2}
92
+ assert encode(basket) == {
93
+ "fruit": {
94
+ "type": "apple", # corresponds to the registered name
95
+ "calories": 150,
96
+ "price": 1.5,
97
+ "variety": "Granny Smith",
98
+ },
99
+ "count": 2,
100
+ }
93
101
  assert decode(FruitBasket, encode(basket)) == basket
94
102
  ```
@@ -115,6 +115,17 @@ def _coerce(
115
115
  except ValueError:
116
116
  pass
117
117
 
118
+ if allowed_type is int:
119
+ if _safe_isinstance(value, (int, float)) and (value_as_int := int(value)) == value:
120
+ return value_as_int
121
+ elif _safe_isinstance(value, str):
122
+ try:
123
+ value_as_float = float(value)
124
+ if (value_as_int := int(value_as_float)) == value_as_float:
125
+ return value_as_int
126
+ except ValueError:
127
+ pass
128
+
118
129
  origin = getattr(allowed_type, "__origin__", None)
119
130
  args = getattr(allowed_type, "__args__", None)
120
131
  if (origin is list or origin is collections.abc.MutableSequence) and _safe_isinstance(
@@ -17,6 +17,8 @@ if sys.version_info < (3, 11):
17
17
  class Registrable:
18
18
  _registry: ClassVar[dict[str, Type[Registrable]]]
19
19
  _default_type: ClassVar[str | None]
20
+ registered_name: ClassVar[str | None]
21
+ registered_base: ClassVar[Type[Registrable] | None]
20
22
 
21
23
  type: dataclasses.InitVar[str | None] = dataclasses.field(
22
24
  default=None, kw_only=True, repr=False
@@ -24,16 +26,14 @@ class Registrable:
24
26
 
25
27
  def __new__(cls, *args, type: str | None = None, **kwargs):
26
28
  del args, kwargs
27
- if type is not None and (
28
- not hasattr(cls, "registered_name") or type != cls.registered_name # type: ignore
29
- ):
29
+ if type is not None and type != cls.registered_name:
30
30
  if type not in cls._registry:
31
31
  raise KeyError(
32
32
  f"'{type}' is not registered name for {cls.__name__}. "
33
33
  f"Available choices are: {list(cls._registry.keys())}"
34
34
  )
35
35
  return super().__new__(cls._registry[type])
36
- elif cls._default_type is not None and not hasattr(cls, "registered_name"):
36
+ elif cls._default_type is not None and cls.registered_name is None:
37
37
  return super().__new__(cls._registry[cls._default_type])
38
38
  else:
39
39
  return super().__new__(cls)
@@ -44,6 +44,10 @@ class Registrable:
44
44
  cls._registry = {}
45
45
  if not hasattr(cls, "_default_type"):
46
46
  cls._default_type = None
47
+ if not hasattr(cls, "registered_name"):
48
+ cls.registered_name = None
49
+ if not hasattr(cls, "registered_base"):
50
+ cls.registered_base = None
47
51
 
48
52
  @classmethod
49
53
  def register(cls, name: str, default: bool = False) -> Callable[[Type[R]], Type[R]]:
@@ -64,28 +68,18 @@ class Registrable:
64
68
  else:
65
69
  cls._default_type = name
66
70
 
67
- fields = [
68
- (f.name, f.type, f) for f in dataclasses.fields(subclass) if f.name != "type" # type: ignore
69
- ] + [
70
- ("registered_name", ClassVar[str], name), # type: ignore
71
- ("registered_base", ClassVar[R], cls), # type: ignore
72
- ("type", dataclasses.InitVar[str | None], dataclasses.field(default=name, kw_only=True, repr=False)), # type: ignore
73
- ]
74
- subclass = dataclasses.make_dataclass(
75
- subclass.__name__,
76
- fields, # type: ignore
77
- bases=(subclass,),
78
- )
79
71
  cls._registry[name] = subclass
80
- return subclass
72
+ subclass.registered_name = name
73
+ subclass.registered_base = cls
74
+ return subclass # type: ignore
81
75
 
82
76
  return register_subclass
83
77
 
84
78
  @classmethod
85
79
  def get_registered_name(cls: Type[R], subclass: Type[R] | None = None) -> str:
86
80
  if subclass is None:
87
- if hasattr(cls, "registered_name"):
88
- return cls.registered_name # type: ignore
81
+ if cls.registered_name is not None:
82
+ return cls.registered_name
89
83
  else:
90
84
  raise ValueError(
91
85
  f"class {cls.__name__} is not a registered subclass of any base registrable class"
@@ -0,0 +1 @@
1
+ VERSION = "0.2.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataclass-extensions
3
- Version: 0.2.6
3
+ Version: 0.2.8
4
4
  Summary: Additional functionality for Python dataclasses
5
5
  Author-email: Pete Walsh <epwalsh10@gmail.com>
6
6
  License: Apache License
@@ -263,7 +263,6 @@ assert decode(FruitBasket, encode(basket)) == basket
263
263
 
264
264
  You can also define how to encode/decode non-dataclass types:
265
265
 
266
-
267
266
  ```python
268
267
  from dataclasses import dataclass
269
268
  from dataclass_extensions import decode, encode
@@ -280,11 +279,12 @@ class Bar:
280
279
  encode.register_encoder(lambda foo: {"x": foo.x}, Foo)
281
280
  decode.register_decoder(lambda d: Foo(d["x"]), Foo)
282
281
 
283
- encode(Bar(foo=Foo(10))) # {'foo': {'x': 10}}
284
- decode(Bar, {"foo": {"x": 10}})
282
+ bar = Bar(foo=Foo(10))
283
+ assert encode(bar) == {"foo": {"x": 10}}
284
+ assert decode(Bar, encode(bar)) == bar
285
285
  ```
286
286
 
287
- ### Registrable subclasses
287
+ ### Polymorphism through registrable subclasses
288
288
 
289
289
  ```python
290
290
  from dataclasses import dataclass
@@ -307,6 +307,7 @@ class Banana(Fruit):
307
307
  class Apple(Fruit):
308
308
  calories: int = 150
309
309
  price: float = 1.50
310
+ variety: str = "Granny Smith"
310
311
 
311
312
  @dataclass
312
313
  class FruitBasket:
@@ -314,7 +315,14 @@ class FruitBasket:
314
315
  count: int
315
316
 
316
317
  basket = FruitBasket(fruit=Apple(), count=2)
317
- # note the additional "type" field, which corresponds to the registered name of the subclass
318
- assert encode(basket) == {"fruit": {"calories": 150, "price": 1.5, "type": "apple"}, "count": 2}
318
+ assert encode(basket) == {
319
+ "fruit": {
320
+ "type": "apple", # corresponds to the registered name
321
+ "calories": 150,
322
+ "price": 1.5,
323
+ "variety": "Granny Smith",
324
+ },
325
+ "count": 2,
326
+ }
319
327
  assert decode(FruitBasket, encode(basket)) == basket
320
328
  ```
@@ -1 +0,0 @@
1
- VERSION = "0.2.6"