dynapydantic 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- dynapydantic/subclass_tracking_model.py +18 -1
- dynapydantic-0.1.1.dist-info/METADATA +139 -0
- {dynapydantic-0.1.0.dist-info → dynapydantic-0.1.1.dist-info}/RECORD +5 -5
- dynapydantic-0.1.0.dist-info/METADATA +0 -21
- {dynapydantic-0.1.0.dist-info → dynapydantic-0.1.1.dist-info}/WHEEL +0 -0
- {dynapydantic-0.1.0.dist-info → dynapydantic-0.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -26,7 +26,24 @@ def direct_children_of_base_in_mro(derived: type, base: type) -> list[type]:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class SubclassTrackingModel(pydantic.BaseModel):
|
|
29
|
-
"""Subclass-tracking BaseModel
|
|
29
|
+
"""Subclass-tracking BaseModel
|
|
30
|
+
|
|
31
|
+
This will inject a TrackingGroup into your class and automate the
|
|
32
|
+
registration of subclasses.
|
|
33
|
+
|
|
34
|
+
Inheriting from this class will augment your class with the following
|
|
35
|
+
members functions:
|
|
36
|
+
1. registered_subclasses() -> dict[str, type[cls]]:
|
|
37
|
+
This will return a mapping of discriminator value to the corresponding
|
|
38
|
+
sublcass. See TrackingGroup.models for details.
|
|
39
|
+
2. union() -> typing.GenericAlias:
|
|
40
|
+
This will return an (optionally) annotated subclass union. See
|
|
41
|
+
TrackingGroup.union() for details.
|
|
42
|
+
3. load_plugins() -> None:
|
|
43
|
+
If plugin_entry_point was specified, then this method will load plugin
|
|
44
|
+
packages to discover additional subclasses. See
|
|
45
|
+
TrackingGroup.load_plugins for more details.
|
|
46
|
+
"""
|
|
30
47
|
|
|
31
48
|
def __init_subclass__(
|
|
32
49
|
cls,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dynapydantic
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Dyanmic pydantic models
|
|
5
|
+
Author-email: Philip Salvaggio <salvaggio.philip@gmail.com>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# dynapydantic
|
|
12
|
+
|
|
13
|
+
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
|
|
14
|
+
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
|
|
15
|
+
[](https://psalvaggio.github.io/dynapydantic/dev/)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
`dynapydantic` is an extension to the [pydantic](https://pydantic.dev) Python
|
|
19
|
+
package that allow for dynamic tracking of `pydantic.BaseModel` subclasses.
|
|
20
|
+
|
|
21
|
+
Installation
|
|
22
|
+
==
|
|
23
|
+
This project can be installed via PyPI:
|
|
24
|
+
```
|
|
25
|
+
pip install dynapydantic
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Usage
|
|
29
|
+
==
|
|
30
|
+
|
|
31
|
+
`TrackingGroup`
|
|
32
|
+
--
|
|
33
|
+
The core entity in this library is the `dynapydantic.TrackingGroup`:
|
|
34
|
+
```python
|
|
35
|
+
import typing as ty
|
|
36
|
+
|
|
37
|
+
import dynapydantic
|
|
38
|
+
import pydantic
|
|
39
|
+
|
|
40
|
+
mygroup = dynapydantic.TrackingGroup(
|
|
41
|
+
name="mygroup",
|
|
42
|
+
discriminator_field="name"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@mygroup.register("A")
|
|
46
|
+
class A(pydantic.BaseModel):
|
|
47
|
+
"""A class to be tracked, will be tracked as "A"."""
|
|
48
|
+
a: int
|
|
49
|
+
|
|
50
|
+
@mygroup.register()
|
|
51
|
+
class B(pydantic.BaseModel):
|
|
52
|
+
"""Another class, will be tracked as "B"."""
|
|
53
|
+
name: ty.Literal["B"] = "B"
|
|
54
|
+
a: int
|
|
55
|
+
|
|
56
|
+
class Model(pydantic.BaseModel):
|
|
57
|
+
"""A model that can have A or B"""
|
|
58
|
+
field: mygroup.union() # call after all subclasses have been registered
|
|
59
|
+
|
|
60
|
+
print(Model(field={"name": "A", "a": 4})) # field=A(a=4, name='A')
|
|
61
|
+
print(Model(field={"name": "B", "a": 5})) # field=B(name='B', a=5)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `union()` method produces a [discriminated union](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions)
|
|
65
|
+
of all registered `pydantic.BaseModel` subclasses. It also accepts an
|
|
66
|
+
`annotated=False` keyword argument to produce a plain `typing.Union` for use in
|
|
67
|
+
type annotations. This union is based on a discriminator field, which was
|
|
68
|
+
configured by the `discriminator_field` argument to `TrackingGroup`. The field
|
|
69
|
+
can be created by hand, as was shown with `B`, or `dynapydantic` will inject it
|
|
70
|
+
for you, as was shown with `A`.
|
|
71
|
+
|
|
72
|
+
`TrackingGroup` has a few opt-in features to make it more powerful and easier to use:
|
|
73
|
+
1. `discriminator_value_generator`: This parameter is a optional callback
|
|
74
|
+
function that is called with each class that gets registered and produces a
|
|
75
|
+
default value for the discriminator field. This allows the user to call
|
|
76
|
+
`register()` without a value for the discriminator. The most common value to
|
|
77
|
+
pass here would be `lambda cls: cls.__name__`, to use the name of the class as
|
|
78
|
+
the discriminator value.
|
|
79
|
+
2. `plugin_entry_point`: This parameter indicates to `dynapydantic` that there
|
|
80
|
+
might be models to be discovered in other packages. Packages are discovered by
|
|
81
|
+
the Python entrypoint mechanism. See the `tests/example` directory for an
|
|
82
|
+
example of how this works.
|
|
83
|
+
|
|
84
|
+
`SubclassTrackingModel`
|
|
85
|
+
--
|
|
86
|
+
The most common use case of this pattern is to automatically register subclasses
|
|
87
|
+
of a given `pydantic.BaseModel`. This is supported via the use of
|
|
88
|
+
`dynapydantic.SubclassTrackingModel`. For example:
|
|
89
|
+
```python
|
|
90
|
+
import typing as ty
|
|
91
|
+
|
|
92
|
+
import dynapydantic
|
|
93
|
+
import pydantic
|
|
94
|
+
|
|
95
|
+
class Base(
|
|
96
|
+
dynapydantic.SubclassTrackingModel,
|
|
97
|
+
discriminator_field="name",
|
|
98
|
+
discriminator_value_generator=lambda cls: cls.__name__,
|
|
99
|
+
):
|
|
100
|
+
"""Base model, will track its subclasses"""
|
|
101
|
+
|
|
102
|
+
# The TrackingGroup can be specified here like model_config, or passed in
|
|
103
|
+
# kwargs of the class declaration, just like how model_config works with
|
|
104
|
+
# pydantic.BaseModel. If you do it like this, you have to give the tracking
|
|
105
|
+
# group a name
|
|
106
|
+
# tracking_config: ty.ClassVar[dynapydantic.TrackingGroup] = dynapydantic.TrackingGroup(
|
|
107
|
+
# name="BaseSubclasses",
|
|
108
|
+
# discriminator_field="name",
|
|
109
|
+
# discriminator_value_generator=lambda cls: cls.__name__,
|
|
110
|
+
# )
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Intermediate(Base, exclude_from_union=True):
|
|
114
|
+
"""Subclasses can opt out of being tracked"""
|
|
115
|
+
|
|
116
|
+
class Derived1(Intermediate):
|
|
117
|
+
"""Non-direct descendants are registered"""
|
|
118
|
+
a: int
|
|
119
|
+
|
|
120
|
+
class Derived2(Intermediate):
|
|
121
|
+
"""You can override the value generator if desired"""
|
|
122
|
+
name: ty.Literal["Custom"] = "Custom"
|
|
123
|
+
a: int
|
|
124
|
+
|
|
125
|
+
print(Base.registered_subclasses())
|
|
126
|
+
# {'Derived1': <class '__main__.Derived1'>, 'Custom': <class '__main__.Derived2'>}
|
|
127
|
+
|
|
128
|
+
# if plugin_entry_point was specificed, load plugin packages
|
|
129
|
+
# Base.load_plugins()
|
|
130
|
+
|
|
131
|
+
class Model(pydantic.BaseModel):
|
|
132
|
+
"""A model that can have any registered Base subclass"""
|
|
133
|
+
field: Base.union() # call after all subclasses have been registered
|
|
134
|
+
|
|
135
|
+
print(Model(field={"name": "Derived1", "a": 4}))
|
|
136
|
+
# field=Derived1(a=4, name='Derived1')
|
|
137
|
+
print(Model(field={"name": "Custom", "a": 5}))
|
|
138
|
+
# field=Derived2(name='Custom', a=5)
|
|
139
|
+
```
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
dynapydantic/__init__.py,sha256=vIK1dasCAghxjXaUjw8blTHLFwsHZ9L2txuX_8AB7cQ,452
|
|
2
2
|
dynapydantic/exceptions.py,sha256=R1wJj-FmKv2JdYSG5HVMkZ0zLyFRKJRuUsBxXGnEjsQ,394
|
|
3
3
|
dynapydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
dynapydantic/subclass_tracking_model.py,sha256
|
|
4
|
+
dynapydantic/subclass_tracking_model.py,sha256=MmxQgbIusULSEM04R7Yz5kHCHZCeb-vLHenbNctmZPM,4914
|
|
5
5
|
dynapydantic/tracking_group.py,sha256=-rorYSrjHCCV6yeiulA8lI8e9aVrqG-u57UDgD5gZKA,7342
|
|
6
|
-
dynapydantic-0.1.
|
|
7
|
-
dynapydantic-0.1.
|
|
8
|
-
dynapydantic-0.1.
|
|
9
|
-
dynapydantic-0.1.
|
|
6
|
+
dynapydantic-0.1.1.dist-info/METADATA,sha256=4b5kR3ImNfPHo7acEQy3LXRAV4nUDFwLBM4v_OjfEqQ,5048
|
|
7
|
+
dynapydantic-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
dynapydantic-0.1.1.dist-info/licenses/LICENSE,sha256=I6pwCRw86q30bFjJohgVzXYgCLNCWN3A4jNGJX2iVM4,1073
|
|
9
|
+
dynapydantic-0.1.1.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: dynapydantic
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Dyanmic pydantic models
|
|
5
|
-
Author-email: Philip Salvaggio <salvaggio.philip@gmail.com>
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Requires-Dist: pydantic>=2.0
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
|
|
11
|
-
# dynapydantic
|
|
12
|
-
|
|
13
|
-
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
|
|
14
|
-
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
|
|
15
|
-
|
|
16
|
-
This is a demonstration about how `pydantic` models can track their subclasses
|
|
17
|
-
and round-trip through serialization, both within the package in which they are
|
|
18
|
-
defined and in other packages via `pluggy`.
|
|
19
|
-
|
|
20
|
-
This package is not intended for public use yet. It's strictly a
|
|
21
|
-
proof-of-concept.
|
|
File without changes
|
|
File without changes
|