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.
- didactic_fastapi-0.1.0/.gitignore +48 -0
- didactic_fastapi-0.1.0/PKG-INFO +75 -0
- didactic_fastapi-0.1.0/README.md +56 -0
- didactic_fastapi-0.1.0/pyproject.toml +35 -0
- didactic_fastapi-0.1.0/src/didactic/fastapi/__init__.py +33 -0
- didactic_fastapi-0.1.0/src/didactic/fastapi/_adapter.py +100 -0
- didactic_fastapi-0.1.0/src/didactic/fastapi/py.typed +0 -0
|
@@ -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
|