didactic-fastapi 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.
@@ -0,0 +1,48 @@
1
+ # dev-only working notes, design drafts, scratch
2
+ notes/
3
+
4
+ # python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+ .Python
10
+ build/
11
+ dist/
12
+ *.egg-info/
13
+ .eggs/
14
+ *.egg
15
+
16
+ # virtualenvs
17
+ .venv/
18
+ venv/
19
+ env/
20
+
21
+ # uv
22
+ .uv/
23
+
24
+ # testing / coverage
25
+ .pytest_cache/
26
+ .coverage
27
+ .coverage.*
28
+ htmlcov/
29
+ .tox/
30
+ .nox/
31
+
32
+ # type-checkers / linters
33
+ .mypy_cache/
34
+ .ruff_cache/
35
+ .pyright/
36
+
37
+ # mkdocs build output
38
+ site/
39
+
40
+ # editors
41
+ .vscode/
42
+ .idea/
43
+ *.swp
44
+ *.swo
45
+
46
+ # os
47
+ .DS_Store
48
+ Thumbs.db
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: didactic-fastapi
3
+ Version: 0.1.0
4
+ Summary: FastAPI integration for didactic Models.
5
+ Author-email: Aaron Steven White <aaronstevenwhite@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: didactic,fastapi,openapi,panproto
8
+ Classifier: Development Status :: 2 - Pre-Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.14
15
+ Requires-Dist: didactic
16
+ Requires-Dist: didactic-pydantic
17
+ Requires-Dist: fastapi>=0.115
18
+ Description-Content-Type: text/markdown
19
+
20
+ # didactic-fastapi
21
+
22
+ FastAPI integration for `dx.Model` types. Contributes
23
+ `didactic.fastapi` to the namespace package.
24
+
25
+ ## Install
26
+
27
+ ```sh
28
+ pip install didactic-fastapi
29
+ ```
30
+
31
+ The package depends on `didactic`, `didactic-pydantic`, and
32
+ `fastapi>=0.115`.
33
+
34
+ ## Usage
35
+
36
+ ```python
37
+ import didactic.api as dx
38
+ from fastapi import FastAPI
39
+ from didactic.fastapi import as_response, register_validation_handler
40
+
41
+
42
+ class User(dx.Model):
43
+ id: str
44
+ email: str
45
+
46
+
47
+ app = FastAPI()
48
+ register_validation_handler(app)
49
+
50
+
51
+ @app.get("/users/{uid}", response_model=as_response(User))
52
+ def get_user(uid: str) -> User:
53
+ return User(id=uid, email="ada@example.org")
54
+ ```
55
+
56
+ `as_response(model)` returns a `pydantic.BaseModel` subclass
57
+ mirroring the input `dx.Model`. FastAPI uses the result for response
58
+ validation and OpenAPI generation. The conversion is cached per input
59
+ class.
60
+
61
+ `as_request(model)` is a synonym; use whichever name reads naturally
62
+ in your route signatures.
63
+
64
+ `register_validation_handler(app)` installs an exception handler so
65
+ that `dx.ValidationError` raised inside a route surfaces as a 422
66
+ response shaped like FastAPI's own validation errors.
67
+
68
+ ## Documentation
69
+
70
+ See [Guides > FastAPI](https://panproto.dev/didactic/guide/fastapi/)
71
+ for the full integration guide and caveats.
72
+
73
+ ## License
74
+
75
+ MIT.
@@ -0,0 +1,56 @@
1
+ # didactic-fastapi
2
+
3
+ FastAPI integration for `dx.Model` types. Contributes
4
+ `didactic.fastapi` to the namespace package.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ pip install didactic-fastapi
10
+ ```
11
+
12
+ The package depends on `didactic`, `didactic-pydantic`, and
13
+ `fastapi>=0.115`.
14
+
15
+ ## Usage
16
+
17
+ ```python
18
+ import didactic.api as dx
19
+ from fastapi import FastAPI
20
+ from didactic.fastapi import as_response, register_validation_handler
21
+
22
+
23
+ class User(dx.Model):
24
+ id: str
25
+ email: str
26
+
27
+
28
+ app = FastAPI()
29
+ register_validation_handler(app)
30
+
31
+
32
+ @app.get("/users/{uid}", response_model=as_response(User))
33
+ def get_user(uid: str) -> User:
34
+ return User(id=uid, email="ada@example.org")
35
+ ```
36
+
37
+ `as_response(model)` returns a `pydantic.BaseModel` subclass
38
+ mirroring the input `dx.Model`. FastAPI uses the result for response
39
+ validation and OpenAPI generation. The conversion is cached per input
40
+ class.
41
+
42
+ `as_request(model)` is a synonym; use whichever name reads naturally
43
+ in your route signatures.
44
+
45
+ `register_validation_handler(app)` installs an exception handler so
46
+ that `dx.ValidationError` raised inside a route surfaces as a 422
47
+ response shaped like FastAPI's own validation errors.
48
+
49
+ ## Documentation
50
+
51
+ See [Guides > FastAPI](https://panproto.dev/didactic/guide/fastapi/)
52
+ for the full integration guide and caveats.
53
+
54
+ ## License
55
+
56
+ MIT.
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "didactic-fastapi"
7
+ version = "0.1.0"
8
+ description = "FastAPI integration for didactic Models."
9
+ readme = "README.md"
10
+ requires-python = ">=3.14"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Aaron Steven White", email = "aaronstevenwhite@gmail.com" },
14
+ ]
15
+ keywords = ["fastapi", "didactic", "openapi", "panproto"]
16
+ classifiers = [
17
+ "Development Status :: 2 - Pre-Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.14",
22
+ "Typing :: Typed",
23
+ ]
24
+ dependencies = [
25
+ "didactic",
26
+ "didactic-pydantic",
27
+ "fastapi>=0.115",
28
+ ]
29
+
30
+ [tool.hatch.build.targets.sdist]
31
+ include = ["src/didactic/fastapi"]
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ only-include = ["src/didactic/fastapi"]
35
+ sources = ["src"]
@@ -0,0 +1,33 @@
1
+ """didactic-fastapi: FastAPI integration for didactic Models.
2
+
3
+ Top-level surface:
4
+
5
+ [as_request][didactic.fastapi.as_request]
6
+ Wrap a [didactic.api.Model][didactic.api.Model] as a FastAPI-friendly
7
+ request body type. Calls
8
+ [didactic.pydantic.to_pydantic][didactic.pydantic.to_pydantic]
9
+ under the hood.
10
+ [as_response][didactic.fastapi.as_response]
11
+ The same conversion for response models. Synonymous with
12
+ [as_request][didactic.fastapi.as_request]; provided as a
13
+ separate name so route signatures read naturally.
14
+ [register_validation_handler][didactic.fastapi.register_validation_handler]
15
+ Install an exception handler that turns
16
+ [didactic.api.ValidationError][didactic.api.ValidationError] into a 422
17
+ response shaped like FastAPI's own validation errors.
18
+ """
19
+
20
+ from didactic.fastapi._adapter import (
21
+ as_request,
22
+ as_response,
23
+ register_validation_handler,
24
+ )
25
+
26
+ __version__ = "0.1.0"
27
+
28
+ __all__ = [
29
+ "__version__",
30
+ "as_request",
31
+ "as_response",
32
+ "register_validation_handler",
33
+ ]
@@ -0,0 +1,100 @@
1
+ """didactic Model -> FastAPI route adapter.
2
+
3
+ The adapter is a thin layer over
4
+ [didactic.pydantic.to_pydantic][didactic.pydantic.to_pydantic]: each
5
+ ``dx.Model`` is converted to a Pydantic ``BaseModel`` once, cached,
6
+ and reused on every request. The cache is keyed by the source class
7
+ so the conversion only happens once per Model regardless of how many
8
+ routes reference it.
9
+
10
+ See Also
11
+ --------
12
+ didactic.pydantic.to_pydantic : the underlying converter.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import TYPE_CHECKING
18
+
19
+ from didactic.pydantic import to_pydantic
20
+
21
+ if TYPE_CHECKING:
22
+ from pydantic import BaseModel
23
+
24
+ import didactic.api as dx
25
+ from fastapi import FastAPI
26
+
27
+
28
+ # cache the conversion so each Model maps to the same Pydantic class
29
+ _CACHE: dict[type, type] = {}
30
+
31
+
32
+ def as_request[M: dx.Model](model: type[M]) -> type[BaseModel]:
33
+ """Return the Pydantic adapter for use as a FastAPI request body type.
34
+
35
+ Parameters
36
+ ----------
37
+ model
38
+ A [didactic.api.Model][didactic.api.Model] subclass.
39
+
40
+ Returns
41
+ -------
42
+ type
43
+ A Pydantic ``BaseModel`` subclass mirroring ``model``'s shape.
44
+ Cached: subsequent calls with the same ``model`` return the
45
+ same class.
46
+ """
47
+ if model not in _CACHE:
48
+ _CACHE[model] = to_pydantic(model)
49
+ return _CACHE[model]
50
+
51
+
52
+ def as_response[M: dx.Model](model: type[M]) -> type[BaseModel]:
53
+ """Synonym of [as_request][didactic.fastapi.as_request].
54
+
55
+ Provided as a separate name so route signatures read naturally
56
+ (``response_model=as_response(User)``).
57
+ """
58
+ return as_request(model)
59
+
60
+
61
+ def register_validation_handler(app: FastAPI) -> None:
62
+ """Install a 422 handler for ``dx.ValidationError``.
63
+
64
+ Parameters
65
+ ----------
66
+ app
67
+ The FastAPI application to attach the handler to.
68
+
69
+ Notes
70
+ -----
71
+ The response shape mirrors FastAPI's own validation-error format,
72
+ so existing clients see no change. didactic's per-error
73
+ ``loc``/``type``/``msg`` map onto FastAPI's identical names.
74
+ """
75
+ import didactic.api as dx_module # noqa: PLC0415
76
+ from fastapi import Request # noqa: PLC0415
77
+ from fastapi.responses import JSONResponse # noqa: PLC0415
78
+
79
+ # ``_handler`` is registered with the app via the decorator side
80
+ # effect; the local binding is not used directly afterwards. The
81
+ # explicit ``del`` makes the intent obvious and silences pyright's
82
+ # ``reportUnusedFunction`` warning.
83
+ @app.exception_handler(dx_module.ValidationError)
84
+ async def _handler(_: Request, exc: dx_module.ValidationError) -> JSONResponse:
85
+ body = {
86
+ "detail": [
87
+ {"loc": list(entry.loc), "msg": entry.msg, "type": entry.type}
88
+ for entry in exc.entries
89
+ ],
90
+ }
91
+ return JSONResponse(status_code=422, content=body)
92
+
93
+ del _handler
94
+
95
+
96
+ __all__ = [
97
+ "as_request",
98
+ "as_response",
99
+ "register_validation_handler",
100
+ ]
File without changes