dynapydantic 0.1.1__tar.gz → 0.2.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.
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.github/workflows/ci.yml +7 -1
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.github/workflows/pre-commit.yml +2 -1
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.pre-commit-config.yaml +9 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/PKG-INFO +104 -21
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/README.md +103 -20
- dynapydantic-0.2.0/htmlcov/class_index.html +313 -0
- dynapydantic-0.1.1/htmlcov/coverage_html_cb_6fb7b396.js → dynapydantic-0.2.0/htmlcov/coverage_html_cb_bcae5fc4.js +12 -10
- dynapydantic-0.2.0/htmlcov/function_index.html +418 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/index.html +80 -27
- dynapydantic-0.2.0/htmlcov/status.json +1 -0
- dynapydantic-0.1.1/htmlcov/style_cb_81f8c14c.css → dynapydantic-0.2.0/htmlcov/style_cb_8432e98f.css +52 -4
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d___init___py.html +27 -25
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d_exceptions_py.html +9 -9
- dynapydantic-0.2.0/htmlcov/z_f3e6dac33013b94d_polymorphic_py.html +128 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d_subclass_tracking_model_py.html +164 -139
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d_tracking_group_py.html +210 -206
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/mkdocs.yml +1 -1
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/pyproject.toml +11 -1
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/__init__.py +2 -0
- dynapydantic-0.2.0/src/dynapydantic/polymorphic.py +29 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/subclass_tracking_model.py +37 -12
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/tracking_group.py +11 -7
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/cli.py +4 -2
- dynapydantic-0.2.0/tests/example/base-package/uv.lock +149 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/test_plugins.py +4 -2
- dynapydantic-0.2.0/tests/test_polymorphic.py +88 -0
- dynapydantic-0.2.0/tests/test_recursive_models.py +64 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/test_subclass_tracking_model.py +6 -1
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/test_tracking_group.py +1 -1
- dynapydantic-0.2.0/uv.lock +1499 -0
- dynapydantic-0.1.1/htmlcov/class_index.html +0 -213
- dynapydantic-0.1.1/htmlcov/function_index.html +0 -283
- dynapydantic-0.1.1/htmlcov/status.json +0 -1
- dynapydantic-0.1.1/uv.lock +0 -1266
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.github/workflows/deploy-docs.yml +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.gitignore +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.python-version +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/LICENSE +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/docs/README.md +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/docs/reference.md +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/.gitignore +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/favicon_32_cb_58284776.png +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/keybd_closed_cb_ce680311.png +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/exceptions.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/py.typed +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/__init__.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/animal-plugins/animal_plugins/__init__.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/animal-plugins/pyproject.toml +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/__init__.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/__main__.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/animal.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/cat.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/circle.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/shape.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/pyproject.toml +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/pyproject.toml +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/shape_plugins/__init__.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/shape_plugins/plugin_classes.py +0 -0
- {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/shape_plugins/registration.py +0 -0
|
@@ -23,7 +23,13 @@ jobs:
|
|
|
23
23
|
- name: Install the project
|
|
24
24
|
run: uv sync --locked --all-extras --dev
|
|
25
25
|
- name: Run tests
|
|
26
|
-
run: uv run pytest
|
|
26
|
+
run: uv run pytest --cov-report=xml
|
|
27
|
+
- name: Upload coverage to Coveralls
|
|
28
|
+
uses: coverallsapp/github-action@v2
|
|
29
|
+
if: matrix.python-version == '3.13'
|
|
30
|
+
with:
|
|
31
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
32
|
+
path-to-lcov: coverage.xml
|
|
27
33
|
|
|
28
34
|
publish-pypi:
|
|
29
35
|
name: Publish to PyPI
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dynapydantic
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Dyanmic pydantic models
|
|
5
5
|
Author-email: Philip Salvaggio <salvaggio.philip@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -13,23 +13,101 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
|
|
14
14
|
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
|
|
15
15
|
[](https://psalvaggio.github.io/dynapydantic/dev/)
|
|
16
|
+
[](https://pypi.org/project/dynapydantic/)
|
|
17
|
+
[](https://coveralls.io/github/psalvaggio/dynapydantic?branch=main)
|
|
18
|
+
[](https://anaconda.org/conda-forge/dynapydantic)
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
`dynapydantic` is an extension to the [pydantic](https://pydantic.dev) Python
|
|
19
22
|
package that allow for dynamic tracking of `pydantic.BaseModel` subclasses.
|
|
20
23
|
|
|
21
|
-
Installation
|
|
22
|
-
==
|
|
24
|
+
## Installation
|
|
23
25
|
This project can be installed via PyPI:
|
|
24
26
|
```
|
|
25
27
|
pip install dynapydantic
|
|
26
28
|
```
|
|
29
|
+
or with `conda` via the `conda-forge` channel:
|
|
30
|
+
```
|
|
31
|
+
conda install dynapydantic
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Motiviation
|
|
36
|
+
Consider the following simple class setup:
|
|
37
|
+
```python
|
|
38
|
+
import pydantic
|
|
39
|
+
|
|
40
|
+
class Base(pydantic.BaseModel):
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
class A(Base):
|
|
44
|
+
field: int
|
|
45
|
+
|
|
46
|
+
class B(Base):
|
|
47
|
+
field: str
|
|
48
|
+
|
|
49
|
+
class Model(pydantic.BaseModel):
|
|
50
|
+
val: Base
|
|
51
|
+
```
|
|
52
|
+
As expected, we can use `A`'s and `B`'s for `Model.val`:
|
|
53
|
+
```python
|
|
54
|
+
>>> m = Model(val=A(field=1))
|
|
55
|
+
>>> m
|
|
56
|
+
Model(val=A(field=1))
|
|
57
|
+
```
|
|
58
|
+
However, we quickly run into trouble when serializing and validating:
|
|
59
|
+
```python
|
|
60
|
+
>>> m.model_dump()
|
|
61
|
+
{'base': {}}
|
|
62
|
+
>>> m.model_dump(serialize_as_any=True)
|
|
63
|
+
{'val': {'field': 1}}
|
|
64
|
+
>>> Model.model_validate(m.model_dump(serialize_as_any=True))
|
|
65
|
+
Model(val=Base())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Pydantic provides a solution for serialization via `serialize_as_any` (and
|
|
69
|
+
its corresponding field annotation `SerializeAsAny`), but offers no native
|
|
70
|
+
solution for the validation half. Currently, the canonical way of doing this
|
|
71
|
+
is to annotate the field as a discriminated union of all subclasses. Often, a
|
|
72
|
+
single field in the model is chosen as the "discriminator". This library,
|
|
73
|
+
`dynapydantic`, automates this process.
|
|
74
|
+
|
|
75
|
+
Let's reframe the above problem with `dynapydantic`:
|
|
76
|
+
```python
|
|
77
|
+
import dynapydantic
|
|
78
|
+
import pydantic
|
|
79
|
+
|
|
80
|
+
class Base(
|
|
81
|
+
dynapydantic.SubclassTrackingModel,
|
|
82
|
+
discriminator_field="name",
|
|
83
|
+
discriminator_value_generator=lambda t: t.__name__,
|
|
84
|
+
):
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
class A(Base):
|
|
88
|
+
field: int
|
|
89
|
+
|
|
90
|
+
class B(Base):
|
|
91
|
+
field: str
|
|
92
|
+
|
|
93
|
+
class Model(pydantic.BaseModel):
|
|
94
|
+
val: dynapydantic.Polymorphic[Base]
|
|
95
|
+
```
|
|
96
|
+
Now, the same set of operations works as intended:
|
|
97
|
+
```python
|
|
98
|
+
>>> m = Model(val=A(field=1))
|
|
99
|
+
>>> m
|
|
100
|
+
Model(val=A(field=1, name='A'))
|
|
101
|
+
>>> m.model_dump()
|
|
102
|
+
{'val': {'field': 1, 'name': 'A'}}
|
|
103
|
+
>>> Model.model_validate(m.model_dump())
|
|
104
|
+
Model(val=A(field=1, name='A')
|
|
105
|
+
```
|
|
106
|
+
|
|
27
107
|
|
|
28
|
-
|
|
29
|
-
==
|
|
108
|
+
## How it works
|
|
30
109
|
|
|
31
|
-
`TrackingGroup`
|
|
32
|
-
--
|
|
110
|
+
### `TrackingGroup`
|
|
33
111
|
The core entity in this library is the `dynapydantic.TrackingGroup`:
|
|
34
112
|
```python
|
|
35
113
|
import typing as ty
|
|
@@ -63,26 +141,26 @@ print(Model(field={"name": "B", "a": 5})) # field=B(name='B', a=5)
|
|
|
63
141
|
|
|
64
142
|
The `union()` method produces a [discriminated union](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions)
|
|
65
143
|
of all registered `pydantic.BaseModel` subclasses. It also accepts an
|
|
66
|
-
`annotated=False` keyword argument to produce a plain `typing.Union` for use
|
|
67
|
-
type annotations
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
144
|
+
`annotated=False` keyword argument to produce a plain `typing.Union` for use
|
|
145
|
+
in type annotations, but since this is a runtime-computed union, this will not
|
|
146
|
+
work with static type checkers. This union is based on a discriminator field,
|
|
147
|
+
which was configured by the `discriminator_field` argument to `TrackingGroup`.
|
|
148
|
+
The field can be created by hand, as was shown with `B`, or `dynapydantic`
|
|
149
|
+
will inject it for you, as was shown with `A`.
|
|
71
150
|
|
|
72
151
|
`TrackingGroup` has a few opt-in features to make it more powerful and easier to use:
|
|
73
152
|
1. `discriminator_value_generator`: This parameter is a optional callback
|
|
74
153
|
function that is called with each class that gets registered and produces a
|
|
75
154
|
default value for the discriminator field. This allows the user to call
|
|
76
|
-
`register()` without a value for the discriminator.
|
|
77
|
-
|
|
78
|
-
|
|
155
|
+
`register()` without a value for the discriminator. For example, passing:
|
|
156
|
+
`lambda cls: cls.__name__` would use the name of the class as the
|
|
157
|
+
discriminator value.
|
|
79
158
|
2. `plugin_entry_point`: This parameter indicates to `dynapydantic` that there
|
|
80
|
-
might be models to be discovered in other packages. Packages are discovered
|
|
81
|
-
the Python entrypoint mechanism. See the `tests/example` directory for an
|
|
159
|
+
might be models to be discovered in other packages. Packages are discovered
|
|
160
|
+
by the Python entrypoint mechanism. See the `tests/example` directory for an
|
|
82
161
|
example of how this works.
|
|
83
162
|
|
|
84
|
-
`SubclassTrackingModel`
|
|
85
|
-
--
|
|
163
|
+
### `SubclassTrackingModel`
|
|
86
164
|
The most common use case of this pattern is to automatically register subclasses
|
|
87
165
|
of a given `pydantic.BaseModel`. This is supported via the use of
|
|
88
166
|
`dynapydantic.SubclassTrackingModel`. For example:
|
|
@@ -102,7 +180,7 @@ class Base(
|
|
|
102
180
|
# The TrackingGroup can be specified here like model_config, or passed in
|
|
103
181
|
# kwargs of the class declaration, just like how model_config works with
|
|
104
182
|
# pydantic.BaseModel. If you do it like this, you have to give the tracking
|
|
105
|
-
# group a name
|
|
183
|
+
# group a name, whereas using kwargs will generate the name for you.
|
|
106
184
|
# tracking_config: ty.ClassVar[dynapydantic.TrackingGroup] = dynapydantic.TrackingGroup(
|
|
107
185
|
# name="BaseSubclasses",
|
|
108
186
|
# discriminator_field="name",
|
|
@@ -130,10 +208,15 @@ print(Base.registered_subclasses())
|
|
|
130
208
|
|
|
131
209
|
class Model(pydantic.BaseModel):
|
|
132
210
|
"""A model that can have any registered Base subclass"""
|
|
133
|
-
field: Base
|
|
211
|
+
field: dynapydantic.Polymorphic[Base]
|
|
134
212
|
|
|
135
213
|
print(Model(field={"name": "Derived1", "a": 4}))
|
|
136
214
|
# field=Derived1(a=4, name='Derived1')
|
|
137
215
|
print(Model(field={"name": "Custom", "a": 5}))
|
|
138
216
|
# field=Derived2(name='Custom', a=5)
|
|
139
217
|
```
|
|
218
|
+
It is important to note that the subclasses that are supported are those that
|
|
219
|
+
were defined *prior* to defining the model that uses `dynapydantic.Polymorphic`
|
|
220
|
+
(`Model` in the above example). If you declare additional subclasses afterwards,
|
|
221
|
+
you must call `.model_rebuild(force=True)` on the model that uses the subclass
|
|
222
|
+
union.
|
|
@@ -3,23 +3,101 @@
|
|
|
3
3
|
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
|
|
4
4
|
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
|
|
5
5
|
[](https://psalvaggio.github.io/dynapydantic/dev/)
|
|
6
|
+
[](https://pypi.org/project/dynapydantic/)
|
|
7
|
+
[](https://coveralls.io/github/psalvaggio/dynapydantic?branch=main)
|
|
8
|
+
[](https://anaconda.org/conda-forge/dynapydantic)
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
`dynapydantic` is an extension to the [pydantic](https://pydantic.dev) Python
|
|
9
12
|
package that allow for dynamic tracking of `pydantic.BaseModel` subclasses.
|
|
10
13
|
|
|
11
|
-
Installation
|
|
12
|
-
==
|
|
14
|
+
## Installation
|
|
13
15
|
This project can be installed via PyPI:
|
|
14
16
|
```
|
|
15
17
|
pip install dynapydantic
|
|
16
18
|
```
|
|
19
|
+
or with `conda` via the `conda-forge` channel:
|
|
20
|
+
```
|
|
21
|
+
conda install dynapydantic
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Motiviation
|
|
26
|
+
Consider the following simple class setup:
|
|
27
|
+
```python
|
|
28
|
+
import pydantic
|
|
29
|
+
|
|
30
|
+
class Base(pydantic.BaseModel):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
class A(Base):
|
|
34
|
+
field: int
|
|
35
|
+
|
|
36
|
+
class B(Base):
|
|
37
|
+
field: str
|
|
38
|
+
|
|
39
|
+
class Model(pydantic.BaseModel):
|
|
40
|
+
val: Base
|
|
41
|
+
```
|
|
42
|
+
As expected, we can use `A`'s and `B`'s for `Model.val`:
|
|
43
|
+
```python
|
|
44
|
+
>>> m = Model(val=A(field=1))
|
|
45
|
+
>>> m
|
|
46
|
+
Model(val=A(field=1))
|
|
47
|
+
```
|
|
48
|
+
However, we quickly run into trouble when serializing and validating:
|
|
49
|
+
```python
|
|
50
|
+
>>> m.model_dump()
|
|
51
|
+
{'base': {}}
|
|
52
|
+
>>> m.model_dump(serialize_as_any=True)
|
|
53
|
+
{'val': {'field': 1}}
|
|
54
|
+
>>> Model.model_validate(m.model_dump(serialize_as_any=True))
|
|
55
|
+
Model(val=Base())
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Pydantic provides a solution for serialization via `serialize_as_any` (and
|
|
59
|
+
its corresponding field annotation `SerializeAsAny`), but offers no native
|
|
60
|
+
solution for the validation half. Currently, the canonical way of doing this
|
|
61
|
+
is to annotate the field as a discriminated union of all subclasses. Often, a
|
|
62
|
+
single field in the model is chosen as the "discriminator". This library,
|
|
63
|
+
`dynapydantic`, automates this process.
|
|
64
|
+
|
|
65
|
+
Let's reframe the above problem with `dynapydantic`:
|
|
66
|
+
```python
|
|
67
|
+
import dynapydantic
|
|
68
|
+
import pydantic
|
|
69
|
+
|
|
70
|
+
class Base(
|
|
71
|
+
dynapydantic.SubclassTrackingModel,
|
|
72
|
+
discriminator_field="name",
|
|
73
|
+
discriminator_value_generator=lambda t: t.__name__,
|
|
74
|
+
):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
class A(Base):
|
|
78
|
+
field: int
|
|
79
|
+
|
|
80
|
+
class B(Base):
|
|
81
|
+
field: str
|
|
82
|
+
|
|
83
|
+
class Model(pydantic.BaseModel):
|
|
84
|
+
val: dynapydantic.Polymorphic[Base]
|
|
85
|
+
```
|
|
86
|
+
Now, the same set of operations works as intended:
|
|
87
|
+
```python
|
|
88
|
+
>>> m = Model(val=A(field=1))
|
|
89
|
+
>>> m
|
|
90
|
+
Model(val=A(field=1, name='A'))
|
|
91
|
+
>>> m.model_dump()
|
|
92
|
+
{'val': {'field': 1, 'name': 'A'}}
|
|
93
|
+
>>> Model.model_validate(m.model_dump())
|
|
94
|
+
Model(val=A(field=1, name='A')
|
|
95
|
+
```
|
|
96
|
+
|
|
17
97
|
|
|
18
|
-
|
|
19
|
-
==
|
|
98
|
+
## How it works
|
|
20
99
|
|
|
21
|
-
`TrackingGroup`
|
|
22
|
-
--
|
|
100
|
+
### `TrackingGroup`
|
|
23
101
|
The core entity in this library is the `dynapydantic.TrackingGroup`:
|
|
24
102
|
```python
|
|
25
103
|
import typing as ty
|
|
@@ -53,26 +131,26 @@ print(Model(field={"name": "B", "a": 5})) # field=B(name='B', a=5)
|
|
|
53
131
|
|
|
54
132
|
The `union()` method produces a [discriminated union](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions)
|
|
55
133
|
of all registered `pydantic.BaseModel` subclasses. It also accepts an
|
|
56
|
-
`annotated=False` keyword argument to produce a plain `typing.Union` for use
|
|
57
|
-
type annotations
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
134
|
+
`annotated=False` keyword argument to produce a plain `typing.Union` for use
|
|
135
|
+
in type annotations, but since this is a runtime-computed union, this will not
|
|
136
|
+
work with static type checkers. This union is based on a discriminator field,
|
|
137
|
+
which was configured by the `discriminator_field` argument to `TrackingGroup`.
|
|
138
|
+
The field can be created by hand, as was shown with `B`, or `dynapydantic`
|
|
139
|
+
will inject it for you, as was shown with `A`.
|
|
61
140
|
|
|
62
141
|
`TrackingGroup` has a few opt-in features to make it more powerful and easier to use:
|
|
63
142
|
1. `discriminator_value_generator`: This parameter is a optional callback
|
|
64
143
|
function that is called with each class that gets registered and produces a
|
|
65
144
|
default value for the discriminator field. This allows the user to call
|
|
66
|
-
`register()` without a value for the discriminator.
|
|
67
|
-
|
|
68
|
-
|
|
145
|
+
`register()` without a value for the discriminator. For example, passing:
|
|
146
|
+
`lambda cls: cls.__name__` would use the name of the class as the
|
|
147
|
+
discriminator value.
|
|
69
148
|
2. `plugin_entry_point`: This parameter indicates to `dynapydantic` that there
|
|
70
|
-
might be models to be discovered in other packages. Packages are discovered
|
|
71
|
-
the Python entrypoint mechanism. See the `tests/example` directory for an
|
|
149
|
+
might be models to be discovered in other packages. Packages are discovered
|
|
150
|
+
by the Python entrypoint mechanism. See the `tests/example` directory for an
|
|
72
151
|
example of how this works.
|
|
73
152
|
|
|
74
|
-
`SubclassTrackingModel`
|
|
75
|
-
--
|
|
153
|
+
### `SubclassTrackingModel`
|
|
76
154
|
The most common use case of this pattern is to automatically register subclasses
|
|
77
155
|
of a given `pydantic.BaseModel`. This is supported via the use of
|
|
78
156
|
`dynapydantic.SubclassTrackingModel`. For example:
|
|
@@ -92,7 +170,7 @@ class Base(
|
|
|
92
170
|
# The TrackingGroup can be specified here like model_config, or passed in
|
|
93
171
|
# kwargs of the class declaration, just like how model_config works with
|
|
94
172
|
# pydantic.BaseModel. If you do it like this, you have to give the tracking
|
|
95
|
-
# group a name
|
|
173
|
+
# group a name, whereas using kwargs will generate the name for you.
|
|
96
174
|
# tracking_config: ty.ClassVar[dynapydantic.TrackingGroup] = dynapydantic.TrackingGroup(
|
|
97
175
|
# name="BaseSubclasses",
|
|
98
176
|
# discriminator_field="name",
|
|
@@ -120,10 +198,15 @@ print(Base.registered_subclasses())
|
|
|
120
198
|
|
|
121
199
|
class Model(pydantic.BaseModel):
|
|
122
200
|
"""A model that can have any registered Base subclass"""
|
|
123
|
-
field: Base
|
|
201
|
+
field: dynapydantic.Polymorphic[Base]
|
|
124
202
|
|
|
125
203
|
print(Model(field={"name": "Derived1", "a": 4}))
|
|
126
204
|
# field=Derived1(a=4, name='Derived1')
|
|
127
205
|
print(Model(field={"name": "Custom", "a": 5}))
|
|
128
206
|
# field=Derived2(name='Custom', a=5)
|
|
129
207
|
```
|
|
208
|
+
It is important to note that the subclasses that are supported are those that
|
|
209
|
+
were defined *prior* to defining the model that uses `dynapydantic.Polymorphic`
|
|
210
|
+
(`Model` in the above example). If you declare additional subclasses afterwards,
|
|
211
|
+
you must call `.model_rebuild(force=True)` on the model that uses the subclass
|
|
212
|
+
union.
|