Name | dynapydantic JSON |
Version |
0.1.1
JSON |
| download |
home_page | None |
Summary | Dyanmic pydantic models |
upload_time | 2025-07-11 20:37:40 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.10 |
license | None |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# dynapydantic
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)
[](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)
[](https://psalvaggio.github.io/dynapydantic/dev/)
`dynapydantic` is an extension to the [pydantic](https://pydantic.dev) Python
package that allow for dynamic tracking of `pydantic.BaseModel` subclasses.
Installation
==
This project can be installed via PyPI:
```
pip install dynapydantic
```
Usage
==
`TrackingGroup`
--
The core entity in this library is the `dynapydantic.TrackingGroup`:
```python
import typing as ty
import dynapydantic
import pydantic
mygroup = dynapydantic.TrackingGroup(
name="mygroup",
discriminator_field="name"
)
@mygroup.register("A")
class A(pydantic.BaseModel):
"""A class to be tracked, will be tracked as "A"."""
a: int
@mygroup.register()
class B(pydantic.BaseModel):
"""Another class, will be tracked as "B"."""
name: ty.Literal["B"] = "B"
a: int
class Model(pydantic.BaseModel):
"""A model that can have A or B"""
field: mygroup.union() # call after all subclasses have been registered
print(Model(field={"name": "A", "a": 4})) # field=A(a=4, name='A')
print(Model(field={"name": "B", "a": 5})) # field=B(name='B', a=5)
```
The `union()` method produces a [discriminated union](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions)
of all registered `pydantic.BaseModel` subclasses. It also accepts an
`annotated=False` keyword argument to produce a plain `typing.Union` for use in
type annotations. This union is based on a discriminator field, which was
configured by the `discriminator_field` argument to `TrackingGroup`. The field
can be created by hand, as was shown with `B`, or `dynapydantic` will inject it
for you, as was shown with `A`.
`TrackingGroup` has a few opt-in features to make it more powerful and easier to use:
1. `discriminator_value_generator`: This parameter is a optional callback
function that is called with each class that gets registered and produces a
default value for the discriminator field. This allows the user to call
`register()` without a value for the discriminator. The most common value to
pass here would be `lambda cls: cls.__name__`, to use the name of the class as
the discriminator value.
2. `plugin_entry_point`: This parameter indicates to `dynapydantic` that there
might be models to be discovered in other packages. Packages are discovered by
the Python entrypoint mechanism. See the `tests/example` directory for an
example of how this works.
`SubclassTrackingModel`
--
The most common use case of this pattern is to automatically register subclasses
of a given `pydantic.BaseModel`. This is supported via the use of
`dynapydantic.SubclassTrackingModel`. For example:
```python
import typing as ty
import dynapydantic
import pydantic
class Base(
dynapydantic.SubclassTrackingModel,
discriminator_field="name",
discriminator_value_generator=lambda cls: cls.__name__,
):
"""Base model, will track its subclasses"""
# The TrackingGroup can be specified here like model_config, or passed in
# kwargs of the class declaration, just like how model_config works with
# pydantic.BaseModel. If you do it like this, you have to give the tracking
# group a name
# tracking_config: ty.ClassVar[dynapydantic.TrackingGroup] = dynapydantic.TrackingGroup(
# name="BaseSubclasses",
# discriminator_field="name",
# discriminator_value_generator=lambda cls: cls.__name__,
# )
class Intermediate(Base, exclude_from_union=True):
"""Subclasses can opt out of being tracked"""
class Derived1(Intermediate):
"""Non-direct descendants are registered"""
a: int
class Derived2(Intermediate):
"""You can override the value generator if desired"""
name: ty.Literal["Custom"] = "Custom"
a: int
print(Base.registered_subclasses())
# {'Derived1': <class '__main__.Derived1'>, 'Custom': <class '__main__.Derived2'>}
# if plugin_entry_point was specificed, load plugin packages
# Base.load_plugins()
class Model(pydantic.BaseModel):
"""A model that can have any registered Base subclass"""
field: Base.union() # call after all subclasses have been registered
print(Model(field={"name": "Derived1", "a": 4}))
# field=Derived1(a=4, name='Derived1')
print(Model(field={"name": "Custom", "a": 5}))
# field=Derived2(name='Custom', a=5)
```
Raw data
{
"_id": null,
"home_page": null,
"name": "dynapydantic",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": null,
"author": null,
"author_email": "Philip Salvaggio <salvaggio.philip@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/9a/f6/5ee0376aaac2f289d6117799a0e2aa7b01296702383e7f03da12215d6dff/dynapydantic-0.1.1.tar.gz",
"platform": null,
"description": "# dynapydantic\n\n[](https://github.com/psalvaggio/dynapydantic/actions/workflows/ci.yml)\n[](https://github.com/psalvaggio/dynapydantic/actions/workflows/pre-commit.yml)\n[](https://psalvaggio.github.io/dynapydantic/dev/)\n\n\n`dynapydantic` is an extension to the [pydantic](https://pydantic.dev) Python\npackage that allow for dynamic tracking of `pydantic.BaseModel` subclasses.\n\nInstallation\n==\nThis project can be installed via PyPI:\n```\npip install dynapydantic\n```\n\nUsage\n==\n\n`TrackingGroup`\n--\nThe core entity in this library is the `dynapydantic.TrackingGroup`:\n```python\nimport typing as ty\n\nimport dynapydantic\nimport pydantic\n\nmygroup = dynapydantic.TrackingGroup(\n name=\"mygroup\",\n discriminator_field=\"name\"\n)\n\n@mygroup.register(\"A\")\nclass A(pydantic.BaseModel):\n \"\"\"A class to be tracked, will be tracked as \"A\".\"\"\"\n a: int\n\n@mygroup.register()\nclass B(pydantic.BaseModel):\n \"\"\"Another class, will be tracked as \"B\".\"\"\"\n name: ty.Literal[\"B\"] = \"B\"\n a: int\n\nclass Model(pydantic.BaseModel):\n \"\"\"A model that can have A or B\"\"\"\n field: mygroup.union() # call after all subclasses have been registered\n\nprint(Model(field={\"name\": \"A\", \"a\": 4})) # field=A(a=4, name='A')\nprint(Model(field={\"name\": \"B\", \"a\": 5})) # field=B(name='B', a=5)\n```\n\nThe `union()` method produces a [discriminated union](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions)\nof all registered `pydantic.BaseModel` subclasses. It also accepts an\n`annotated=False` keyword argument to produce a plain `typing.Union` for use in\ntype annotations. This union is based on a discriminator field, which was\nconfigured by the `discriminator_field` argument to `TrackingGroup`. The field\ncan be created by hand, as was shown with `B`, or `dynapydantic` will inject it\nfor you, as was shown with `A`.\n\n`TrackingGroup` has a few opt-in features to make it more powerful and easier to use:\n1. `discriminator_value_generator`: This parameter is a optional callback\n function that is called with each class that gets registered and produces a\n default value for the discriminator field. This allows the user to call\n `register()` without a value for the discriminator. The most common value to\n pass here would be `lambda cls: cls.__name__`, to use the name of the class as\n the discriminator value.\n2. `plugin_entry_point`: This parameter indicates to `dynapydantic` that there\n might be models to be discovered in other packages. Packages are discovered by\n the Python entrypoint mechanism. See the `tests/example` directory for an\n example of how this works.\n\n`SubclassTrackingModel`\n--\nThe most common use case of this pattern is to automatically register subclasses\nof a given `pydantic.BaseModel`. This is supported via the use of\n`dynapydantic.SubclassTrackingModel`. For example:\n```python\nimport typing as ty\n\nimport dynapydantic\nimport pydantic\n\nclass Base(\n dynapydantic.SubclassTrackingModel,\n discriminator_field=\"name\",\n discriminator_value_generator=lambda cls: cls.__name__,\n):\n \"\"\"Base model, will track its subclasses\"\"\"\n\n # The TrackingGroup can be specified here like model_config, or passed in\n # kwargs of the class declaration, just like how model_config works with\n # pydantic.BaseModel. If you do it like this, you have to give the tracking\n # group a name\n # tracking_config: ty.ClassVar[dynapydantic.TrackingGroup] = dynapydantic.TrackingGroup(\n # name=\"BaseSubclasses\",\n # discriminator_field=\"name\",\n # discriminator_value_generator=lambda cls: cls.__name__,\n # )\n\n\nclass Intermediate(Base, exclude_from_union=True):\n \"\"\"Subclasses can opt out of being tracked\"\"\"\n\nclass Derived1(Intermediate):\n \"\"\"Non-direct descendants are registered\"\"\"\n a: int\n\nclass Derived2(Intermediate):\n \"\"\"You can override the value generator if desired\"\"\"\n name: ty.Literal[\"Custom\"] = \"Custom\"\n a: int\n\nprint(Base.registered_subclasses())\n# {'Derived1': <class '__main__.Derived1'>, 'Custom': <class '__main__.Derived2'>}\n\n# if plugin_entry_point was specificed, load plugin packages\n# Base.load_plugins()\n\nclass Model(pydantic.BaseModel):\n \"\"\"A model that can have any registered Base subclass\"\"\"\n field: Base.union() # call after all subclasses have been registered\n\nprint(Model(field={\"name\": \"Derived1\", \"a\": 4}))\n# field=Derived1(a=4, name='Derived1')\nprint(Model(field={\"name\": \"Custom\", \"a\": 5}))\n# field=Derived2(name='Custom', a=5)\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Dyanmic pydantic models",
"version": "0.1.1",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "2f6304fb795b1852937379a6ebd0746718ece7a92c594e2ed7a0b1aa3a506d75",
"md5": "78deec23120e71130d84e3f510073dc4",
"sha256": "958ea7d67de4f35933d79d619ccd5c5970ba38fe455f1f45343a34c91ccb3a7f"
},
"downloads": -1,
"filename": "dynapydantic-0.1.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "78deec23120e71130d84e3f510073dc4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 8202,
"upload_time": "2025-07-11T20:37:39",
"upload_time_iso_8601": "2025-07-11T20:37:39.309159Z",
"url": "https://files.pythonhosted.org/packages/2f/63/04fb795b1852937379a6ebd0746718ece7a92c594e2ed7a0b1aa3a506d75/dynapydantic-0.1.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "9af65ee0376aaac2f289d6117799a0e2aa7b01296702383e7f03da12215d6dff",
"md5": "1c82b7df9fbad2fa33d4cd23c148c4ff",
"sha256": "42116241990de5f82a1e47190115f1420e068ef1c553f03935c4023623235a12"
},
"downloads": -1,
"filename": "dynapydantic-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "1c82b7df9fbad2fa33d4cd23c148c4ff",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 102912,
"upload_time": "2025-07-11T20:37:40",
"upload_time_iso_8601": "2025-07-11T20:37:40.336667Z",
"url": "https://files.pythonhosted.org/packages/9a/f6/5ee0376aaac2f289d6117799a0e2aa7b01296702383e7f03da12215d6dff/dynapydantic-0.1.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-11 20:37:40",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "dynapydantic"
}