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.
@@ -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
+ [![CI](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml/badge.svg)](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
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
+ [![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
+
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=-pE7gEd3ZWmeJ6QW32a4-g-KIK4yEZ-gfvBUPAv0AnM,4145
4
+ dynapydantic/subclass_tracking_model.py,sha256=MmxQgbIusULSEM04R7Yz5kHCHZCeb-vLHenbNctmZPM,4914
5
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,,
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
- [![CI](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml/badge.svg)](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
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
-
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.