pydantic-prism 0.1.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 (63) hide show
  1. pydantic_prism-0.1.0/LICENSE +21 -0
  2. pydantic_prism-0.1.0/PKG-INFO +138 -0
  3. pydantic_prism-0.1.0/README.md +98 -0
  4. pydantic_prism-0.1.0/pyproject.toml +151 -0
  5. pydantic_prism-0.1.0/src/pydantic_prism/__init__.py +73 -0
  6. pydantic_prism-0.1.0/src/pydantic_prism/__main__.py +5 -0
  7. pydantic_prism-0.1.0/src/pydantic_prism/_internal/__init__.py +7 -0
  8. pydantic_prism-0.1.0/src/pydantic_prism/_internal/codegen/__init__.py +44 -0
  9. pydantic_prism-0.1.0/src/pydantic_prism/_internal/codegen/cli.py +175 -0
  10. pydantic_prism-0.1.0/src/pydantic_prism/_internal/codegen/config.py +105 -0
  11. pydantic_prism-0.1.0/src/pydantic_prism/_internal/codegen/discover.py +124 -0
  12. pydantic_prism-0.1.0/src/pydantic_prism/_internal/codegen/generate.py +110 -0
  13. pydantic_prism-0.1.0/src/pydantic_prism/_internal/codegen/render.py +231 -0
  14. pydantic_prism-0.1.0/src/pydantic_prism/_internal/diagram.py +442 -0
  15. pydantic_prism-0.1.0/src/pydantic_prism/_internal/drift.py +62 -0
  16. pydantic_prism-0.1.0/src/pydantic_prism/_internal/flow.py +174 -0
  17. pydantic_prism-0.1.0/src/pydantic_prism/_internal/markers.py +155 -0
  18. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/__init__.py +24 -0
  19. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/bases.py +90 -0
  20. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/build.py +321 -0
  21. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/classes.py +411 -0
  22. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/collect.py +183 -0
  23. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/narrow.py +75 -0
  24. pydantic_prism-0.1.0/src/pydantic_prism/_internal/model/schema.py +100 -0
  25. pydantic_prism-0.1.0/src/pydantic_prism/_internal/readme.py +86 -0
  26. pydantic_prism-0.1.0/src/pydantic_prism/_internal/refs.py +362 -0
  27. pydantic_prism-0.1.0/src/pydantic_prism/_internal/scopes.py +397 -0
  28. pydantic_prism-0.1.0/src/pydantic_prism/_internal/validators.py +73 -0
  29. pydantic_prism-0.1.0/src/pydantic_prism/errors.py +69 -0
  30. pydantic_prism-0.1.0/src/pydantic_prism/py.typed +0 -0
  31. pydantic_prism-0.1.0/tests/__init__.py +0 -0
  32. pydantic_prism-0.1.0/tests/_codegen_fixtures.py +54 -0
  33. pydantic_prism-0.1.0/tests/_flow_fixtures.py +44 -0
  34. pydantic_prism-0.1.0/tests/test_bugsweep.py +274 -0
  35. pydantic_prism-0.1.0/tests/test_classification.py +171 -0
  36. pydantic_prism-0.1.0/tests/test_codegen.py +646 -0
  37. pydantic_prism-0.1.0/tests/test_custom_base.py +203 -0
  38. pydantic_prism-0.1.0/tests/test_default_scope.py +237 -0
  39. pydantic_prism-0.1.0/tests/test_diagram.py +294 -0
  40. pydantic_prism-0.1.0/tests/test_dict_refs.py +143 -0
  41. pydantic_prism-0.1.0/tests/test_docs.py +81 -0
  42. pydantic_prism-0.1.0/tests/test_embedded_refs.py +111 -0
  43. pydantic_prism-0.1.0/tests/test_ergonomics.py +86 -0
  44. pydantic_prism-0.1.0/tests/test_errors.py +104 -0
  45. pydantic_prism-0.1.0/tests/test_example_readmes.py +25 -0
  46. pydantic_prism-0.1.0/tests/test_fastapi.py +65 -0
  47. pydantic_prism-0.1.0/tests/test_from_canonical.py +107 -0
  48. pydantic_prism-0.1.0/tests/test_future_annotations.py +63 -0
  49. pydantic_prism-0.1.0/tests/test_internals.py +248 -0
  50. pydantic_prism-0.1.0/tests/test_name_template.py +72 -0
  51. pydantic_prism-0.1.0/tests/test_nested.py +97 -0
  52. pydantic_prism-0.1.0/tests/test_partial.py +156 -0
  53. pydantic_prism-0.1.0/tests/test_projection.py +201 -0
  54. pydantic_prism-0.1.0/tests/test_readme.py +374 -0
  55. pydantic_prism-0.1.0/tests/test_refinfo_kinds.py +98 -0
  56. pydantic_prism-0.1.0/tests/test_refs.py +160 -0
  57. pydantic_prism-0.1.0/tests/test_roundtrip.py +58 -0
  58. pydantic_prism-0.1.0/tests/test_scope_schema.py +158 -0
  59. pydantic_prism-0.1.0/tests/test_scoped_validator.py +185 -0
  60. pydantic_prism-0.1.0/tests/test_scopes.py +109 -0
  61. pydantic_prism-0.1.0/tests/test_sqlalchemy_bridge.py +115 -0
  62. pydantic_prism-0.1.0/tests/test_sqlmodel_bridge.py +176 -0
  63. pydantic_prism-0.1.0/tests/test_with_updates.py +144 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilja Orlovs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: pydantic-prism
