dynapydantic 0.1.0__py3-none-any.whl → 0.2.0__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/__init__.py +2 -0
- dynapydantic/polymorphic.py +29 -0
- dynapydantic/subclass_tracking_model.py +55 -13
- dynapydantic/tracking_group.py +11 -7
- dynapydantic-0.2.0.dist-info/METADATA +222 -0
- dynapydantic-0.2.0.dist-info/RECORD +10 -0
- {dynapydantic-0.1.0.dist-info → dynapydantic-0.2.0.dist-info}/WHEEL +1 -1
- dynapydantic-0.1.0.dist-info/METADATA +0 -21
- dynapydantic-0.1.0.dist-info/RECORD +0 -9
- {dynapydantic-0.1.0.dist-info → dynapydantic-0.2.0.dist-info}/licenses/LICENSE +0 -0
dynapydantic/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from .exceptions import (
|
|
|
6
6
|
Error,
|
|
7
7
|
RegistrationError,
|
|
8
8
|
)
|
|
9
|
+
from .polymorphic import Polymorphic
|
|
9
10
|
from .subclass_tracking_model import SubclassTrackingModel
|
|
10
11
|
from .tracking_group import TrackingGroup
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@ __all__ = [
|
|
|
13
14
|
"AmbiguousDiscriminatorValueError",
|
|
14
15
|
"ConfigurationError",
|
|
15
16
|
"Error",
|
|
17
|
+
"Polymorphic",
|
|
16
18
|
"RegistrationError",
|
|
17
19
|
"SubclassTrackingModel",
|
|
18
20
|
"TrackingGroup",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Definition for Polymorphic"""
|
|
2
|
+
|
|
3
|
+
import typing as ty
|
|
4
|
+
|
|
5
|
+
from .subclass_tracking_model import SubclassTrackingModel
|
|
6
|
+
|
|
7
|
+
ModelT = ty.TypeVar("ModelT", bound=SubclassTrackingModel)
|
|
8
|
+
|
|
9
|
+
if ty.TYPE_CHECKING: # pragma: no cover
|
|
10
|
+
Polymorphic = ty.Annotated[ModelT, ...]
|
|
11
|
+
else:
|
|
12
|
+
|
|
13
|
+
class Polymorphic:
|
|
14
|
+
"""Annotation used to mark a type as having duck-typing behavior
|
|
15
|
+
|
|
16
|
+
This annotation is only valid for SubclassTrackingModel's.
|
|
17
|
+
|
|
18
|
+
Similar to SerializeAsAny, a field annotated with this shall serialize as
|
|
19
|
+
according to its actual type, not the field annotation type. In addition,
|
|
20
|
+
parsing will function as if the field annotation type were the union of
|
|
21
|
+
all tracked subclasses.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __class_getitem__(cls, item: ModelT) -> ty.Any: # noqa: ANN401
|
|
25
|
+
"""Get the annotation for the pydantic field"""
|
|
26
|
+
if not isinstance(item, type):
|
|
27
|
+
msg = f"dynapydantic.Polymorphic must be given a type, not {item}"
|
|
28
|
+
raise TypeError(msg)
|
|
29
|
+
return ty.Annotated[item, SubclassTrackingModel.PydanticAdaptor]
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"""Base class for dynamic pydantic models"""
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
import typing as ty
|
|
4
5
|
|
|
5
6
|
import pydantic
|
|
7
|
+
from pydantic import GetCoreSchemaHandler
|
|
8
|
+
from pydantic.errors import PydanticSchemaGenerationError
|
|
9
|
+
from pydantic_core import core_schema
|
|
6
10
|
|
|
7
11
|
from .exceptions import ConfigurationError
|
|
8
12
|
from .tracking_group import TrackingGroup
|
|
@@ -26,19 +30,37 @@ def direct_children_of_base_in_mro(derived: type, base: type) -> list[type]:
|
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
class SubclassTrackingModel(pydantic.BaseModel):
|
|
29
|
-
"""Subclass-tracking BaseModel
|
|
33
|
+
"""Subclass-tracking BaseModel
|
|
34
|
+
|
|
35
|
+
This will inject a TrackingGroup into your class and automate the
|
|
36
|
+
registration of subclasses.
|
|
37
|
+
|
|
38
|
+
Inheriting from this class will augment your class with the following
|
|
39
|
+
members functions:
|
|
40
|
+
1. registered_subclasses() -> dict[str, type[cls]]:
|
|
41
|
+
This will return a mapping of discriminator value to the corresponding
|
|
42
|
+
sublcass. See TrackingGroup.models for details.
|
|
43
|
+
2. union() -> typing.GenericAlias:
|
|
44
|
+
This will return an (optionally) annotated subclass union. See
|
|
45
|
+
TrackingGroup.union() for details.
|
|
46
|
+
3. load_plugins() -> None:
|
|
47
|
+
If plugin_entry_point was specified, then this method will load plugin
|
|
48
|
+
packages to discover additional subclasses. See
|
|
49
|
+
TrackingGroup.load_plugins for more details.
|
|
50
|
+
"""
|
|
30
51
|
|
|
31
|
-
def __init_subclass__(
|
|
32
|
-
cls,
|
|
33
|
-
*args,
|
|
34
|
-
exclude_from_union: bool | None = None,
|
|
35
|
-
**kwargs,
|
|
36
|
-
) -> None:
|
|
52
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
37
53
|
"""Subclass hook"""
|
|
38
|
-
# Intercept any kwargs that are intended for TrackingGroup
|
|
39
|
-
|
|
54
|
+
# Intercept any kwargs that are intended for TrackingGroup or
|
|
55
|
+
# __pydantic_init_subclass__
|
|
56
|
+
sig = inspect.signature(SubclassTrackingModel.__pydantic_init_subclass__)
|
|
57
|
+
super().__init_subclass__(
|
|
40
58
|
*args,
|
|
41
|
-
**{
|
|
59
|
+
**{
|
|
60
|
+
k: v
|
|
61
|
+
for k, v in kwargs.items()
|
|
62
|
+
if k not in TrackingGroup.model_fields and k not in sig.parameters
|
|
63
|
+
},
|
|
42
64
|
)
|
|
43
65
|
|
|
44
66
|
@classmethod
|
|
@@ -60,8 +82,8 @@ class SubclassTrackingModel(pydantic.BaseModel):
|
|
|
60
82
|
},
|
|
61
83
|
)
|
|
62
84
|
|
|
63
|
-
if isinstance(getattr(cls, "tracking_config", None), TrackingGroup):
|
|
64
|
-
cls.__DYNAPYDANTIC__ =
|
|
85
|
+
if isinstance((tc := getattr(cls, "tracking_config", None)), TrackingGroup):
|
|
86
|
+
cls.__DYNAPYDANTIC__ = tc
|
|
65
87
|
else:
|
|
66
88
|
try:
|
|
67
89
|
cls.__DYNAPYDANTIC__: TrackingGroup = TrackingGroup.model_validate(
|
|
@@ -101,7 +123,7 @@ class SubclassTrackingModel(pydantic.BaseModel):
|
|
|
101
123
|
|
|
102
124
|
cls.union = staticmethod(_union)
|
|
103
125
|
|
|
104
|
-
def _subclasses() -> dict[str, type[
|
|
126
|
+
def _subclasses() -> dict[str, type[pydantic.BaseModel]]:
|
|
105
127
|
"""Return a mapping of discriminator values to registered model"""
|
|
106
128
|
return cls.__DYNAPYDANTIC__.models
|
|
107
129
|
|
|
@@ -117,3 +139,23 @@ class SubclassTrackingModel(pydantic.BaseModel):
|
|
|
117
139
|
supers = direct_children_of_base_in_mro(cls, SubclassTrackingModel)
|
|
118
140
|
for base in supers:
|
|
119
141
|
base.__DYNAPYDANTIC__.register_model(cls)
|
|
142
|
+
|
|
143
|
+
class PydanticAdaptor:
|
|
144
|
+
"""Pydantic type adaptor for SubclassTrackingModel"""
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def __get_pydantic_core_schema__(
|
|
148
|
+
source_type: ty.Any, # noqa: ANN401
|
|
149
|
+
handler: GetCoreSchemaHandler,
|
|
150
|
+
) -> core_schema.CoreSchema:
|
|
151
|
+
"""Get the pydantic schema for this type"""
|
|
152
|
+
if not isinstance(source_type, type) or not issubclass(
|
|
153
|
+
source_type,
|
|
154
|
+
SubclassTrackingModel,
|
|
155
|
+
):
|
|
156
|
+
msg = (
|
|
157
|
+
f"{source_type} was not a SubclassTrackingModel, "
|
|
158
|
+
"so it is incompatible with dynapydantic.Polymorphic"
|
|
159
|
+
)
|
|
160
|
+
raise PydanticSchemaGenerationError(msg)
|
|
161
|
+
return handler(source_type.union())
|
dynapydantic/tracking_group.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Base class for dynamic pydantic models"""
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import typing as ty
|
|
4
5
|
|
|
5
6
|
import pydantic
|
|
@@ -27,10 +28,11 @@ def _inject_discriminator_field(
|
|
|
27
28
|
"""
|
|
28
29
|
cls.model_fields[disc_field] = pydantic.fields.FieldInfo(
|
|
29
30
|
default=value,
|
|
30
|
-
annotation=ty.Literal[value],
|
|
31
|
+
annotation=ty.Literal[value], # type: ignore[not-a-type]
|
|
31
32
|
frozen=True,
|
|
32
33
|
)
|
|
33
|
-
|
|
34
|
+
with contextlib.suppress(pydantic.errors.PydanticUndefinedAnnotation):
|
|
35
|
+
cls.model_rebuild(force=True)
|
|
34
36
|
return cls.model_fields[disc_field]
|
|
35
37
|
|
|
36
38
|
|
|
@@ -95,7 +97,7 @@ class TrackingGroup(pydantic.BaseModel):
|
|
|
95
97
|
discriminator field must be declared by hand.
|
|
96
98
|
"""
|
|
97
99
|
|
|
98
|
-
def _wrapper(cls: type[pydantic.BaseModel]) ->
|
|
100
|
+
def _wrapper(cls: type[pydantic.BaseModel]) -> type[pydantic.BaseModel]:
|
|
99
101
|
disc = self.discriminator_field
|
|
100
102
|
field = cls.model_fields.get(self.discriminator_field)
|
|
101
103
|
if field is None:
|
|
@@ -169,8 +171,7 @@ class TrackingGroup(pydantic.BaseModel):
|
|
|
169
171
|
unique default value in the group.
|
|
170
172
|
"""
|
|
171
173
|
disc = self.discriminator_field
|
|
172
|
-
|
|
173
|
-
value = field.default
|
|
174
|
+
value = cls.model_fields[disc].default
|
|
174
175
|
if value == pydantic_core.PydanticUndefined:
|
|
175
176
|
msg = (
|
|
176
177
|
f"{cls.__name__}.{disc} had no default value, it must "
|
|
@@ -187,17 +188,20 @@ class TrackingGroup(pydantic.BaseModel):
|
|
|
187
188
|
|
|
188
189
|
self.models[value] = cls
|
|
189
190
|
|
|
190
|
-
def union(self, *, annotated: bool = True) -> ty.
|
|
191
|
+
def union(self, *, annotated: bool = True) -> ty.Any: # noqa: ANN401
|
|
191
192
|
"""Return the union of all registered models"""
|
|
192
193
|
return (
|
|
193
194
|
ty.Annotated[
|
|
194
195
|
ty.Union[ # noqa: UP007
|
|
195
|
-
|
|
196
|
+
# This is fundamentally incompatible with static type
|
|
197
|
+
# checking, as this is resolved at runtime.
|
|
198
|
+
tuple( # type: ignore[not-a-type]
|
|
196
199
|
ty.Annotated[x, pydantic.Tag(v)] for v, x in self.models.items()
|
|
197
200
|
)
|
|
198
201
|
],
|
|
199
202
|
pydantic.Field(discriminator=self.discriminator_field),
|
|
200
203
|
]
|
|
201
204
|
if annotated
|
|
205
|
+
# type: ignore[not-a-type]
|
|
202
206
|
else ty.Union[tuple(self.models.values())] # noqa: UP007
|
|
203
207
|
)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dynapydantic
|
|
3
|
+
Version: 0.2.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
|
+
[](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)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
`dynapydantic` is an extension to the [pydantic](https://pydantic.dev) Python
|
|
22
|
+
package that allow for dynamic tracking of `pydantic.BaseModel` subclasses.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
This project can be installed via PyPI:
|
|
26
|
+
```
|
|
27
|
+
pip install dynapydantic
|
|
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
|
+
|
|
107
|
+
|
|
108
|
+
## How it works
|
|
109
|
+
|
|
110
|
+
### `TrackingGroup`
|
|
111
|
+
The core entity in this library is the `dynapydantic.TrackingGroup`:
|
|
112
|
+
```python
|
|
113
|
+
import typing as ty
|
|
114
|
+
|
|
115
|
+
import dynapydantic
|
|
116
|
+
import pydantic
|
|
117
|
+
|
|
118
|
+
mygroup = dynapydantic.TrackingGroup(
|
|
119
|
+
name="mygroup",
|
|
120
|
+
discriminator_field="name"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@mygroup.register("A")
|
|
124
|
+
class A(pydantic.BaseModel):
|
|
125
|
+
"""A class to be tracked, will be tracked as "A"."""
|
|
126
|
+
a: int
|
|
127
|
+
|
|
128
|
+
@mygroup.register()
|
|
129
|
+
class B(pydantic.BaseModel):
|
|
130
|
+
"""Another class, will be tracked as "B"."""
|
|
131
|
+
name: ty.Literal["B"] = "B"
|
|
132
|
+
a: int
|
|
133
|
+
|
|
134
|
+
class Model(pydantic.BaseModel):
|
|
135
|
+
"""A model that can have A or B"""
|
|
136
|
+
field: mygroup.union() # call after all subclasses have been registered
|
|
137
|
+
|
|
138
|
+
print(Model(field={"name": "A", "a": 4})) # field=A(a=4, name='A')
|
|
139
|
+
print(Model(field={"name": "B", "a": 5})) # field=B(name='B', a=5)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The `union()` method produces a [discriminated union](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions)
|
|
143
|
+
of all registered `pydantic.BaseModel` subclasses. It also accepts an
|
|
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`.
|
|
150
|
+
|
|
151
|
+
`TrackingGroup` has a few opt-in features to make it more powerful and easier to use:
|
|
152
|
+
1. `discriminator_value_generator`: This parameter is a optional callback
|
|
153
|
+
function that is called with each class that gets registered and produces a
|
|
154
|
+
default value for the discriminator field. This allows the user to call
|
|
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.
|
|
158
|
+
2. `plugin_entry_point`: This parameter indicates to `dynapydantic` that there
|
|
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
|
|
161
|
+
example of how this works.
|
|
162
|
+
|
|
163
|
+
### `SubclassTrackingModel`
|
|
164
|
+
The most common use case of this pattern is to automatically register subclasses
|
|
165
|
+
of a given `pydantic.BaseModel`. This is supported via the use of
|
|
166
|
+
`dynapydantic.SubclassTrackingModel`. For example:
|
|
167
|
+
```python
|
|
168
|
+
import typing as ty
|
|
169
|
+
|
|
170
|
+
import dynapydantic
|
|
171
|
+
import pydantic
|
|
172
|
+
|
|
173
|
+
class Base(
|
|
174
|
+
dynapydantic.SubclassTrackingModel,
|
|
175
|
+
discriminator_field="name",
|
|
176
|
+
discriminator_value_generator=lambda cls: cls.__name__,
|
|
177
|
+
):
|
|
178
|
+
"""Base model, will track its subclasses"""
|
|
179
|
+
|
|
180
|
+
# The TrackingGroup can be specified here like model_config, or passed in
|
|
181
|
+
# kwargs of the class declaration, just like how model_config works with
|
|
182
|
+
# pydantic.BaseModel. If you do it like this, you have to give the tracking
|
|
183
|
+
# group a name, whereas using kwargs will generate the name for you.
|
|
184
|
+
# tracking_config: ty.ClassVar[dynapydantic.TrackingGroup] = dynapydantic.TrackingGroup(
|
|
185
|
+
# name="BaseSubclasses",
|
|
186
|
+
# discriminator_field="name",
|
|
187
|
+
# discriminator_value_generator=lambda cls: cls.__name__,
|
|
188
|
+
# )
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class Intermediate(Base, exclude_from_union=True):
|
|
192
|
+
"""Subclasses can opt out of being tracked"""
|
|
193
|
+
|
|
194
|
+
class Derived1(Intermediate):
|
|
195
|
+
"""Non-direct descendants are registered"""
|
|
196
|
+
a: int
|
|
197
|
+
|
|
198
|
+
class Derived2(Intermediate):
|
|
199
|
+
"""You can override the value generator if desired"""
|
|
200
|
+
name: ty.Literal["Custom"] = "Custom"
|
|
201
|
+
a: int
|
|
202
|
+
|
|
203
|
+
print(Base.registered_subclasses())
|
|
204
|
+
# {'Derived1': <class '__main__.Derived1'>, 'Custom': <class '__main__.Derived2'>}
|
|
205
|
+
|
|
206
|
+
# if plugin_entry_point was specificed, load plugin packages
|
|
207
|
+
# Base.load_plugins()
|
|
208
|
+
|
|
209
|
+
class Model(pydantic.BaseModel):
|
|
210
|
+
"""A model that can have any registered Base subclass"""
|
|
211
|
+
field: dynapydantic.Polymorphic[Base]
|
|
212
|
+
|
|
213
|
+
print(Model(field={"name": "Derived1", "a": 4}))
|
|
214
|
+
# field=Derived1(a=4, name='Derived1')
|
|
215
|
+
print(Model(field={"name": "Custom", "a": 5}))
|
|
216
|
+
# field=Derived2(name='Custom', a=5)
|
|
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.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
dynapydantic/__init__.py,sha256=5i1hbdkJO6fUwO1Sal9CfMv-htg_9MibQemwJH0gFGY,508
|
|
2
|
+
dynapydantic/exceptions.py,sha256=R1wJj-FmKv2JdYSG5HVMkZ0zLyFRKJRuUsBxXGnEjsQ,394
|
|
3
|
+
dynapydantic/polymorphic.py,sha256=qt7m2LBZH5jJzV0Uv8ipO-LUvWDXXPk7rRbAyJ7ghcg,1097
|
|
4
|
+
dynapydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
dynapydantic/subclass_tracking_model.py,sha256=-dVA7eAOlzPDLg75S2PNZ5nuVpZmgbwA3drxclKRNyc,5980
|
|
6
|
+
dynapydantic/tracking_group.py,sha256=EdY9REjM_bhW7MAbFuKtm52ZZvQhBDVb_sght12KxAo,7673
|
|
7
|
+
dynapydantic-0.2.0.dist-info/METADATA,sha256=BHEgxpPBCak2VsCIDgYfkij1OJWYxgtEKaMX47mHqUc,7653
|
|
8
|
+
dynapydantic-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
9
|
+
dynapydantic-0.2.0.dist-info/licenses/LICENSE,sha256=I6pwCRw86q30bFjJohgVzXYgCLNCWN3A4jNGJX2iVM4,1073
|
|
10
|
+
dynapydantic-0.2.0.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.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
dynapydantic/__init__.py,sha256=vIK1dasCAghxjXaUjw8blTHLFwsHZ9L2txuX_8AB7cQ,452
|
|
2
|
-
dynapydantic/exceptions.py,sha256=R1wJj-FmKv2JdYSG5HVMkZ0zLyFRKJRuUsBxXGnEjsQ,394
|
|
3
|
-
dynapydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
dynapydantic/subclass_tracking_model.py,sha256=-pE7gEd3ZWmeJ6QW32a4-g-KIK4yEZ-gfvBUPAv0AnM,4145
|
|
5
|
-
dynapydantic/tracking_group.py,sha256=-rorYSrjHCCV6yeiulA8lI8e9aVrqG-u57UDgD5gZKA,7342
|
|
6
|
-
dynapydantic-0.1.0.dist-info/METADATA,sha256=WWZavxdQLdBBtxOZ0li5JTeNCNtu_QCb573_1s2ytZg,905
|
|
7
|
-
dynapydantic-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
dynapydantic-0.1.0.dist-info/licenses/LICENSE,sha256=I6pwCRw86q30bFjJohgVzXYgCLNCWN3A4jNGJX2iVM4,1073
|
|
9
|
-
dynapydantic-0.1.0.dist-info/RECORD,,
|
|
File without changes
|