tinydantic 0.1.19__tar.gz → 0.3.0__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.
Files changed (43) hide show
  1. tinydantic-0.3.0/PKG-INFO +96 -0
  2. tinydantic-0.3.0/README.md +60 -0
  3. tinydantic-0.3.0/pyproject.toml +236 -0
  4. tinydantic-0.3.0/src/tinydantic/__init__.py +38 -0
  5. tinydantic-0.3.0/src/tinydantic/_config.py +137 -0
  6. tinydantic-0.3.0/src/tinydantic/_errors.py +76 -0
  7. tinydantic-0.3.0/src/tinydantic/_model.py +654 -0
  8. {tinydantic-0.1.19/tests → tinydantic-0.3.0/src/tinydantic/tinydb}/__init__.py +1 -1
  9. tinydantic-0.3.0/src/tinydantic/tinydb/middlewares.py +89 -0
  10. tinydantic-0.3.0/src/tinydantic/tinydb/operations.py +31 -0
  11. tinydantic-0.3.0/src/tinydantic/tinydb/storages.py +120 -0
  12. tinydantic-0.1.19/.editorconfig +0 -31
  13. tinydantic-0.1.19/.github/workflows/ci.yaml +0 -80
  14. tinydantic-0.1.19/.github/workflows/docs-dev.yaml +0 -60
  15. tinydantic-0.1.19/.github/workflows/docs-release.yaml +0 -66
  16. tinydantic-0.1.19/.github/workflows/publish-python-package.yaml +0 -126
  17. tinydantic-0.1.19/.gitignore +0 -171
  18. tinydantic-0.1.19/.linkcheckerrc +0 -8
  19. tinydantic-0.1.19/.markdownlint.yaml +0 -13
  20. tinydantic-0.1.19/.pre-commit-config.yaml +0 -84
  21. tinydantic-0.1.19/.prettierrc.yaml +0 -22
  22. tinydantic-0.1.19/.vscode/extensions.json +0 -18
  23. tinydantic-0.1.19/.yamllint.yaml +0 -33
  24. tinydantic-0.1.19/LICENSE.md +0 -25
  25. tinydantic-0.1.19/PKG-INFO +0 -82
  26. tinydantic-0.1.19/README.md +0 -23
  27. tinydantic-0.1.19/REUSE.toml +0 -25
  28. tinydantic-0.1.19/docs/.pages +0 -9
  29. tinydantic-0.1.19/docs/css/code_select.css +0 -10
  30. tinydantic-0.1.19/docs/index.md +0 -9
  31. tinydantic-0.1.19/docs/installation.md +0 -7
  32. tinydantic-0.1.19/docs/reference/.pages +0 -52
  33. tinydantic-0.1.19/docs/reference/index.md +0 -5
  34. tinydantic-0.1.19/mkdocs.yaml +0 -126
  35. tinydantic-0.1.19/pyproject.toml +0 -290
  36. tinydantic-0.1.19/scripts/gen_api_docs.py +0 -161
  37. tinydantic-0.1.19/src/tinydantic/__init__.py +0 -17
  38. tinydantic-0.1.19/taplo.toml +0 -38
  39. tinydantic-0.1.19/tests/test_version.py +0 -30
  40. {tinydantic-0.1.19 → tinydantic-0.3.0}/LICENSES/Apache-2.0.txt +0 -0
  41. {tinydantic-0.1.19 → tinydantic-0.3.0}/LICENSES/CC-BY-4.0.txt +0 -0
  42. {tinydantic-0.1.19 → tinydantic-0.3.0}/LICENSES/MIT.txt +0 -0
  43. {tinydantic-0.1.19 → tinydantic-0.3.0}/src/tinydantic/py.typed +0 -0
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: tinydantic
3
+ Version: 0.3.0
4
+ Summary: A Pydantic-powered ODM (object-document mapper) for TinyDB
5
+ Keywords: odm,pydantic,tinydb
6
+ Author: Chris Wilson
7
+ Author-email: Chris Wilson <christopher.david.wilson@gmail.com>
8
+ License-Expression: Apache-2.0 OR MIT
9
+ License-File: LICENSES/Apache-2.0.txt
10
+ License-File: LICENSES/CC-BY-4.0.txt
11
+ License-File: LICENSES/MIT.txt
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: Pydantic :: 2
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: Implementation :: CPython
23
+ Classifier: Programming Language :: Python
24
+ Classifier: Topic :: Database
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Classifier: Typing :: Typed
27
+ Requires-Dist: pyyaml>=6.0
28
+ Requires-Dist: pydantic[email]>=2.11
29
+ Requires-Dist: tinydb>=4.8
30
+ Requires-Python: >=3.10
31
+ Project-URL: Changelog, https://github.com/tinydantic/tinydantic/blob/main/CHANGELOG.md
32
+ Project-URL: Documentation, https://tinydantic.dev
33
+ Project-URL: Issues, https://github.com/tinydantic/tinydantic/issues
34
+ Project-URL: Source, https://github.com/tinydantic/tinydantic
35
+ Description-Content-Type: text/markdown
36
+
37
+ # tinydantic
38
+
39
+ <!-- overview-start -->
40
+
41
+ [![PyPI - Version](https://img.shields.io/pypi/v/tinydantic.svg)](https://pypi.org/project/tinydantic) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/tinydantic.svg)](https://pypi.org/project/tinydantic) [![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev)
42
+
43
+ `tinydantic` is a simple Python object-document mapper (ODM) for the [TinyDB](https://tinydb.readthedocs.io/en/latest/) document database. It uses [Pydantic](https://docs.pydantic.dev/latest/)—the most widely used data validation library—for document model definition and validation.
44
+
45
+ <!-- prettier-ignore-start -->
46
+
47
+ > [!NOTE]
48
+ > `tinydantic` is an experimental project under active development and should not be considered stable for use in a production environment.
49
+ >
50
+ > Releases follow the [SemVer versioning spec](https://semver.org):
51
+ >
52
+ > > Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
53
+ >
54
+ > Minor releases may include breaking changes until v1.0 — check the [changelog](https://github.com/tinydantic/tinydantic/blob/main/CHANGELOG.md) when upgrading.
55
+ >
56
+ > **AI Disclaimer**
57
+ >
58
+ > I started thinking about the idea for this project after discovering TinyDB a couple years ago. However, I never had the time (or a real use case) to finish the implementation. With the release of [Claude Fable 5](https://www.anthropic.com/claude/fable), it seemed like a good opportunity to revisit this idea and try to get an initial prototype of this working with the help of AI. I'm currently experimenting with the implementation generated by Claude to understand what works well and what needs further improvement.
59
+ >
60
+ > I am *not* a NoSQL database expert and it's very possible that there are better implementations and/or interfaces than what Claude has produced in this initial prototype. If you find things that are broken, awkward, missing, or don't follow best practices, please feel free to open an issue or a PR to suggest changes. Feedback is welcome!
61
+
62
+ <!-- prettier-ignore-end -->
63
+
64
+ <!-- overview-end -->
65
+
66
+ Full documentation — including a [quickstart](https://tinydantic.dev/latest/usage/quickstart/) that walks through a complete create-read-update-delete example — is available at [tinydantic.dev](https://tinydantic.dev).
67
+
68
+ ## Table of Contents
69
+
70
+ - [Installation](#installation)
71
+ - [Introduction](#introduction)
72
+ - [License](#license)
73
+
74
+ ## Introduction
75
+
76
+ <!-- introduction-start -->
77
+
78
+ `tinydantic` is a wrapper library around [TinyDB](https://tinydb.readthedocs.io/en/latest/) that enables using [Pydantic](https://docs.pydantic.dev/) models to manage documents stored in a TinyDB database. By specifying Python type annotations for each field in a document model, data validation is handled automatically by Pydantic when instantiating a model from the database.
79
+
80
+ <!-- introduction-end -->
81
+
82
+ ## Installation
83
+
84
+ <!-- installation-start -->
85
+
86
+ `tinydantic` is [available on PyPI](https://pypi.org/project/tinydantic/) and can be installed with [pip](https://github.com/pypa/pip). Easy peasy lemon squeezy 🍋
87
+
88
+ ```sh
89
+ pip install tinydantic
90
+ ```
91
+
92
+ <!-- installation-end -->
93
+
94
+ ## License
95
+
96
+ See the [LICENSE](https://github.com/tinydantic/tinydantic/blob/main/LICENSE.md) file for copyright & license information.
@@ -0,0 +1,60 @@
1
+ # tinydantic
2
+
3
+ <!-- overview-start -->
4
+
5
+ [![PyPI - Version](https://img.shields.io/pypi/v/tinydantic.svg)](https://pypi.org/project/tinydantic) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/tinydantic.svg)](https://pypi.org/project/tinydantic) [![Pydantic v2](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/pydantic/pydantic/main/docs/badge/v2.json)](https://pydantic.dev)
6
+
7
+ `tinydantic` is a simple Python object-document mapper (ODM) for the [TinyDB](https://tinydb.readthedocs.io/en/latest/) document database. It uses [Pydantic](https://docs.pydantic.dev/latest/)—the most widely used data validation library—for document model definition and validation.
8
+
9
+ <!-- prettier-ignore-start -->
10
+
11
+ > [!NOTE]
12
+ > `tinydantic` is an experimental project under active development and should not be considered stable for use in a production environment.
13
+ >
14
+ > Releases follow the [SemVer versioning spec](https://semver.org):
15
+ >
16
+ > > Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
17
+ >
18
+ > Minor releases may include breaking changes until v1.0 — check the [changelog](https://github.com/tinydantic/tinydantic/blob/main/CHANGELOG.md) when upgrading.
19
+ >
20
+ > **AI Disclaimer**
21
+ >
22
+ > I started thinking about the idea for this project after discovering TinyDB a couple years ago. However, I never had the time (or a real use case) to finish the implementation. With the release of [Claude Fable 5](https://www.anthropic.com/claude/fable), it seemed like a good opportunity to revisit this idea and try to get an initial prototype of this working with the help of AI. I'm currently experimenting with the implementation generated by Claude to understand what works well and what needs further improvement.
23
+ >
24
+ > I am *not* a NoSQL database expert and it's very possible that there are better implementations and/or interfaces than what Claude has produced in this initial prototype. If you find things that are broken, awkward, missing, or don't follow best practices, please feel free to open an issue or a PR to suggest changes. Feedback is welcome!
25
+
26
+ <!-- prettier-ignore-end -->
27
+
28
+ <!-- overview-end -->
29
+
30
+ Full documentation — including a [quickstart](https://tinydantic.dev/latest/usage/quickstart/) that walks through a complete create-read-update-delete example — is available at [tinydantic.dev](https://tinydantic.dev).
31
+
32
+ ## Table of Contents
33
+
34
+ - [Installation](#installation)
35
+ - [Introduction](#introduction)
36
+ - [License](#license)
37
+
38
+ ## Introduction
39
+
40
+ <!-- introduction-start -->
41
+
42
+ `tinydantic` is a wrapper library around [TinyDB](https://tinydb.readthedocs.io/en/latest/) that enables using [Pydantic](https://docs.pydantic.dev/) models to manage documents stored in a TinyDB database. By specifying Python type annotations for each field in a document model, data validation is handled automatically by Pydantic when instantiating a model from the database.
43
+
44
+ <!-- introduction-end -->
45
+
46
+ ## Installation
47
+
48
+ <!-- installation-start -->
49
+
50
+ `tinydantic` is [available on PyPI](https://pypi.org/project/tinydantic/) and can be installed with [pip](https://github.com/pypa/pip). Easy peasy lemon squeezy 🍋
51
+
52
+ ```sh
53
+ pip install tinydantic
54
+ ```
55
+
56
+ <!-- installation-end -->
57
+
58
+ ## License
59
+
60
+ See the [LICENSE](https://github.com/tinydantic/tinydantic/blob/main/LICENSE.md) file for copyright & license information.
@@ -0,0 +1,236 @@
1
+ # SPDX-FileCopyrightText: Chris Wilson <christopher.david.wilson@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0 OR MIT
4
+
5
+ #:schema https://json.schemastore.org/pyproject.json
6
+
7
+ [build-system]
8
+ build-backend = "uv_build"
9
+ requires = [
10
+ "uv_build>=0.11,<0.12",
11
+ ]
12
+
13
+ [project]
14
+ authors = [
15
+ { name = "Chris Wilson", email = "christopher.david.wilson@gmail.com" },
16
+ ]
17
+ # https://pypi.org/classifiers/
18
+ classifiers = [
19
+ "Development Status :: 3 - Alpha",
20
+ "Framework :: Pydantic :: 2",
21
+ "Intended Audience :: Developers",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Programming Language :: Python :: Implementation :: CPython",
30
+ "Programming Language :: Python",
31
+ "Topic :: Database",
32
+ "Topic :: Software Development :: Libraries",
33
+ "Typing :: Typed",
34
+ ]
35
+ dependencies = [
36
+ "PyYAML>=6.0",
37
+ "pydantic[email]>=2.11",
38
+ "tinydb>=4.8",
39
+ ]
40
+ description = "A Pydantic-powered ODM (object-document mapper) for TinyDB"
41
+ keywords = [
42
+ "odm",
43
+ "pydantic",
44
+ "tinydb",
45
+ ]
46
+ license = "Apache-2.0 OR MIT"
47
+ license-files = [
48
+ "LICENSES/*",
49
+ ]
50
+ name = "tinydantic"
51
+ readme = "README.md"
52
+ requires-python = ">=3.10"
53
+ version = "0.3.0"
54
+
55
+ [project.urls]
56
+ Changelog = "https://github.com/tinydantic/tinydantic/blob/main/CHANGELOG.md"
57
+ Documentation = "https://tinydantic.dev"
58
+ Issues = "https://github.com/tinydantic/tinydantic/issues"
59
+ Source = "https://github.com/tinydantic/tinydantic"
60
+
61
+ [dependency-groups]
62
+ dev = [
63
+ "check-jsonschema",
64
+ "commitizen",
65
+ "editorconfig-checker",
66
+ "interrogate",
67
+ "jsonschema[format]",
68
+ "poethepoet",
69
+ "pre-commit",
70
+ "pre-commit-ci-config",
71
+ "pyclean",
72
+ "reuse>=5.0.0",
73
+ { include-group = "docs" },
74
+ { include-group = "lint" },
75
+ { include-group = "test" },
76
+ { include-group = "types" },
77
+ ]
78
+ docs = [
79
+ # black is used by separate_signature option in mkdocstrings-python
80
+ "black",
81
+ "fastapi",
82
+ "griffe",
83
+ "httpx2",
84
+ "linkchecker",
85
+ "markdown-callouts",
86
+ "markdown-gfm-admonition",
87
+ "mdx_truly_sane_lists",
88
+ "mike",
89
+ "mkdocs-awesome-pages-plugin",
90
+ "mkdocs-gen-files",
91
+ "mkdocs-github-admonitions-plugin",
92
+ "mkdocs-include-markdown-plugin",
93
+ "mkdocs-materialx",
94
+ "mkdocstrings",
95
+ "mkdocstrings-python",
96
+ "poethepoet",
97
+ # properdocs is the docs build engine. mike >=2.2.0 auto-detects it and
98
+ # runs properdocs for versioned deploys too. The mkdocs package remains
99
+ # installed only as a transitive dependency (mkdocs-materialx pins it for
100
+ # import compatibility).
101
+ "properdocs",
102
+ ]
103
+ lint = [
104
+ "poethepoet",
105
+ "ruff>=0.14",
106
+ ]
107
+ test = [
108
+ "fastapi",
109
+ "httpx2",
110
+ "packaging",
111
+ "poethepoet",
112
+ "pytest-cov",
113
+ "pytest-randomly",
114
+ "pytest-rerunfailures",
115
+ "pytest-xdist",
116
+ "pytest>=8",
117
+ ]
118
+ types = [
119
+ "mypy>=1.14",
120
+ "packaging",
121
+ "types-PyYAML",
122
+ ]
123
+
124
+ [tool.coverage.run]
125
+ branch = true
126
+ parallel = true
127
+ source_pkgs = [
128
+ "tests",
129
+ "tinydantic",
130
+ ]
131
+
132
+ [tool.coverage.paths]
133
+ tests = [
134
+ "*/tinydantic/tests",
135
+ "tests",
136
+ ]
137
+ tinydantic = [
138
+ "*/tinydantic/src/tinydantic",
139
+ "src/tinydantic",
140
+ ]
141
+
142
+ [tool.coverage.report]
143
+ exclude_lines = [
144
+ "if TYPE_CHECKING:",
145
+ "if __name__ == .__main__.:",
146
+ "no cov",
147
+ ]
148
+
149
+ [tool.commitizen]
150
+ annotated_tag = true
151
+ major_version_zero = true
152
+ name = "cz_conventional_commits"
153
+ tag_format = "v$version"
154
+ version_provider = "pep621"
155
+ version_scheme = "semver2"
156
+
157
+ [tool.poe.tasks]
158
+ clean = "pyclean . --debris --verbose"
159
+ fmt = [
160
+ "_ruff-fix",
161
+ "_ruff-format",
162
+ ]
163
+ lint = [
164
+ "_ruff-check",
165
+ "_ruff-format-check",
166
+ ]
167
+ pre-commit = "pre-commit run --all-files --verbose"
168
+ sbom = "reuse spdx -o reuse.spdx"
169
+ sbom-check = "reuse lint"
170
+ spell-check = "npx cspell --gitignore --dot ."
171
+ test = "pytest"
172
+ test-cov = "pytest --cov --cov-branch"
173
+ types = "mypy --install-types --non-interactive src/tinydantic tests"
174
+ update-project-words = { shell = "npx cspell --gitignore --dot --words-only --unique . | sort --ignore-case >> project-words.txt" }
175
+
176
+ _ruff-check = "ruff check ."
177
+ _ruff-fix = "ruff check --fix ."
178
+ _ruff-format = "ruff format ."
179
+ _ruff-format-check = "ruff format --check ."
180
+
181
+ check = [
182
+ "lint",
183
+ "sbom-check",
184
+ "spell-check",
185
+ "types",
186
+ ]
187
+
188
+ [tool.poe.tasks.docs-build]
189
+ cmd = "properdocs build --config-file mkdocs.yaml --clean --strict"
190
+ env = { PYTHONUNBUFFERED = "1", SOURCE_DATE_EPOCH = "1580601600" }
191
+
192
+ [tool.poe.tasks.docs-build-check]
193
+ sequence = [
194
+ { ref = "docs-build --no-directory-urls" },
195
+ { ref = "docs-check" },
196
+ ]
197
+
198
+ [tool.poe.tasks.docs-check]
199
+ cmd = "linkchecker --config .linkcheckerrc site"
200
+
201
+ [tool.poe.tasks.docs-ci-publish]
202
+ cmd = "mike deploy --config-file mkdocs.yaml --update-aliases --push"
203
+ env = { PYTHONUNBUFFERED = "1", SOURCE_DATE_EPOCH = "1580601600" }
204
+
205
+ [tool.poe.tasks.docs-serve]
206
+ cmd = "properdocs serve --config-file mkdocs.yaml --dev-addr localhost:8000"
207
+ env = { PYTHONUNBUFFERED = "1" }
208
+
209
+ [tool.interrogate]
210
+ fail-under = 100
211
+ ignore-overloaded-functions = true
212
+ ignore-private = true
213
+ ignore-semiprivate = true
214
+ omit-covered-files = true
215
+ style = "google"
216
+ verbose = 2
217
+
218
+ [tool.mypy]
219
+ python_version = "3.10"
220
+
221
+ [tool.pytest.ini_options]
222
+ addopts = "--doctest-modules --doctest-glob '*.md' --ignore=docs/superpowers"
223
+ doctest_optionflags = "NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL"
224
+ filterwarnings = [
225
+ # The queries.md "shadowing" section deliberately defines a field named
226
+ # `search` to demonstrate the pitfall, so pydantic's warning is expected.
227
+ 'ignore:Field name "search" in "Command" shadows an attribute in parent "TinydanticModel":UserWarning',
228
+ ]
229
+ minversion = "6.0"
230
+ testpaths = [
231
+ "CONTRIBUTING.md",
232
+ "README.md",
233
+ "docs",
234
+ "src",
235
+ "tests",
236
+ ]
@@ -0,0 +1,38 @@
1
+ # SPDX-FileCopyrightText: Chris Wilson <christopher.david.wilson@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0 OR MIT
4
+
5
+ """`tinydantic` is a simple Python object-document mapper (ODM) for the
6
+ [TinyDB](https://tinydb.readthedocs.io/en/latest/) document database.
7
+
8
+ Attributes:
9
+ __version__: The `tinydantic` package version.
10
+ """ # noqa: D205
11
+
12
+ from importlib import metadata
13
+
14
+ from tinydantic._config import TinydanticConfig
15
+ from tinydantic._errors import (
16
+ AmbiguousConfigError,
17
+ DatabaseNotBoundError,
18
+ DocumentIDRequiredError,
19
+ DocumentNotFoundError,
20
+ TinydanticError,
21
+ TinydanticUserError,
22
+ )
23
+ from tinydantic._model import TinydanticModel, q
24
+
25
+ __version__: str = metadata.version("tinydantic")
26
+
27
+ __all__ = [
28
+ "AmbiguousConfigError",
29
+ "DatabaseNotBoundError",
30
+ "DocumentIDRequiredError",
31
+ "DocumentNotFoundError",
32
+ "TinydanticConfig",
33
+ "TinydanticError",
34
+ "TinydanticModel",
35
+ "TinydanticUserError",
36
+ "__version__",
37
+ "q",
38
+ ]
@@ -0,0 +1,137 @@
1
+ # SPDX-FileCopyrightText: Chris Wilson <christopher.david.wilson@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0 OR MIT
4
+
5
+ """Configuration machinery for tinydantic models.
6
+
7
+ Design note — why tinydantic config does NOT live in ``model_config``:
8
+
9
+ Pydantic merges ``model_config`` across multiple base classes in
10
+ "last wins" order — the *opposite* of Python's MRO (see
11
+ https://github.com/pydantic/pydantic/issues/9992). Community fixes
12
+ were rejected as breaking changes, and the behavior may change in
13
+ pydantic v3. Storing tinydantic's keys there would (a) inherit those
14
+ surprising semantics, (b) risk future key collisions with
15
+ ``ConfigDict``, and (c) couple binding resolution to behavior pydantic
16
+ itself may flip.
17
+
18
+ Instead, every model class stores only the config keys *explicitly set
19
+ on it* in its own ``__tinydantic_config__`` class attribute, and
20
+ lookup walks ``cls.__mro__`` for the first class that provides the
21
+ key — standard Python attribute semantics. For the one scenario where
22
+ pydantic's ordering and Python's ordering could disagree (two
23
+ *unrelated* bases providing conflicting values), tinydantic refuses to
24
+ guess and raises
25
+ [AmbiguousConfigError][tinydantic.AmbiguousConfigError]
26
+ at class-definition time instead.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import TYPE_CHECKING, Any, TypedDict
32
+
33
+ from tinydantic._errors import AmbiguousConfigError
34
+
35
+ if TYPE_CHECKING:
36
+ from tinydb import TinyDB
37
+
38
+ # Name of the per-class attribute holding explicitly-set config.
39
+ CONFIG_ATTR = "__tinydantic_config__"
40
+
41
+ _CONFIG_KEYS = ("database", "table_name")
42
+
43
+
44
+ class TinydanticConfig(TypedDict, total=False):
45
+ """Configuration options for tinydantic models.
46
+
47
+ This is a plain [TypedDict][typing.TypedDict] — deliberately NOT a
48
+ ``pydantic.ConfigDict`` subclass; see the module docstring for the
49
+ design rationale. Values are provided as class keyword arguments:
50
+
51
+ ```python
52
+ class User(TinydanticModel, database=db, table_name="users"):
53
+ name: str
54
+ ```
55
+ """
56
+
57
+ database: TinyDB
58
+ """TinyDB database where documents of this model are stored."""
59
+
60
+ table_name: str | None
61
+ """Database table name for documents of this model.
62
+
63
+ When unset (or falsy), the table name is derived from the model
64
+ class name converted to snake_case — for example, a model class
65
+ named ``AdminUser`` uses the table ``admin_user``.
66
+ """
67
+
68
+
69
+ def get_config_value(
70
+ model_class: type,
71
+ key: str,
72
+ default: Any = None,
73
+ ) -> Any:
74
+ """Resolve a tinydantic config key for a model class.
75
+
76
+ Walks ``model_class.__mro__`` and returns the value from the first
77
+ class whose own ``__tinydantic_config__`` contains ``key`` —
78
+ standard Python attribute-lookup semantics, unlike pydantic's
79
+ ``model_config`` merge (see the module docstring).
80
+
81
+ Args:
82
+ model_class: The model class to resolve the key for.
83
+ key: The [TinydanticConfig][tinydantic.TinydanticConfig]
84
+ key to look up.
85
+ default: Value returned when no class in the MRO provides the
86
+ key.
87
+
88
+ Returns:
89
+ The resolved value, or ``default``.
90
+ """
91
+ for klass in model_class.__mro__:
92
+ config = klass.__dict__.get(CONFIG_ATTR)
93
+ if config is not None and key in config:
94
+ return config[key]
95
+ return default
96
+
97
+
98
+ def check_config_ambiguity(model_class: type) -> None:
99
+ """Raise if unrelated bases supply conflicting config values.
100
+
101
+ For each config key not set on ``model_class`` itself, this checks
102
+ whether two classes in the MRO that are *not* part of one
103
+ inheritance chain (neither is a subclass of the other) provide
104
+ different values. Ordinary single-chain overrides — a subclass
105
+ overriding its parent — are never flagged.
106
+
107
+ Args:
108
+ model_class: The freshly created model class to validate.
109
+
110
+ Raises:
111
+ AmbiguousConfigError: If a genuine multi-base conflict exists.
112
+ """
113
+ own = model_class.__dict__.get(CONFIG_ATTR) or {}
114
+ for key in _CONFIG_KEYS:
115
+ if key in own:
116
+ continue
117
+ providers = [
118
+ klass
119
+ for klass in model_class.__mro__[1:]
120
+ if key in (klass.__dict__.get(CONFIG_ATTR) or {})
121
+ ]
122
+ if not providers[1:]:
123
+ continue
124
+ first = providers[0]
125
+ first_value = first.__dict__[CONFIG_ATTR][key]
126
+ for other in providers[1:]:
127
+ if issubclass(first, other):
128
+ # Normal override along one inheritance chain: the
129
+ # earlier (more derived) class legitimately wins.
130
+ continue
131
+ if first_value != other.__dict__[CONFIG_ATTR][key]:
132
+ raise AmbiguousConfigError(
133
+ model_name=model_class.__name__,
134
+ key=key,
135
+ first=first.__name__,
136
+ second=other.__name__,
137
+ )
@@ -0,0 +1,76 @@
1
+ # SPDX-FileCopyrightText: Chris Wilson <christopher.david.wilson@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0 OR MIT
4
+
5
+ """Errors specific to tinydantic."""
6
+
7
+ from __future__ import annotations
8
+
9
+
10
+ class TinydanticError(Exception):
11
+ """Base class for `tinydantic` errors."""
12
+
13
+
14
+ class TinydanticUserError(TinydanticError):
15
+ """Base class for errors caused by incorrect use of tinydantic."""
16
+
17
+
18
+ class DatabaseNotBoundError(TinydanticUserError):
19
+ """No database is bound to the model class.
20
+
21
+ Raised when a table operation is attempted on a model that has no
22
+ ``database`` configured anywhere in its class hierarchy.
23
+ """
24
+
25
+ def __init__(self, model_name: str) -> None:
26
+ """Initialize with the name of the unbound model class."""
27
+ super().__init__(
28
+ f"No database is bound to {model_name!r}. Pass "
29
+ "database=<TinyDB instance> as a class keyword when "
30
+ f"defining the model, or call {model_name}.bind("
31
+ "database=...) before using it.",
32
+ )
33
+
34
+
35
+ class AmbiguousConfigError(TinydanticUserError):
36
+ """Conflicting tinydantic config inherited from unrelated bases.
37
+
38
+ Raised at class-definition time when two base classes that are not
39
+ part of one inheritance chain supply different values for the same
40
+ tinydantic config key and the new class does not set that key
41
+ itself. tinydantic refuses to guess which base should win — see
42
+ the ``tinydantic._config`` module docstring for the design
43
+ rationale.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ *,
49
+ model_name: str,
50
+ key: str,
51
+ first: str,
52
+ second: str,
53
+ ) -> None:
54
+ """Initialize with the conflicting classes and config key."""
55
+ super().__init__(
56
+ f"{model_name!r} inherits conflicting values for tinydantic "
57
+ f"config key {key!r} from unrelated base classes {first!r} "
58
+ f"and {second!r}. Set {key}= explicitly on {model_name!r} "
59
+ "to resolve the ambiguity.",
60
+ )
61
+
62
+
63
+ class DocumentNotFoundError(TinydanticError):
64
+ """Requested document is not found."""
65
+
66
+ def __init__(self) -> None:
67
+ """Initializes an instance of this class."""
68
+ super().__init__("Document not found")
69
+
70
+
71
+ class DocumentIDRequiredError(TinydanticError):
72
+ """Required document ID is missing."""
73
+
74
+ def __init__(self) -> None:
75
+ """Initializes an instance of this class."""
76
+ super().__init__("Document ID is required")