3
+ Version: 0.1.0
4
+ Summary: One canonical pydantic model, many scoped projections — with a relationship graph that survives them.
5
+ Keywords: pydantic,schema,projection,scopes,dto,serialization,validation,json-schema,fastapi,data-classification,pii,code-generation,type-safety
6
+ Author-Email: Ilja Orlovs <IljaOrlovs@users.noreply.github.com>
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Code Generators
17
+ Classifier: Framework :: Pydantic
18
+ Classifier: Framework :: Pydantic :: 2
19
+ Classifier: Framework :: FastAPI
20
+ Classifier: Typing :: Typed
21
+ Project-URL: Homepage, https://github.com/release-art/pydantic-prism
22
+ Project-URL: Documentation, https://github.com/release-art/pydantic-prism/blob/main/docs/README.md
23
+ Project-URL: Repository, https://github.com/release-art/pydantic-prism
24
+ Project-URL: Changelog, https://github.com/release-art/pydantic-prism/blob/main/CHANGELOG.md
25
+ Project-URL: Issues, https://github.com/release-art/pydantic-prism/issues
26
+ Requires-Python: >=3.12
27
+ Requires-Dist: pydantic>=2.12
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=9.0.3; extra == "dev"
30
+ Requires-Dist: pytest-cov>=6.0; extra == "dev"
31
+ Requires-Dist: pytest-mock>=3.15.1; extra == "dev"
32
+ Requires-Dist: ruff>=0.15.16; extra == "dev"
33
+ Requires-Dist: pyright>=1.1.410; extra == "dev"
34
+ Requires-Dist: fastapi>=0.115; extra == "dev"
35
+ Requires-Dist: httpx>=0.27; extra == "dev"
36
+ Requires-Dist: pytest-local-badge>=1.3.0; extra == "dev"
37
+ Requires-Dist: sqlmodel>=0.0.22; extra == "dev"
38
+ Requires-Dist: sqlalchemy>=2.0; extra == "dev"
39
+ Description-Content-Type: text/markdown
40
+
41
+ # pydantic-prism
42
+
43
+ ![Tests](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/tests.svg)
44
+ ![Coverage](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/coverage.svg)
45
+ ![Skipped](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/skipped.svg)
46
+ ![XFailed](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/xfailed.svg)
47
+ ![Warnings](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/warnings.svg)
48
+ ![Duration](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/duration.svg)
49
+ ![Last run](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/last-run.svg)
50
+
51
+ Project one canonical pydantic model along any axis — and keep the
52
+ relationship graph that survives the projection.
53
+
54
+ Tag a single model's fields with named **scopes**; derive real
55
+ `pydantic.BaseModel` subclasses per scope (API response, storage row, LLM
56
+ tool input, audit log) with working validation, serialization, and JSON
57
+ schema. Declare FK-style **references** in the same metadata and introspect
58
+ them through `__refs__` — the graph survives every projection. The
59
+ projection half has prior art; [combining it with an introspectable
60
+ relationship graph](docs/explanation/vs-prior-art.md) does not.
61
+
62
+ ## 30 seconds
63
+
64
+ ```python
65
+ from typing import Annotated
66
+ from uuid import UUID
67
+
68
+ from pydantic_prism import Scope, ScopedModel, scoped
69
+
70
+
71
+ class Public(Scope): ...
72
+ class Internal(Public): ... # Internal sees everything Public sees
73
+ class Storage(Internal): ... # Storage sees everything Internal sees
74
+
75
+
76
+ class User(ScopedModel):
77
+ id: Annotated[UUID, scoped(Public)]
78
+ email: Annotated[str, scoped(Internal)]
79
+ password_hash: Annotated[str, scoped(Storage)]
80
+ display_name: Annotated[str, scoped(Public)]
81
+
82
+
83
+ UserPublic = User.scope(Public) # fields: id, display_name
84
+ UserInternal = User.scope(Internal) # fields: id, email, display_name
85
+ UserStorage = User.scope(Storage) # all four fields
86
+ ```
87
+
88
+ `UserPublic` is a real, cached `BaseModel` subclass named `"UserPublic"` —
89
+ `User.scope(Public) is User.scope(Public)`, so FastAPI response models and
90
+ OpenAPI component schemas stay stable.
91
+
92
+ Scopes are classes; inheritance forms the scope graph, so the membership
93
+ rule is one line: a field tagged `T` is in projection `S` iff
94
+ `issubclass(S, T)`. Untagged fields belong to no scope and can never leak
95
+ into a projection. Scopes compose with set operators (`| & - ~`), both in
96
+ field tags and at the call site.
97
+
98
+ ## Why not hand-write `UserIn` / `UserOut`?
99
+
100
+ Parallel classes drift from the canonical, lose constraints, and have no
101
+ idea your models reference each other. Prism derives every face from one
102
+ source of truth and keeps the references coherent across all of them.
103
+ See [projections, not inheritance](docs/explanation/projections-not-inheritance.md).
104
+
105
+ ## Install
106
+
107
+ ```sh
108
+ pip install pydantic-prism # pydantic >= 2.12, Python >= 3.12
109
+ ```
110
+
111
+ ## Documentation
112
+
113
+ The docs follow the [Diátaxis](https://diataxis.fr) framework — start where
114
+ your need fits:
115
+
116
+ - **[Documentation home](docs/README.md)** — the full table of contents.
117
+ - **[Tutorial: your first scoped model](docs/tutorial/first-scoped-model.md)** —
118
+ one hand-held lesson, one model to two projections.
119
+ - **[How-to guides](docs/how-to/README.md)** — short recipes: redact PII,
120
+ trace data flow, PATCH models, FastAPI, ORM bridge, editor stubs, diagrams.
121
+ - **[Reference](docs/reference/README.md)** — the [API](docs/reference/api.md),
122
+ the [`prism` CLI](docs/reference/cli.md), and the [error table](docs/reference/errors.md).
123
+ - **[Explanation](docs/explanation/README.md)** — the scope algebra, why
124
+ projections aren't inheritance, what `ref()` does and does not model.
125
+
126
+ [**ROADMAP.md**](ROADMAP.md) lists what is shipped, planned, and deliberately
127
+ out of scope.
128
+
129
+ ## Develop
130
+
131
+ ```sh
132
+ pdm install -G dev
133
+ bin/test.sh # pytest with coverage (100% gate)
134
+ bin/autoformat.sh # ruff format + ruff check --fix
135
+ pdm run pyright # strict, src/
136
+ ```
137
+
138
+ MIT licensed. Built on the public pydantic API only — no `pydantic._internal`.
@@ -0,0 +1,98 @@
1
+ # pydantic-prism
2
+
3
+ ![Tests](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/tests.svg)
4
+ ![Coverage](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/coverage.svg)
5
+ ![Skipped](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/skipped.svg)
6
+ ![XFailed](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/xfailed.svg)
7
+ ![Warnings](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/warnings.svg)
8
+ ![Duration](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/duration.svg)
9
+ ![Last run](https://raw.githubusercontent.com/release-art/pydantic-prism/main/docs/badges/last-run.svg)
10
+
11
+ Project one canonical pydantic model along any axis — and keep the
12
+ relationship graph that survives the projection.
13
+
14
+ Tag a single model's fields with named **scopes**; derive real
15
+ `pydantic.BaseModel` subclasses per scope (API response, storage row, LLM
16
+ tool input, audit log) with working validation, serialization, and JSON
17
+ schema. Declare FK-style **references** in the same metadata and introspect
18
+ them through `__refs__` — the graph survives every projection. The
19
+ projection half has prior art; [combining it with an introspectable
20
+ relationship graph](docs/explanation/vs-prior-art.md) does not.
21
+
22
+ ## 30 seconds
23
+
24
+ ```python
25
+ from typing import Annotated
26
+ from uuid import UUID
27
+
28
+ from pydantic_prism import Scope, ScopedModel, scoped
29
+
30
+
31
+ class Public(Scope): ...
32
+ class Internal(Public): ... # Internal sees everything Public sees
33
+ class Storage(Internal): ... # Storage sees everything Internal sees
34
+
35
+
36
+ class User(ScopedModel):
37
+ id: Annotated[UUID, scoped(Public)]
38
+ email: Annotated[str, scoped(Internal)]
39
+ password_hash: Annotated[str, scoped(Storage)]
40
+ display_name: Annotated[str, scoped(Public)]
41
+
42
+
43
+ UserPublic = User.scope(Public) # fields: id, display_name
44
+ UserInternal = User.scope(Internal) # fields: id, email, display_name
45
+ UserStorage = User.scope(Storage) # all four fields
46
+ ```
47
+
48
+ `UserPublic` is a real, cached `BaseModel` subclass named `"UserPublic"` —
49
+ `User.scope(Public) is User.scope(Public)`, so FastAPI response models and
50
+ OpenAPI component schemas stay stable.
51
+
52
+ Scopes are classes; inheritance forms the scope graph, so the membership
53
+ rule is one line: a field tagged `T` is in projection `S` iff
54
+ `issubclass(S, T)`. Untagged fields belong to no scope and can never leak
55
+ into a projection. Scopes compose with set operators (`| & - ~`), both in
56
+ field tags and at the call site.
57
+
58
+ ## Why not hand-write `UserIn` / `UserOut`?
59
+
60
+ Parallel classes drift from the canonical, lose constraints, and have no
61
+ idea your models reference each other. Prism derives every face from one
62
+ source of truth and keeps the references coherent across all of them.
63
+ See [projections, not inheritance](docs/explanation/projections-not-inheritance.md).
64
+
65
+ ## Install
66
+
67
+ ```sh
68
+ pip install pydantic-prism # pydantic >= 2.12, Python >= 3.12
69
+ ```
70
+
71
+ ## Documentation
72
+
73
+ The docs follow the [Diátaxis](https://diataxis.fr) framework — start where
74
+ your need fits:
75
+
76
+ - **[Documentation home](docs/README.md)** — the full table of contents.
77
+ - **[Tutorial: your first scoped model](docs/tutorial/first-scoped-model.md)** —
78
+ one hand-held lesson, one model to two projections.
79
+ - **[How-to guides](docs/how-to/README.md)** — short recipes: redact PII,
80
+ trace data flow, PATCH models, FastAPI, ORM bridge, editor stubs, diagrams.
81
+ - **[Reference](docs/reference/README.md)** — the [API](docs/reference/api.md),
82
+ the [`prism` CLI](docs/reference/cli.md), and the [error table](docs/reference/errors.md).
83
+ - **[Explanation](docs/explanation/README.md)** — the scope algebra, why
84
+ projections aren't inheritance, what `ref()` does and does not model.
85
+
86
+ [**ROADMAP.md**](ROADMAP.md) lists what is shipped, planned, and deliberately
87
+ out of scope.
88
+
89
+ ## Develop
90
+
91
+ ```sh
92
+ pdm install -G dev
93
+ bin/test.sh # pytest with coverage (100% gate)
94
+ bin/autoformat.sh # ruff format + ruff check --fix
95
+ pdm run pyright # strict, src/
96
+ ```
97
+
98
+ MIT licensed. Built on the public pydantic API only — no `pydantic._internal`.
@@ -0,0 +1,151 @@
1
+ [project]
2
+ name = "pydantic-prism"
3
+ version = "0.1.0"
4
+ description = "One canonical pydantic model, many scoped projections — with a relationship graph that survives them."
5
+ authors = [
6
+ { name = "Ilja Orlovs", email = "IljaOrlovs@users.noreply.github.com" },
7
+ ]
8
+ dependencies = [
9
+ "pydantic>=2.12",
10
+ ]
11
+ requires-python = ">=3.12"
12
+ readme = "README.md"
13
+ license = "MIT"
14
+ keywords = [
15
+ "pydantic",
16
+ "schema",
17
+ "projection",
18
+ "scopes",
19
+ "dto",
20
+ "serialization",
21
+ "validation",
22
+ "json-schema",
23
+ "fastapi",
24
+ "data-classification",
25
+ "pii",
26
+ "code-generation",
27
+ "type-safety",
28
+ ]
29
+ classifiers = [
30
+ "Development Status :: 4 - Beta",
31
+ "Intended Audience :: Developers",
32
+ "Operating System :: OS Independent",
33
+ "Programming Language :: Python :: 3",
34
+ "Programming Language :: Python :: 3 :: Only",
35
+ "Programming Language :: Python :: 3.12",
36
+ "Programming Language :: Python :: 3.13",
37
+ "Topic :: Software Development :: Libraries :: Python Modules",
38
+ "Topic :: Software Development :: Code Generators",
39
+ "Framework :: Pydantic",
40
+ "Framework :: Pydantic :: 2",
41
+ "Framework :: FastAPI",
42
+ "Typing :: Typed",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/release-art/pydantic-prism"
47
+ Documentation = "https://github.com/release-art/pydantic-prism/blob/main/docs/README.md"
48
+ Repository = "https://github.com/release-art/pydantic-prism"
49
+ Changelog = "https://github.com/release-art/pydantic-prism/blob/main/CHANGELOG.md"
50
+ Issues = "https://github.com/release-art/pydantic-prism/issues"
51
+
52
+ [project.scripts]
53
+ prism = "pydantic_prism._internal.codegen:main"
54
+
55
+ [project.optional-dependencies]
56
+ dev = [
57
+ "pytest>=9.0.3",
58
+ "pytest-cov>=6.0",
59
+ "pytest-mock>=3.15.1",
60
+ "ruff>=0.15.16",
61
+ "pyright>=1.1.410",
62
+ "fastapi>=0.115",
63
+ "httpx>=0.27",
64
+ "pytest-local-badge>=1.3.0",
65
+ "sqlmodel>=0.0.22",
66
+ "sqlalchemy>=2.0",
67
+ ]
68
+
69
+ [build-system]
70
+ requires = [
71
+ "pdm-backend",
72
+ ]
73
+ build-backend = "pdm.backend"
74
+
75
+ [tool.pdm]
76
+ distribution = true
77
+
78
+ [tool.ruff]
79
+ target-version = "py312"
80
+ line-length = 88
81
+
82
+ [tool.ruff.lint]
83
+ select = [
84
+ "A",
85
+ "B",
86
+ "C",
87
+ "E",
88
+ "F",
89
+ "I",
90
+ "W",
91
+ "N",
92
+ "C4",
93
+ "T20",
94
+ "PTH",
95
+ ]
96
+ ignore = [
97
+ "N802",
98
+ ]
99
+
100
+ [tool.ruff.lint.per-file-ignores]
101
+ "__init__.py" = [
102
+ "F401",
103
+ "E402",
104
+ ]
105
+ "src/pydantic_prism/_internal/codegen/cli.py" = [
106
+ "T201",
107
+ ]
108
+ "bin/*.py" = [
109
+ "T201",
110
+ ]
111
+ "tests/**" = [
112
+ "N806",
113
+ ]
114
+ "examples/**" = [
115
+ "N806",
116
+ "T201",
117
+ ]
118
+
119
+ [tool.ruff.lint.isort]
120
+ order-by-type = true
121
+ known-first-party = [
122
+ "pydantic_prism",
123
+ ]
124
+ forced-separate = [
125
+ "tests",
126
+ ]
127
+
128
+ [tool.ruff.format]
129
+ quote-style = "double"
130
+
131
+ [tool.pyright]
132
+ include = [
133
+ "src",
134
+ ]
135
+ typeCheckingMode = "strict"
136
+ pythonVersion = "3.12"
137
+ venvPath = "."
138
+ venv = ".venv"
139
+
140
+ [tool.coverage.report]
141
+ exclude_also = [
142
+ "raise NotImplementedError",
143
+ "if TYPE_CHECKING:",
144
+ ]
145
+
146
+ [tool.pytest.ini_options]
147
+ minversion = "8.4"
148
+ testpaths = [
149
+ "tests",
150
+ ]
151
+ addopts = " --cov --no-cov-on-fail\n -v -l --color=yes\n --local-badge-output-dir docs/badges/\n --cov-fail-under=100\n --local-badge-package=pydantic_prism\n"
@@ -0,0 +1,73 @@
1
+ """pydantic-prism: one canonical pydantic model, many scoped projections.
2
+
3
+ Tag fields on a single model with named scopes via ``Annotated`` metadata,
4
+ derive real pydantic model subclasses per scope, and keep FK-style
5
+ relationships introspectable across projections.
6
+ """
7
+
8
+ from pydantic.experimental.missing_sentinel import MISSING
9
+
10
+ from ._internal.diagram import Diagram, projection_diagram, scope_diagram
11
+ from ._internal.flow import (
12
+ ClassifiedField,
13
+ FlowEdge,
14
+ FlowNode,
15
+ FlowReport,
16
+ build_flow_report,
17
+ )
18
+ from ._internal.markers import BackRef, Ref, Scoped, backref, ref, scoped
19
+ from ._internal.model import Projection, ScopedModel
20
+ from ._internal.refs import (
21
+ BackRefInfo,
22
+ EmbeddedRefInfo,
23
+ IdRefInfo,
24
+ RefGraph,
25
+ RefInfo,
26
+ RefShape,
27
+ )
28
+ from ._internal.scopes import Classification, Scope, ScopeExpr
29
+ from ._internal.validators import scoped_validator
30
+ from .errors import (
31
+ EmptyProjectionError,
32
+ PrismError,
33
+ ProjectionBaseError,
34
+ ProjectionNameError,
35
+ RefResolutionError,
36
+ StaleProjectionStubError,
37
+ )
38
+
39
+ __all__ = [
40
+ "MISSING",
41
+ "BackRef",
42
+ "BackRefInfo",
43
+ "Classification",
44
+ "ClassifiedField",
45
+ "Diagram",
46
+ "EmbeddedRefInfo",
47
+ "EmptyProjectionError",
48
+ "FlowEdge",
49
+ "FlowNode",
50
+ "FlowReport",
51
+ "IdRefInfo",
52
+ "PrismError",
53
+ "Projection",
54
+ "ProjectionBaseError",
55
+ "ProjectionNameError",
56
+ "Ref",
57
+ "RefGraph",
58
+ "RefInfo",
59
+ "RefResolutionError",
60
+ "RefShape",
61
+ "Scope",
62
+ "ScopeExpr",
63
+ "Scoped",
64
+ "ScopedModel",
65
+ "StaleProjectionStubError",
66
+ "backref",
67
+ "build_flow_report",
68
+ "projection_diagram",
69
+ "ref",
70
+ "scope_diagram",
71
+ "scoped",
72
+ "scoped_validator",
73
+ ]
@@ -0,0 +1,5 @@
1
+ """``python -m pydantic_prism`` — equivalent to the ``prism`` console script."""
2
+
3
+ from ._internal.codegen import main
4
+
5
+ raise SystemExit(main())
@@ -0,0 +1,7 @@
1
+ """Private implementation package for pydantic-prism.
2
+
3
+ Everything here is internal: the public API is re-exported from
4
+ :mod:`pydantic_prism`'s top-level ``__init__``. Import paths under
5
+ ``pydantic_prism._internal`` are not part of the supported surface and may change
6
+ between releases without notice.
7
+ """
@@ -0,0 +1,44 @@
1
+ """``prism gen`` / ``prism check`` — generate static-typing stubs for projections.
2
+
3
+ Static type checkers (pyright/Pylance — and thus VSCode — plus mypy) cannot see
4
+ the fields of ``Model.scope(...)``: scope membership lives in ``Annotated``
5
+ metadata and the selection runs the scope algebra at runtime. Pyright has no
6
+ third-party plugin API, so the only *universal* fix is ordinary type
7
+ declarations every tool already reads. This package generates them.
8
+
9
+ For each projection it emits, into one module::
10
+
11
+ if TYPE_CHECKING:
12
+ class ScreenshotRef(Projection): # the checker reads this
13
+ id: UUID
14
+ timestamp: datetime
15
+ else:
16
+ ScreenshotRef = Screenshot.scope(Ref) # the genuine cached projection
17
+
18
+ assert_fresh(ScreenshotRef, "<signature>") # startup drift guard
19
+
20
+ Layout: :mod:`config` (load `[tool.pydantic-prism]`), :mod:`discover` (find
21
+ the projection workset), :mod:`render` (annotations/scopes/defaults → source),
22
+ :mod:`generate` (assemble the stub + README), :mod:`cli` (the ``prism``
23
+ command). Names re-exported here (with ``as`` aliases) keep the
24
+ ``pydantic_prism._internal.codegen.X`` import paths stable.
25
+ """
26
+
27
+ from .cli import main as main
28
+ from .config import CodegenError as CodegenError
29
+ from .config import Config as Config
30
+ from .config import ProjectionSpec as ProjectionSpec
31
+ from .config import load_config as load_config
32
+ from .discover import _projections_in as _projections_in
33
+ from .discover import _reject_name_clashes as _reject_name_clashes
34
+ from .generate import generate as generate
35
+ from .generate import generate_readme as generate_readme
36
+ from .render import _field_suffix as _field_suffix
37
+ from .render import _import_lines as _import_lines
38
+ from .render import _Imports as _Imports
39
+ from .render import _render_annotation as _render_annotation
40
+ from .render import _render_bare as _render_bare
41
+ from .render import _render_literal as _render_literal
42
+ from .render import _render_scope_expr as _render_scope_expr
43
+
44
+ __all__ = ["CodegenError", "Config", "ProjectionSpec", "generate", "main"]