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.
Files changed (59) hide show
  1. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.github/workflows/ci.yml +7 -1
  2. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.github/workflows/pre-commit.yml +2 -1
  3. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.pre-commit-config.yaml +9 -0
  4. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/PKG-INFO +104 -21
  5. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/README.md +103 -20
  6. dynapydantic-0.2.0/htmlcov/class_index.html +313 -0
  7. dynapydantic-0.1.1/htmlcov/coverage_html_cb_6fb7b396.js → dynapydantic-0.2.0/htmlcov/coverage_html_cb_bcae5fc4.js +12 -10
  8. dynapydantic-0.2.0/htmlcov/function_index.html +418 -0
  9. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/index.html +80 -27
  10. dynapydantic-0.2.0/htmlcov/status.json +1 -0
  11. dynapydantic-0.1.1/htmlcov/style_cb_81f8c14c.css → dynapydantic-0.2.0/htmlcov/style_cb_8432e98f.css +52 -4
  12. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d___init___py.html +27 -25
  13. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d_exceptions_py.html +9 -9
  14. dynapydantic-0.2.0/htmlcov/z_f3e6dac33013b94d_polymorphic_py.html +128 -0
  15. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d_subclass_tracking_model_py.html +164 -139
  16. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/z_f3e6dac33013b94d_tracking_group_py.html +210 -206
  17. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/mkdocs.yml +1 -1
  18. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/pyproject.toml +11 -1
  19. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/__init__.py +2 -0
  20. dynapydantic-0.2.0/src/dynapydantic/polymorphic.py +29 -0
  21. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/subclass_tracking_model.py +37 -12
  22. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/tracking_group.py +11 -7
  23. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/cli.py +4 -2
  24. dynapydantic-0.2.0/tests/example/base-package/uv.lock +149 -0
  25. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/test_plugins.py +4 -2
  26. dynapydantic-0.2.0/tests/test_polymorphic.py +88 -0
  27. dynapydantic-0.2.0/tests/test_recursive_models.py +64 -0
  28. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/test_subclass_tracking_model.py +6 -1
  29. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/test_tracking_group.py +1 -1
  30. dynapydantic-0.2.0/uv.lock +1499 -0
  31. dynapydantic-0.1.1/htmlcov/class_index.html +0 -213
  32. dynapydantic-0.1.1/htmlcov/function_index.html +0 -283
  33. dynapydantic-0.1.1/htmlcov/status.json +0 -1
  34. dynapydantic-0.1.1/uv.lock +0 -1266
  35. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.github/workflows/deploy-docs.yml +0 -0
  36. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.gitignore +0 -0
  37. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/.python-version +0 -0
  38. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/LICENSE +0 -0
  39. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/docs/README.md +0 -0
  40. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/docs/reference.md +0 -0
  41. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/.gitignore +0 -0
  42. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/favicon_32_cb_58284776.png +0 -0
  43. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/htmlcov/keybd_closed_cb_ce680311.png +0 -0
  44. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/exceptions.py +0 -0
  45. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/src/dynapydantic/py.typed +0 -0
  46. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/__init__.py +0 -0
  47. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/animal-plugins/animal_plugins/__init__.py +0 -0
  48. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/animal-plugins/pyproject.toml +0 -0
  49. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/__init__.py +0 -0
  50. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/__main__.py +0 -0
  51. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/animal.py +0 -0
  52. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/cat.py +0 -0
  53. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/circle.py +0 -0
  54. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/base_package/shape.py +0 -0
  55. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/base-package/pyproject.toml +0 -0
  56. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/pyproject.toml +0 -0
  57. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/shape_plugins/__init__.py +0 -0
  58. {dynapydantic-0.1.1 → dynapydantic-0.2.0}/tests/example/shape-plugins/shape_plugins/plugin_classes.py +0 -0
  59. {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
@@ -10,5 +10,6 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
12
  - uses: actions/checkout@v3
13
- - uses: actions/setup-python@v3
13
+ - name: Install uv
14
+ uses: astral-sh/setup-uv@v6
14
15
  - uses: pre-commit/action@v3.0.1
@@ -29,3 +29,12 @@ repos:
29
29
  rev: 0.7.20
30
30
  hooks:
31
31
  - id: uv-lock
32
+
33
+ - repo: local
34
+ hooks:
35
+ - id: pyrefly
36
+ name: pyrefly type check
37
+ entry: uv run pyrefly check
38
+ language: system
39
+ types: [python]
40
+ pass_filenames: false
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dynapydantic
3
- Version: 0.1.1
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
  [![CI](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml/badge.svg)](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
14
14
  [![Pre-commit](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
15
15
  [![Docs](https://img.shields.io/badge/docs-Docs-blue?style=flat-square&logo=github&logoColor=white&link=https://psalvaggio.github.io/dynapydantic/dev/)](https://psalvaggio.github.io/dynapydantic/dev/)
16
+ [![PyPI - Version](https://img.shields.io/pypi/v/dynapydantic)](https://pypi.org/project/dynapydantic/)
17
+ [![Coverage Status](https://coveralls.io/repos/github/psalvaggio/dynapydantic/badge.svg?branch=main)](https://coveralls.io/github/psalvaggio/dynapydantic?branch=main)
18
+ [![Conda Version](https://img.shields.io/conda/v/conda-forge/dynapydantic)](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
- Usage
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 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`.
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. 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.
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 by
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.union() # call after all subclasses have been registered
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
  [![CI](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml/badge.svg)](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
4
4
  [![Pre-commit](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
5
5
  [![Docs](https://img.shields.io/badge/docs-Docs-blue?style=flat-square&logo=github&logoColor=white&link=https://psalvaggio.github.io/dynapydantic/dev/)](https://psalvaggio.github.io/dynapydantic/dev/)
6
+ [![PyPI - Version](https://img.shields.io/pypi/v/dynapydantic)](https://pypi.org/project/dynapydantic/)
7
+ [![Coverage Status](https://coveralls.io/repos/github/psalvaggio/dynapydantic/badge.svg?branch=main)](https://coveralls.io/github/psalvaggio/dynapydantic?branch=main)
8
+ [![Conda Version](https://img.shields.io/conda/v/conda-forge/dynapydantic)](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
- Usage
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 in
57
- type annotations. This union is based on a discriminator field, which was
58
- configured by the `discriminator_field` argument to `TrackingGroup`. The field
59
- can be created by hand, as was shown with `B`, or `dynapydantic` will inject it
60
- for you, as was shown with `A`.
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. The most common value to
67
- pass here would be `lambda cls: cls.__name__`, to use the name of the class as
68
- the discriminator value.
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 by
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.union() # call after all subclasses have been registered
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.