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.
- tigrbl_model-0.4.5/LICENSE +17 -0
- tigrbl_model-0.4.5/NOTICE +4 -0
- tigrbl_model-0.4.5/PKG-INFO +166 -0
- tigrbl_model-0.4.5/README.md +121 -0
- tigrbl_model-0.4.5/pyproject.toml +45 -0
- tigrbl_model-0.4.5/tigrbl_model/__init__.py +35 -0
- tigrbl_model-0.4.5/tigrbl_model/config.py +36 -0
- tigrbl_model-0.4.5/tigrbl_model/errors.py +98 -0
- tigrbl_model-0.4.5/tigrbl_model/factory.py +53 -0
- tigrbl_model-0.4.5/tigrbl_model/fields.py +172 -0
- tigrbl_model-0.4.5/tigrbl_model/mixins/__init__.py +8 -0
- tigrbl_model-0.4.5/tigrbl_model/mixins/json.py +75 -0
- tigrbl_model-0.4.5/tigrbl_model/mixins/serde.py +142 -0
- tigrbl_model-0.4.5/tigrbl_model/mixins/toml.py +48 -0
- tigrbl_model-0.4.5/tigrbl_model/mixins/yaml.py +49 -0
- tigrbl_model-0.4.5/tigrbl_model/model.py +319 -0
- tigrbl_model-0.4.5/tigrbl_model/py.typed +1 -0
- tigrbl_model-0.4.5/tigrbl_model/root.py +54 -0
- tigrbl_model-0.4.5/tigrbl_model/schema.py +207 -0
- tigrbl_model-0.4.5/tigrbl_model/serialization.py +161 -0
- tigrbl_model-0.4.5/tigrbl_model/validation.py +457 -0
- tigrbl_model-0.4.5/tigrbl_model/validators.py +47 -0
|
@@ -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,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"]
|