tigrbl_model 0.4.5__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.
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ https://www.apache.org/licenses/
4
+
5
+ Copyright 2025-2026 Jacob Stewart
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ https://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
@@ -0,0 +1,4 @@
1
+ tigrbl_model
2
+ Copyright 2025-2026 Jacob Stewart
3
+
4
+ Licensed under the Apache License, Version 2.0.
@@ -0,0 +1,166 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigrbl_model
3
+ Version: 0.4.5
4
+ Summary: Pydantic-independent validation, serialization, and JSON Schema models for Tigrbl.
5
+ License: Apache License
6
+ Version 2.0, January 2004
7
+ https://www.apache.org/licenses/
8
+
9
+ Copyright 2025-2026 Jacob Stewart
10
+
11
+ Licensed under the Apache License, Version 2.0 (the "License");
12
+ you may not use this file except in compliance with the License.
13
+ You may obtain a copy of the License at
14
+
15
+ https://www.apache.org/licenses/LICENSE-2.0
16
+
17
+ Unless required by applicable law or agreed to in writing, software
18
+ distributed under the License is distributed on an "AS IS" BASIS,
19
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ See the License for the specific language governing permissions and
21
+ limitations under the License.
22
+ License-File: LICENSE
23
+ License-File: NOTICE
24
+ Author: Jacob Stewart
25
+ Author-email: jacob@swarmauri.com
26
+ Requires-Python: >=3.10,<3.15
27
+ Classifier: Development Status :: 3 - Alpha
28
+ Classifier: License :: OSI Approved :: Apache Software License
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: Programming Language :: Python :: 3 :: Only
31
+ Classifier: Programming Language :: Python :: 3.10
32
+ Classifier: Programming Language :: Python :: 3.11
33
+ Classifier: Programming Language :: Python :: 3.12
34
+ Classifier: Programming Language :: Python :: 3.13
35
+ Classifier: Programming Language :: Python :: 3.14
36
+ Requires-Dist: pyyaml (>=6.0)
37
+ Requires-Dist: tomli (>=2.0.1) ; python_version < "3.11"
38
+ Requires-Dist: tomli-w (>=1.0)
39
+ Requires-Dist: typing-extensions (>=4.0) ; python_version < "3.11"
40
+ Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
41
+ Project-URL: Organization, https://github.com/tigrbl
42
+ Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/02_model/tigrbl_model
43
+ Description-Content-Type: text/markdown
44
+
45
+ <div align="center">
46
+ <h1>tigrbl_model</h1>
47
+ <p><strong>Pydantic-independent model validation, schema generation, and JSON, YAML, and TOML serialization for Tigrbl.</strong></p>
48
+ <a href="https://pypi.org/project/tigrbl-model/"><img src="https://img.shields.io/pypi/v/tigrbl-model?label=PyPI" alt="PyPI version for tigrbl_model"/></a>
49
+ <a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl_model"/></a>
50
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
51
+ <a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl_model"/></a>
52
+ </div>
53
+
54
+ ## What is tigrbl_model?
55
+
56
+ `tigrbl_model` is Tigrbl's Pydantic-independent model engine. It owns field metadata, validation, serialization, dynamic model construction, forward-reference rebuilding, and JSON Schema generation.
57
+
58
+ ## Why use tigrbl_model?
59
+
60
+ Use it when code needs the Pydantic v2 surface validated by the package tests for Tigrbl without taking a runtime dependency on Pydantic.
61
+
62
+ ## When should I install tigrbl_model?
63
+
64
+ Install it when building Tigrbl extensions, reusable schema models, or applications that need the same model contract as Tigrbl.
65
+
66
+ ## Who is tigrbl_model for?
67
+
68
+ Tigrbl maintainers, extension authors, and application developers who need validated models and portable serialization.
69
+
70
+ ## Where does tigrbl_model fit?
71
+
72
+ It is a foundational package under `pkgs/02_model`, below Tigrbl packages that consume models, validation, and JSON Schema.
73
+
74
+ ## How does tigrbl_model work?
75
+
76
+ Type annotations and `Field` metadata are compiled into an internal field graph. The graph drives validation, dumping, JSON Schema, and the JSON, YAML, and TOML mixins.
77
+
78
+ ## Install
79
+
80
+ ```bash
81
+ uv add tigrbl-model
82
+ ```
83
+
84
+ ```bash
85
+ pip install tigrbl-model
86
+ ```
87
+
88
+ ## Surface Coverage
89
+
90
+ | Surface | Value |
91
+ |---|---|
92
+ | PyPI package | `tigrbl-model` |
93
+ | Repository path | `pkgs/02_model/tigrbl_model` |
94
+ | Python import root | `tigrbl_model` |
95
+ | Console scripts | none |
96
+ | Public mixins | `JsonMixin`, `YamlMixin`, `TomlMixin`, `SerdeMixin` |
97
+ | Runtime Pydantic dependency | none |
98
+ | Legal files | `LICENSE`, `NOTICE` |
99
+ | Supported Python | `3.10 | 3.11 | 3.12 | 3.13 | 3.14` |
100
+
101
+ ## What It Owns
102
+
103
+ - Runtime model construction and field metadata.
104
+ - Annotation-driven validation and structured errors.
105
+ - Dictionary and JSON serialization.
106
+ - JSON Schema generation.
107
+ - Independently composable JSON, YAML, and TOML mixins.
108
+ - `SerdeMixin`, which aggregates all three format mixins.
109
+
110
+ ## Public API and Import Surface
111
+
112
+ The primary imports are `Model`/`BaseModel`, `RootModel`, `Field`, `ConfigDict`, `ValidationError`, `create_model`, `field_validator`, `model_validator`, and the four serialization mixins. See `tigrbl_model.__all__` for the complete supported surface.
113
+
114
+ ## Usage Examples
115
+
116
+ ```python
117
+ from tigrbl_model import Field, Model
118
+
119
+ class Widget(Model):
120
+ name: str
121
+ quantity: int = Field(default=0, ge=0)
122
+
123
+ widget = Widget.model_validate_json('{"name":"bolt","quantity":2}')
124
+ assert widget.model_dump() == {"name": "bolt", "quantity": 2}
125
+ assert Widget.model_validate_yaml(widget.model_dump_yaml()) == widget
126
+ assert Widget.model_validate_toml(widget.model_dump_toml()) == widget
127
+ ```
128
+
129
+ Plain dataclasses can inherit `SerdeMixin` when validation and schema-model behavior are unnecessary.
130
+
131
+ ## How To Choose This Package
132
+
133
+ Choose `tigrbl_model` for the focused model and serialization boundary. Choose the `tigrbl` facade when building a complete API application. Keep Pydantic installed only when independently comparing behavior during development.
134
+
135
+ ## Related Packages
136
+
137
+ - `tigrbl`
138
+ - `tigrbl-core`
139
+ - `tigrbl-base`
140
+ - `tigrbl-runtime`
141
+
142
+ ## Documentation Links
143
+
144
+ - [Supported parity boundary](PARITY.md)
145
+ - [Workspace documentation](https://github.com/tigrbl/tigrbl/blob/master/docs/README.md)
146
+ - [Package catalog](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_CATALOG.md)
147
+ - [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json)
148
+
149
+ ## Support
150
+
151
+ - Community: [Discord](https://discord.gg/K4YTAPapjR)
152
+ - Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues)
153
+ - Repository: [tigrbl/tigrbl](https://github.com/tigrbl/tigrbl)
154
+
155
+ ## Certification Status
156
+
157
+ The package's automated suite compares the supported contract with Pydantic v2 and scans first-party Tigrbl runtime usage. `PARITY.md` records intentional boundaries; Pydantic is a development-only parity oracle and is not a runtime dependency.
158
+
159
+ ## License
160
+
161
+ Licensed under the Apache License, Version 2.0. See `LICENSE` and `NOTICE`.
162
+
163
+ ## Workspace documentation
164
+
165
+ This README is the package-local distribution entry point. See [workspace documentation](../../../docs/README.md), [current conformance target](../../../docs/conformance/CURRENT_TARGET.md), [package catalog](../../../docs/developer/PACKAGE_CATALOG.md), [package layout](../../../docs/developer/PACKAGE_LAYOUT.md), and [SSOT registry](../../../.ssot/registry.json).
166
+
@@ -0,0 +1,121 @@
1
+ <div align="center">
2
+ <h1>tigrbl_model</h1>
3
+ <p><strong>Pydantic-independent model validation, schema generation, and JSON, YAML, and TOML serialization for Tigrbl.</strong></p>
4
+ <a href="https://pypi.org/project/tigrbl-model/"><img src="https://img.shields.io/pypi/v/tigrbl-model?label=PyPI" alt="PyPI version for tigrbl_model"/></a>
5
+ <a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl_model"/></a>
6
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
7
+ <a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl_model"/></a>
8
+ </div>
9
+
10
+ ## What is tigrbl_model?
11
+
12
+ `tigrbl_model` is Tigrbl's Pydantic-independent model engine. It owns field metadata, validation, serialization, dynamic model construction, forward-reference rebuilding, and JSON Schema generation.
13
+
14
+ ## Why use tigrbl_model?
15
+
16
+ Use it when code needs the Pydantic v2 surface validated by the package tests for Tigrbl without taking a runtime dependency on Pydantic.
17
+
18
+ ## When should I install tigrbl_model?
19
+
20
+ Install it when building Tigrbl extensions, reusable schema models, or applications that need the same model contract as Tigrbl.
21
+
22
+ ## Who is tigrbl_model for?
23
+
24
+ Tigrbl maintainers, extension authors, and application developers who need validated models and portable serialization.
25
+
26
+ ## Where does tigrbl_model fit?
27
+
28
+ It is a foundational package under `pkgs/02_model`, below Tigrbl packages that consume models, validation, and JSON Schema.
29
+
30
+ ## How does tigrbl_model work?
31
+
32
+ Type annotations and `Field` metadata are compiled into an internal field graph. The graph drives validation, dumping, JSON Schema, and the JSON, YAML, and TOML mixins.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ uv add tigrbl-model
38
+ ```
39
+
40
+ ```bash
41
+ pip install tigrbl-model
42
+ ```
43
+
44
+ ## Surface Coverage
45
+
46
+ | Surface | Value |
47
+ |---|---|
48
+ | PyPI package | `tigrbl-model` |
49
+ | Repository path | `pkgs/02_model/tigrbl_model` |
50
+ | Python import root | `tigrbl_model` |
51
+ | Console scripts | none |
52
+ | Public mixins | `JsonMixin`, `YamlMixin`, `TomlMixin`, `SerdeMixin` |
53
+ | Runtime Pydantic dependency | none |
54
+ | Legal files | `LICENSE`, `NOTICE` |
55
+ | Supported Python | `3.10 | 3.11 | 3.12 | 3.13 | 3.14` |
56
+
57
+ ## What It Owns
58
+
59
+ - Runtime model construction and field metadata.
60
+ - Annotation-driven validation and structured errors.
61
+ - Dictionary and JSON serialization.
62
+ - JSON Schema generation.
63
+ - Independently composable JSON, YAML, and TOML mixins.
64
+ - `SerdeMixin`, which aggregates all three format mixins.
65
+
66
+ ## Public API and Import Surface
67
+
68
+ The primary imports are `Model`/`BaseModel`, `RootModel`, `Field`, `ConfigDict`, `ValidationError`, `create_model`, `field_validator`, `model_validator`, and the four serialization mixins. See `tigrbl_model.__all__` for the complete supported surface.
69
+
70
+ ## Usage Examples
71
+
72
+ ```python
73
+ from tigrbl_model import Field, Model
74
+
75
+ class Widget(Model):
76
+ name: str
77
+ quantity: int = Field(default=0, ge=0)
78
+
79
+ widget = Widget.model_validate_json('{"name":"bolt","quantity":2}')
80
+ assert widget.model_dump() == {"name": "bolt", "quantity": 2}
81
+ assert Widget.model_validate_yaml(widget.model_dump_yaml()) == widget
82
+ assert Widget.model_validate_toml(widget.model_dump_toml()) == widget
83
+ ```
84
+
85
+ Plain dataclasses can inherit `SerdeMixin` when validation and schema-model behavior are unnecessary.
86
+
87
+ ## How To Choose This Package
88
+
89
+ Choose `tigrbl_model` for the focused model and serialization boundary. Choose the `tigrbl` facade when building a complete API application. Keep Pydantic installed only when independently comparing behavior during development.
90
+
91
+ ## Related Packages
92
+
93
+ - `tigrbl`
94
+ - `tigrbl-core`
95
+ - `tigrbl-base`
96
+ - `tigrbl-runtime`
97
+
98
+ ## Documentation Links
99
+
100
+ - [Supported parity boundary](PARITY.md)
101
+ - [Workspace documentation](https://github.com/tigrbl/tigrbl/blob/master/docs/README.md)
102
+ - [Package catalog](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_CATALOG.md)
103
+ - [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json)
104
+
105
+ ## Support
106
+
107
+ - Community: [Discord](https://discord.gg/K4YTAPapjR)
108
+ - Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues)
109
+ - Repository: [tigrbl/tigrbl](https://github.com/tigrbl/tigrbl)
110
+
111
+ ## Certification Status
112
+
113
+ The package's automated suite compares the supported contract with Pydantic v2 and scans first-party Tigrbl runtime usage. `PARITY.md` records intentional boundaries; Pydantic is a development-only parity oracle and is not a runtime dependency.
114
+
115
+ ## License
116
+
117
+ Licensed under the Apache License, Version 2.0. See `LICENSE` and `NOTICE`.
118
+
119
+ ## Workspace documentation
120
+
121
+ This README is the package-local distribution entry point. See [workspace documentation](../../../docs/README.md), [current conformance target](../../../docs/conformance/CURRENT_TARGET.md), [package catalog](../../../docs/developer/PACKAGE_CATALOG.md), [package layout](../../../docs/developer/PACKAGE_LAYOUT.md), and [SSOT registry](../../../.ssot/registry.json).
@@ -0,0 +1,45 @@
1
+ [project]
2
+ name = "tigrbl_model"
3
+ version = "0.4.5"
4
+ description = "Pydantic-independent validation, serialization, and JSON Schema models for Tigrbl."
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ requires-python = ">=3.10,<3.15"
8
+ authors = [{ name = "Jacob Stewart", email = "jacob@swarmauri.com" }]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "License :: OSI Approved :: Apache Software License",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3 :: Only",
14
+ "Programming Language :: Python :: 3.10",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ ]
20
+ dependencies = [
21
+ "pyyaml>=6.0",
22
+ "tomli-w>=1.0",
23
+ "tomli>=2.0.1; python_version < '3.11'",
24
+ "typing-extensions>=4.0; python_version < '3.11'",
25
+ ]
26
+
27
+ [project.urls]
28
+ Organization = "https://github.com/tigrbl"
29
+ Repository = "https://github.com/tigrbl/tigrbl/tree/master/pkgs/02_model/tigrbl_model"
30
+ Issues = "https://github.com/tigrbl/tigrbl/issues"
31
+
32
+ [build-system]
33
+ requires = ["poetry-core>=1.0.0"]
34
+ build-backend = "poetry.core.masonry.api"
35
+
36
+ [tool.poetry]
37
+ packages = [{ include = "tigrbl_model" }]
38
+ include = ["tigrbl_model/py.typed"]
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "pydantic>=2.10,<3",
43
+ "pytest>=8.0",
44
+ "ruff>=0.9.9",
45
+ ]
@@ -0,0 +1,35 @@
1
+ """Pydantic-independent models for Tigrbl."""
2
+
3
+ from .config import ConfigDict, ModelConfig
4
+ from .errors import ErrorDetails, ModelUserError, ValidationError
5
+ from .factory import create_model
6
+ from .fields import AliasChoices, Field, FieldInfo, PydanticUndefined, Undefined
7
+ from .mixins import JsonMixin, SerdeMixin, TomlMixin, YamlMixin
8
+ from .model import BaseModel, Model
9
+ from .root import RootModel
10
+ from .validators import field_validator, model_validator
11
+
12
+ __version__ = "0.4.5.dev4"
13
+
14
+ __all__ = [
15
+ "AliasChoices",
16
+ "BaseModel",
17
+ "ConfigDict",
18
+ "ErrorDetails",
19
+ "Field",
20
+ "FieldInfo",
21
+ "JsonMixin",
22
+ "Model",
23
+ "ModelConfig",
24
+ "ModelUserError",
25
+ "PydanticUndefined",
26
+ "RootModel",
27
+ "SerdeMixin",
28
+ "TomlMixin",
29
+ "Undefined",
30
+ "ValidationError",
31
+ "YamlMixin",
32
+ "create_model",
33
+ "field_validator",
34
+ "model_validator",
35
+ ]
@@ -0,0 +1,36 @@
1
+ """Model configuration primitives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class ConfigDict(dict[str, Any]):
9
+ """Dictionary configuration compatible with Tigrbl's Pydantic usage."""
10
+
11
+
12
+ ModelConfig = ConfigDict
13
+
14
+
15
+ DEFAULT_CONFIG = ConfigDict(
16
+ extra="ignore",
17
+ populate_by_name=False,
18
+ from_attributes=False,
19
+ arbitrary_types_allowed=False,
20
+ validate_default=False,
21
+ use_enum_values=False,
22
+ json_schema_extra=None,
23
+ )
24
+
25
+
26
+ def merge_config(*configs: dict[str, Any] | None) -> ConfigDict:
27
+ """Merge model configuration from oldest base to newest declaration."""
28
+
29
+ merged = ConfigDict(DEFAULT_CONFIG)
30
+ for config in configs:
31
+ if config:
32
+ merged.update(config)
33
+ return merged
34
+
35
+
36
+ __all__ = ["ConfigDict", "ModelConfig"]
@@ -0,0 +1,98 @@
1
+ """Structured model validation errors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import Any, Iterable
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class ErrorDetails:
12
+ """One validation failure with a stable location and machine-readable type."""
13
+
14
+ type: str
15
+ loc: tuple[str | int, ...]
16
+ msg: str
17
+ input: Any = None
18
+ ctx: dict[str, Any] | None = None
19
+
20
+ def as_dict(self, *, include_url: bool = True) -> dict[str, Any]:
21
+ result: dict[str, Any] = {
22
+ "type": self.type,
23
+ "loc": self.loc,
24
+ "msg": self.msg,
25
+ "input": self.input,
26
+ }
27
+ if self.ctx:
28
+ result["ctx"] = dict(self.ctx)
29
+ if include_url:
30
+ result["url"] = f"https://errors.tigrbl.dev/model/{self.type}"
31
+ return result
32
+
33
+
34
+ class ValidationError(ValueError):
35
+ """Aggregate validation failures for a model operation."""
36
+
37
+ def __init__(self, title: str, errors: Iterable[ErrorDetails | dict[str, Any]]):
38
+ self.title = title
39
+ self._errors = tuple(
40
+ error
41
+ if isinstance(error, ErrorDetails)
42
+ else ErrorDetails(
43
+ type=str(error.get("type", "value_error")),
44
+ loc=tuple(error.get("loc", ())),
45
+ msg=str(error.get("msg", "Invalid value")),
46
+ input=error.get("input"),
47
+ ctx=error.get("ctx"),
48
+ )
49
+ for error in errors
50
+ )
51
+ super().__init__(str(self))
52
+
53
+ @classmethod
54
+ def from_exception_data(
55
+ cls, title: str, line_errors: Iterable[dict[str, Any]]
56
+ ) -> "ValidationError":
57
+ return cls(title, line_errors)
58
+
59
+ def error_count(self) -> int:
60
+ return len(self._errors)
61
+
62
+ def errors(
63
+ self,
64
+ *,
65
+ include_url: bool = True,
66
+ include_context: bool = True,
67
+ include_input: bool = True,
68
+ ) -> list[dict[str, Any]]:
69
+ values = [error.as_dict(include_url=include_url) for error in self._errors]
70
+ for value in values:
71
+ if not include_context:
72
+ value.pop("ctx", None)
73
+ if not include_input:
74
+ value.pop("input", None)
75
+ return values
76
+
77
+ def json(self, *, indent: int | None = None, **_: Any) -> str:
78
+ return json.dumps(self.errors(), indent=indent, default=str)
79
+
80
+ def __str__(self) -> str:
81
+ count = len(self._errors)
82
+ heading = f"{count} validation error{'s' if count != 1 else ''} for {self.title}"
83
+ lines = [heading]
84
+ for error in self._errors:
85
+ location = ".".join(str(item) for item in error.loc) or "__root__"
86
+ lines.extend((location, f" {error.msg} [type={error.type}]"))
87
+ return "\n".join(lines)
88
+
89
+
90
+ class ModelUserError(TypeError):
91
+ """Raised when a declaration uses a removed or unsupported model option."""
92
+
93
+ def __init__(self, message: str, *, code: str = "model-user-error") -> None:
94
+ self.code = code
95
+ super().__init__(message)
96
+
97
+
98
+ __all__ = ["ErrorDetails", "ModelUserError", "ValidationError"]
@@ -0,0 +1,53 @@
1
+ """Dynamic model construction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Any
7
+
8
+ from .config import ConfigDict
9
+ from .fields import FieldInfo, PydanticUndefined
10
+ from .model import Model
11
+
12
+
13
+ def create_model(
14
+ model_name: str,
15
+ *,
16
+ __config__: ConfigDict | dict[str, Any] | None = None,
17
+ __base__: type[Model] | tuple[type[Model], ...] | None = None,
18
+ __module__: str | None = None,
19
+ __validators__: dict[str, Any] | None = None,
20
+ __doc__: str | None = None,
21
+ **field_definitions: Any,
22
+ ) -> type[Model]:
23
+ """Create a model class from `(annotation, default)` field definitions."""
24
+
25
+ bases = __base__ or Model
26
+ if not isinstance(bases, tuple):
27
+ bases = (bases,)
28
+ annotations: dict[str, Any] = {}
29
+ namespace: dict[str, Any] = {
30
+ "__annotations__": annotations,
31
+ "__module__": __module__ or sys._getframe(1).f_globals.get("__name__", "__main__"),
32
+ }
33
+ if __doc__ is not None:
34
+ namespace["__doc__"] = __doc__
35
+ if __config__ is not None:
36
+ namespace["model_config"] = ConfigDict(__config__)
37
+ namespace.update(__validators__ or {})
38
+ for name, definition in field_definitions.items():
39
+ if isinstance(definition, tuple) and len(definition) == 2:
40
+ annotation, default = definition
41
+ else:
42
+ annotation, default = definition, PydanticUndefined
43
+ annotations[name] = annotation
44
+ if default is Ellipsis:
45
+ default = PydanticUndefined
46
+ if isinstance(default, FieldInfo):
47
+ namespace[name] = default
48
+ elif default is not PydanticUndefined:
49
+ namespace[name] = default
50
+ return type(model_name, bases, namespace)
51
+
52
+
53
+ __all__ = ["create_model"]