fastapi-error-map 0.9.5__tar.gz → 0.9.7__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.
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/.github/workflows/ci.yaml +3 -3
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/.github/workflows/test-compatibility.yaml +2 -4
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/PKG-INFO +18 -28
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/README.md +13 -9
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/examples/errors.py +3 -1
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/error_handling.py +13 -6
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/openapi.py +6 -1
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/rules.py +13 -5
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/translator_policy.py +10 -3
- fastapi_error_map-0.9.7/noxfile.py +14 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/pyproject.toml +19 -13
- fastapi_error_map-0.9.7/tests/integration/conftest.py +25 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/integration/test_example.py +11 -5
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/integration/test_routing.py +11 -6
- fastapi_error_map-0.9.7/tests/integration/test_threadpool.py +79 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/test_error_handling.py +102 -3
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/test_openapi.py +20 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/test_rules.py +20 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/uv.lock +67 -24
- fastapi_error_map-0.9.5/noxfile.py +0 -13
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/.gitignore +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/.pre-commit-config.yaml +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/LICENSE +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/Makefile +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/docs/example-openapi.png +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/examples/__init__.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/examples/main.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/__init__.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/py.typed +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/routing.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/fastapi_error_map/translators.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/__init__.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/integration/__init__.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/__init__.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/error_stubs.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/test_translators.py +0 -0
- {fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/tests/unit/translator_stubs.py +0 -0
|
@@ -18,13 +18,13 @@ jobs:
|
|
|
18
18
|
uses: astral-sh/setup-uv@v6
|
|
19
19
|
|
|
20
20
|
- name: Install dependencies
|
|
21
|
-
run: uv
|
|
21
|
+
run: uv sync --locked --group all
|
|
22
22
|
|
|
23
23
|
- name: Lint code
|
|
24
|
-
run: make code.lint
|
|
24
|
+
run: uv run make code.lint
|
|
25
25
|
|
|
26
26
|
- name: Run tests for Codecov
|
|
27
|
-
run: pytest --cov=fastapi_error_map --cov-branch --cov-report=xml
|
|
27
|
+
run: uv run pytest --cov=fastapi_error_map --cov-branch --cov-report=xml
|
|
28
28
|
|
|
29
29
|
- name: Upload coverage reports to Codecov
|
|
30
30
|
uses: codecov/codecov-action@v5
|
{fastapi_error_map-0.9.5 → fastapi_error_map-0.9.7}/.github/workflows/test-compatibility.yaml
RENAMED
|
@@ -20,6 +20,7 @@ jobs:
|
|
|
20
20
|
- "3.11"
|
|
21
21
|
- "3.12"
|
|
22
22
|
- "3.13"
|
|
23
|
+
- "3.14"
|
|
23
24
|
|
|
24
25
|
steps:
|
|
25
26
|
- uses: actions/checkout@v4
|
|
@@ -31,8 +32,5 @@ jobs:
|
|
|
31
32
|
- name: Install uv
|
|
32
33
|
uses: astral-sh/setup-uv@v6
|
|
33
34
|
|
|
34
|
-
- name: Install nox
|
|
35
|
-
run: uv pip install nox --system
|
|
36
|
-
|
|
37
35
|
- name: Run tests
|
|
38
|
-
run: nox -s compatibility
|
|
36
|
+
run: uvx nox -s compatibility
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-error-map
|
|
3
|
-
Version: 0.9.
|
|
4
|
-
Summary: Elegant per-endpoint error handling for FastAPI that keeps OpenAPI
|
|
3
|
+
Version: 0.9.7
|
|
4
|
+
Summary: Elegant per-endpoint error handling for FastAPI that keeps OpenAPI in sync
|
|
5
5
|
Project-URL: Homepage, https://github.com/ivan-borovets/fastapi-error-map
|
|
6
6
|
Project-URL: Repository, https://github.com/ivan-borovets/fastapi-error-map
|
|
7
7
|
Author-email: Ivan Borovets <ivan.r.borovets@gmail.com>
|
|
@@ -17,37 +17,23 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
21
22
|
Classifier: Typing :: Typed
|
|
22
23
|
Requires-Python: >=3.9
|
|
23
|
-
Requires-Dist: fastapi
|
|
24
|
-
Requires-Dist: orjson
|
|
25
|
-
Provides-Extra: dev
|
|
26
|
-
Requires-Dist: mypy==1.8.0; extra == 'dev'
|
|
27
|
-
Requires-Dist: pre-commit==4.2.0; extra == 'dev'
|
|
28
|
-
Requires-Dist: ruff==0.11.2; extra == 'dev'
|
|
29
|
-
Provides-Extra: examples
|
|
30
|
-
Requires-Dist: uvicorn==0.35.0; extra == 'examples'
|
|
31
|
-
Provides-Extra: publish
|
|
32
|
-
Requires-Dist: hatch==1.14.1; extra == 'publish'
|
|
33
|
-
Provides-Extra: test
|
|
34
|
-
Requires-Dist: coverage==7.10.1; extra == 'test'
|
|
35
|
-
Requires-Dist: httpx==0.28.1; extra == 'test'
|
|
36
|
-
Requires-Dist: nox==2025.5.1; extra == 'test'
|
|
37
|
-
Requires-Dist: pytest-asyncio==1.1.0; extra == 'test'
|
|
38
|
-
Requires-Dist: pytest-cov==6.2.1; extra == 'test'
|
|
39
|
-
Requires-Dist: pytest==8.4.1; extra == 'test'
|
|
24
|
+
Requires-Dist: fastapi==0.100.0
|
|
25
|
+
Requires-Dist: orjson==3.11.1
|
|
40
26
|
Description-Content-Type: text/markdown
|
|
41
27
|
|
|
42
28
|
## FastAPI Error Map
|
|
43
29
|
|
|
44
|
-
[](https://badge.fury.io/py/fastapi-error-map)
|
|
31
|
+

|
|
46
32
|
[](https://codecov.io/gh/ivan-borovets/fastapi-error-map)
|
|
47
33
|

|
|
48
34
|

|
|
49
35
|
|
|
50
|
-
Elegant per-endpoint error handling for FastAPI that keeps OpenAPI
|
|
36
|
+
Elegant per-endpoint error handling for FastAPI that keeps OpenAPI in sync.
|
|
51
37
|
|
|
52
38
|
### 📦 Installation
|
|
53
39
|
|
|
@@ -128,6 +114,9 @@ See the [example in the 🚀 Quickstart](#-quickstart).
|
|
|
128
114
|
Error handling rules are defined directly in the route declaration of an `ErrorAwareRouter`, using the `error_map`
|
|
129
115
|
parameter.
|
|
130
116
|
|
|
117
|
+
> [!NOTE]
|
|
118
|
+
> `error_map` handles HTTP error responses only — statuses must be 4xx or 5xx.
|
|
119
|
+
|
|
131
120
|
There are two ways to do it:
|
|
132
121
|
|
|
133
122
|
#### 🔸 Short Form
|
|
@@ -173,10 +162,10 @@ error_map = {
|
|
|
173
162
|
|
|
174
163
|
Parameters of `rule(...)`, * — required:
|
|
175
164
|
|
|
176
|
-
- `status`* — HTTP status code to return (e.g. `404`, `409`, `422`)
|
|
177
|
-
- `translator` — object that converts an exception into
|
|
178
|
-
`{ "error": "
|
|
179
|
-
- `on_error` — function to call when an exception occurs (e.g. logging or alerting)
|
|
165
|
+
- `status`* — HTTP status code to return (must be 4xx or 5xx; e.g. `404`, `409`, `422`)
|
|
166
|
+
- `translator` — object that converts an exception into serializable payload. If not provided, the default one is used
|
|
167
|
+
(`{ "error": str(err) }` for 4xx; `{ "error": "Internal server error" }` for 5xx).
|
|
168
|
+
- `on_error` — function to call when an exception occurs (e.g. logging or alerting). Can be awaitable
|
|
180
169
|
|
|
181
170
|
#### 🧩 Matching semantics
|
|
182
171
|
|
|
@@ -228,7 +217,7 @@ If `from_error(...)` fails at runtime, the exception will propagate to FastAPI
|
|
|
228
217
|
### 🔄 Side Effects (`on_error`)
|
|
229
218
|
|
|
230
219
|
The `on_error` parameter in `rule(...)` allows specifying function to run when exception occurs, before response is
|
|
231
|
-
generated.
|
|
220
|
+
generated. The awaitable function will be awaited.
|
|
232
221
|
It doesn’t change the response status/body when it succeeds and is useful for:
|
|
233
222
|
|
|
234
223
|
- logging
|
|
@@ -301,7 +290,8 @@ When an error occurs, `fastapi-error-map` processes it as follows:
|
|
|
301
290
|
- If the status is `>= 500`, `default_server_error_translator` is used (if given)
|
|
302
291
|
- If none are set, the built-in one is used:
|
|
303
292
|
```raw
|
|
304
|
-
{ "error":
|
|
293
|
+
{ "error": str(err) } for 4xx;
|
|
294
|
+
{ "error": "Internal server error" } for 5xx
|
|
305
295
|
```
|
|
306
296
|
|
|
307
297
|
3. `on_error`:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
## FastAPI Error Map
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/py/fastapi-error-map)
|
|
4
|
+

|
|
5
5
|
[](https://codecov.io/gh/ivan-borovets/fastapi-error-map)
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
Elegant per-endpoint error handling for FastAPI that keeps OpenAPI
|
|
9
|
+
Elegant per-endpoint error handling for FastAPI that keeps OpenAPI in sync.
|
|
10
10
|
|
|
11
11
|
### 📦 Installation
|
|
12
12
|
|
|
@@ -87,6 +87,9 @@ See the [example in the 🚀 Quickstart](#-quickstart).
|
|
|
87
87
|
Error handling rules are defined directly in the route declaration of an `ErrorAwareRouter`, using the `error_map`
|
|
88
88
|
parameter.
|
|
89
89
|
|
|
90
|
+
> [!NOTE]
|
|
91
|
+
> `error_map` handles HTTP error responses only — statuses must be 4xx or 5xx.
|
|
92
|
+
|
|
90
93
|
There are two ways to do it:
|
|
91
94
|
|
|
92
95
|
#### 🔸 Short Form
|
|
@@ -132,10 +135,10 @@ error_map = {
|
|
|
132
135
|
|
|
133
136
|
Parameters of `rule(...)`, * — required:
|
|
134
137
|
|
|
135
|
-
- `status`* — HTTP status code to return (e.g. `404`, `409`, `422`)
|
|
136
|
-
- `translator` — object that converts an exception into
|
|
137
|
-
`{ "error": "
|
|
138
|
-
- `on_error` — function to call when an exception occurs (e.g. logging or alerting)
|
|
138
|
+
- `status`* — HTTP status code to return (must be 4xx or 5xx; e.g. `404`, `409`, `422`)
|
|
139
|
+
- `translator` — object that converts an exception into serializable payload. If not provided, the default one is used
|
|
140
|
+
(`{ "error": str(err) }` for 4xx; `{ "error": "Internal server error" }` for 5xx).
|
|
141
|
+
- `on_error` — function to call when an exception occurs (e.g. logging or alerting). Can be awaitable
|
|
139
142
|
|
|
140
143
|
#### 🧩 Matching semantics
|
|
141
144
|
|
|
@@ -187,7 +190,7 @@ If `from_error(...)` fails at runtime, the exception will propagate to FastAPI
|
|
|
187
190
|
### 🔄 Side Effects (`on_error`)
|
|
188
191
|
|
|
189
192
|
The `on_error` parameter in `rule(...)` allows specifying function to run when exception occurs, before response is
|
|
190
|
-
generated.
|
|
193
|
+
generated. The awaitable function will be awaited.
|
|
191
194
|
It doesn’t change the response status/body when it succeeds and is useful for:
|
|
192
195
|
|
|
193
196
|
- logging
|
|
@@ -260,7 +263,8 @@ When an error occurs, `fastapi-error-map` processes it as follows:
|
|
|
260
263
|
- If the status is `>= 500`, `default_server_error_translator` is used (if given)
|
|
261
264
|
- If none are set, the built-in one is used:
|
|
262
265
|
```raw
|
|
263
|
-
{ "error":
|
|
266
|
+
{ "error": str(err) } for 4xx;
|
|
267
|
+
{ "error": "Internal server error" } for 5xx
|
|
264
268
|
```
|
|
265
269
|
|
|
266
270
|
3. `on_error`:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
|
|
3
4
|
from fastapi_error_map.translators import ErrorTranslator
|
|
@@ -25,5 +26,6 @@ class OutOfStockTranslator(ErrorTranslator[ErrorResponseModel]):
|
|
|
25
26
|
return ErrorResponseModel
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
def notify(err: Exception) -> None:
|
|
29
|
+
async def notify(err: Exception) -> None:
|
|
30
|
+
await asyncio.sleep(0)
|
|
29
31
|
print("Notified admin:", err)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
from collections.abc import Awaitable
|
|
2
3
|
from functools import wraps
|
|
3
|
-
from typing import Any, Callable, Optional
|
|
4
|
+
from typing import Any, Callable, Optional, Union
|
|
4
5
|
|
|
5
6
|
from fastapi.encoders import jsonable_encoder
|
|
6
7
|
from fastapi.responses import ORJSONResponse
|
|
8
|
+
from starlette.concurrency import run_in_threadpool
|
|
7
9
|
|
|
8
10
|
from fastapi_error_map.rules import ErrorMap, resolve_rule_for_error
|
|
9
11
|
from fastapi_error_map.translators import ErrorTranslator
|
|
@@ -16,7 +18,7 @@ def wrap_with_error_handling(
|
|
|
16
18
|
warn_on_unmapped: bool,
|
|
17
19
|
default_client_error_translator: ErrorTranslator[Any],
|
|
18
20
|
default_server_error_translator: ErrorTranslator[Any],
|
|
19
|
-
default_on_error: Optional[Callable[[Exception], None]],
|
|
21
|
+
default_on_error: Optional[Callable[[Exception], Union[Awaitable[None], None]]],
|
|
20
22
|
) -> Callable[..., Any]:
|
|
21
23
|
is_coro = inspect.iscoroutinefunction(func)
|
|
22
24
|
|
|
@@ -27,7 +29,7 @@ def wrap_with_error_handling(
|
|
|
27
29
|
return await func(*args, **kwargs)
|
|
28
30
|
return func(*args, **kwargs)
|
|
29
31
|
except Exception as error:
|
|
30
|
-
return handle_with_error_map(
|
|
32
|
+
return await handle_with_error_map(
|
|
31
33
|
error=error,
|
|
32
34
|
error_map=error_map,
|
|
33
35
|
warn_on_unmapped=warn_on_unmapped,
|
|
@@ -39,14 +41,14 @@ def wrap_with_error_handling(
|
|
|
39
41
|
return wrapped
|
|
40
42
|
|
|
41
43
|
|
|
42
|
-
def handle_with_error_map(
|
|
44
|
+
async def handle_with_error_map(
|
|
43
45
|
*,
|
|
44
46
|
error: Exception,
|
|
45
47
|
error_map: ErrorMap,
|
|
46
48
|
warn_on_unmapped: bool,
|
|
47
49
|
default_client_error_translator: ErrorTranslator[Any],
|
|
48
50
|
default_server_error_translator: ErrorTranslator[Any],
|
|
49
|
-
default_on_error: Optional[Callable[[Exception], None]],
|
|
51
|
+
default_on_error: Optional[Callable[[Exception], Union[Awaitable[None], None]]],
|
|
50
52
|
) -> ORJSONResponse:
|
|
51
53
|
try:
|
|
52
54
|
rule = resolve_rule_for_error(
|
|
@@ -63,7 +65,12 @@ def handle_with_error_map(
|
|
|
63
65
|
raise original.with_traceback(original.__traceback__) from None
|
|
64
66
|
|
|
65
67
|
if rule.on_error is not None:
|
|
66
|
-
rule.on_error
|
|
68
|
+
if inspect.iscoroutinefunction(rule.on_error):
|
|
69
|
+
await rule.on_error(error)
|
|
70
|
+
else:
|
|
71
|
+
result = await run_in_threadpool(rule.on_error, error)
|
|
72
|
+
if inspect.isawaitable(result):
|
|
73
|
+
await result
|
|
67
74
|
|
|
68
75
|
content = rule.translator.from_error(error)
|
|
69
76
|
return ORJSONResponse(
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from typing import Any, Union
|
|
2
2
|
|
|
3
3
|
from fastapi_error_map.rules import ErrorMap
|
|
4
|
-
from fastapi_error_map.translator_policy import
|
|
4
|
+
from fastapi_error_map.translator_policy import (
|
|
5
|
+
pick_translator_for_status,
|
|
6
|
+
validate_error_status,
|
|
7
|
+
)
|
|
5
8
|
from fastapi_error_map.translators import ErrorTranslator
|
|
6
9
|
|
|
7
10
|
|
|
@@ -21,6 +24,8 @@ def build_openapi_responses(
|
|
|
21
24
|
status = value.status
|
|
22
25
|
translator = value.translator
|
|
23
26
|
|
|
27
|
+
validate_error_status(status)
|
|
28
|
+
|
|
24
29
|
if translator is None:
|
|
25
30
|
translator = pick_translator_for_status(
|
|
26
31
|
status=status,
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
from collections.abc import Awaitable
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from typing import Any, Callable, Optional, Union
|
|
3
4
|
|
|
4
|
-
from fastapi_error_map.translator_policy import
|
|
5
|
+
from fastapi_error_map.translator_policy import (
|
|
6
|
+
pick_translator_for_status,
|
|
7
|
+
validate_error_status,
|
|
8
|
+
)
|
|
5
9
|
from fastapi_error_map.translators import ErrorTranslator
|
|
6
10
|
|
|
7
11
|
|
|
@@ -9,14 +13,14 @@ from fastapi_error_map.translators import ErrorTranslator
|
|
|
9
13
|
class Rule:
|
|
10
14
|
status: int
|
|
11
15
|
translator: Optional[ErrorTranslator[Any]]
|
|
12
|
-
on_error: Optional[Callable[[Exception], None]]
|
|
16
|
+
on_error: Optional[Callable[[Exception], Union[Awaitable[None], None]]]
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
def rule(
|
|
16
20
|
*,
|
|
17
21
|
status: int,
|
|
18
22
|
translator: Optional[ErrorTranslator[Any]] = None,
|
|
19
|
-
on_error: Optional[Callable[[Exception], None]] = None,
|
|
23
|
+
on_error: Optional[Callable[[Exception], Union[Awaitable[None], None]]] = None,
|
|
20
24
|
) -> Rule:
|
|
21
25
|
"""
|
|
22
26
|
Defines full error handling rule for use in `error_map`.
|
|
@@ -46,7 +50,7 @@ ErrorMap = dict[type[Exception], Union[int, Rule]]
|
|
|
46
50
|
class ResolvedRule:
|
|
47
51
|
status: int
|
|
48
52
|
translator: ErrorTranslator[Any]
|
|
49
|
-
on_error: Optional[Callable[[Exception], None]]
|
|
53
|
+
on_error: Optional[Callable[[Exception], Union[Awaitable[None], None]]]
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
def resolve_rule_for_error(
|
|
@@ -55,7 +59,9 @@ def resolve_rule_for_error(
|
|
|
55
59
|
error_map: ErrorMap,
|
|
56
60
|
default_client_error_translator: ErrorTranslator[Any],
|
|
57
61
|
default_server_error_translator: ErrorTranslator[Any],
|
|
58
|
-
default_on_error: Optional[
|
|
62
|
+
default_on_error: Optional[
|
|
63
|
+
Callable[[Exception], Union[Awaitable[None], None]]
|
|
64
|
+
] = None,
|
|
59
65
|
) -> ResolvedRule:
|
|
60
66
|
try:
|
|
61
67
|
status_or_rule = error_map[type(error)]
|
|
@@ -71,6 +77,8 @@ def resolve_rule_for_error(
|
|
|
71
77
|
status_or_rule.on_error or default_on_error,
|
|
72
78
|
)
|
|
73
79
|
|
|
80
|
+
validate_error_status(status)
|
|
81
|
+
|
|
74
82
|
if translator is None:
|
|
75
83
|
translator = pick_translator_for_status(
|
|
76
84
|
status=status,
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from starlette import status as http_status
|
|
4
|
-
|
|
5
3
|
from fastapi_error_map.translators import ErrorTranslator
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
def is_server_error(status: int) -> bool:
|
|
9
|
-
return status
|
|
7
|
+
return status // 100 == 5
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def is_client_error(status: int) -> bool:
|
|
11
|
+
return status // 100 == 4
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def validate_error_status(status: int) -> None:
|
|
15
|
+
if not (is_client_error(status) or is_server_error(status)):
|
|
16
|
+
raise RuntimeError(f"Unsupported status for error_map: {status}. Use 4xx/5xx.")
|
|
10
17
|
|
|
11
18
|
|
|
12
19
|
def pick_translator_for_status(
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import nox
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@nox.session(python=None)
|
|
5
|
+
@nox.parametrize("fastapi", ["0.100.0", "latest"])
|
|
6
|
+
def compatibility(session, fastapi):
|
|
7
|
+
session.run("uv", "sync", "--locked", "--group", "test", "--active", external=True)
|
|
8
|
+
|
|
9
|
+
if fastapi == "latest":
|
|
10
|
+
session.run("uv", "add", "fastapi", "--active", external=True)
|
|
11
|
+
else:
|
|
12
|
+
session.run("uv", "add", f"fastapi=={fastapi}", "--active", external=True)
|
|
13
|
+
|
|
14
|
+
session.run("uv", "run", "--active", "make", "code.test", external=True)
|
|
@@ -7,8 +7,8 @@ sources = ["src"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "fastapi-error-map"
|
|
10
|
-
version = "0.9.
|
|
11
|
-
description = "Elegant per-endpoint error handling for FastAPI that keeps OpenAPI
|
|
10
|
+
version = "0.9.7"
|
|
11
|
+
description = "Elegant per-endpoint error handling for FastAPI that keeps OpenAPI in sync"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = { text = "Apache-2.0" }
|
|
14
14
|
authors = [
|
|
@@ -23,6 +23,7 @@ classifiers = [
|
|
|
23
23
|
"Programming Language :: Python :: 3.11",
|
|
24
24
|
"Programming Language :: Python :: 3.12",
|
|
25
25
|
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
26
27
|
"Topic :: Software Development :: Libraries",
|
|
27
28
|
"Typing :: Typed",
|
|
28
29
|
"Intended Audience :: Developers",
|
|
@@ -30,19 +31,15 @@ classifiers = [
|
|
|
30
31
|
"Framework :: FastAPI",
|
|
31
32
|
]
|
|
32
33
|
dependencies = [
|
|
33
|
-
"fastapi
|
|
34
|
-
"orjson
|
|
34
|
+
"fastapi==0.100.0",
|
|
35
|
+
"orjson==3.11.1",
|
|
35
36
|
]
|
|
36
|
-
|
|
37
|
-
[project.urls]
|
|
38
|
-
Homepage = "https://github.com/ivan-borovets/fastapi-error-map"
|
|
39
|
-
Repository = "https://github.com/ivan-borovets/fastapi-error-map"
|
|
40
|
-
|
|
41
|
-
[project.optional-dependencies]
|
|
37
|
+
[dependency-groups]
|
|
42
38
|
dev = [
|
|
43
39
|
"mypy==1.8.0",
|
|
44
40
|
"pre-commit==4.2.0",
|
|
45
41
|
"ruff==0.11.2",
|
|
42
|
+
{ include-group = "test" },
|
|
46
43
|
]
|
|
47
44
|
test = [
|
|
48
45
|
"coverage==7.10.1",
|
|
@@ -58,6 +55,15 @@ publish = [
|
|
|
58
55
|
examples = [
|
|
59
56
|
"uvicorn==0.35.0",
|
|
60
57
|
]
|
|
58
|
+
all = [
|
|
59
|
+
{ include-group = "dev" },
|
|
60
|
+
{ include-group = "publish" },
|
|
61
|
+
{ include-group = "examples" },
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[project.urls]
|
|
65
|
+
Homepage = "https://github.com/ivan-borovets/fastapi-error-map"
|
|
66
|
+
Repository = "https://github.com/ivan-borovets/fastapi-error-map"
|
|
61
67
|
|
|
62
68
|
[tool.coverage.report]
|
|
63
69
|
show_missing = true
|
|
@@ -179,6 +185,6 @@ split-on-trailing-comma = true
|
|
|
179
185
|
"src/fastapi_error_map/translators.py" = [
|
|
180
186
|
"ARG002", # unused-method-argument
|
|
181
187
|
]
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
188
|
+
"src/fastapi_error_map/translator_policy.py" = [
|
|
189
|
+
"PLR2004", # magic-value-comparison
|
|
190
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from collections.abc import AsyncIterator
|
|
2
|
+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from fastapi import FastAPI
|
|
7
|
+
from httpx import ASGITransport, AsyncClient
|
|
8
|
+
|
|
9
|
+
AsgiClientFactory = Callable[[FastAPI], AbstractAsyncContextManager[AsyncClient]]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def app() -> FastAPI:
|
|
14
|
+
return FastAPI()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def asgi_client_factory() -> AsgiClientFactory:
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def _make(app: FastAPI) -> AsyncIterator[AsyncClient]:
|
|
21
|
+
transport = ASGITransport(app=app)
|
|
22
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
23
|
+
yield client
|
|
24
|
+
|
|
25
|
+
return _make
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
2
3
|
import pytest
|
|
3
|
-
from httpx import ASGITransport
|
|
4
4
|
|
|
5
5
|
from examples.main import create_app
|
|
6
|
+
from tests.integration.conftest import AsgiClientFactory
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
import httpx
|
|
6
10
|
|
|
7
11
|
|
|
8
12
|
@pytest.mark.asyncio
|
|
9
|
-
async def test_check_stock(
|
|
13
|
+
async def test_check_stock(
|
|
14
|
+
asgi_client_factory: AsgiClientFactory,
|
|
15
|
+
capsys: pytest.CaptureFixture[str],
|
|
16
|
+
) -> None:
|
|
10
17
|
app = create_app()
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
|
19
|
+
async with asgi_client_factory(app) as client:
|
|
14
20
|
authz_err_response: httpx.Response = await client.get("/stock?user_id=0")
|
|
15
21
|
out_of_stock_err_response: httpx.Response = await client.get("/stock?user_id=1")
|
|
16
22
|
openapi_response: httpx.Response = await client.get("/openapi.json")
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
2
3
|
from unittest.mock import Mock
|
|
3
4
|
|
|
4
|
-
import httpx
|
|
5
5
|
import pytest
|
|
6
6
|
from fastapi import FastAPI
|
|
7
|
-
from httpx import ASGITransport
|
|
8
7
|
|
|
9
8
|
from fastapi_error_map import ErrorAwareRouter, rule
|
|
9
|
+
from tests.integration.conftest import AsgiClientFactory
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import httpx
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
class CustomError(Exception):
|
|
@@ -15,8 +18,11 @@ class CustomError(Exception):
|
|
|
15
18
|
|
|
16
19
|
@pytest.mark.asyncio
|
|
17
20
|
@pytest.mark.parametrize("method", ["get", "post", "put", "patch", "delete"])
|
|
18
|
-
async def test_error_aware_router_routes(
|
|
19
|
-
|
|
21
|
+
async def test_error_aware_router_routes(
|
|
22
|
+
method: str,
|
|
23
|
+
app: FastAPI,
|
|
24
|
+
asgi_client_factory: AsgiClientFactory,
|
|
25
|
+
) -> None:
|
|
20
26
|
router = ErrorAwareRouter()
|
|
21
27
|
router_path = "/fail"
|
|
22
28
|
error_message = "This is a test"
|
|
@@ -35,8 +41,7 @@ async def test_error_aware_router_routes(method: str) -> None:
|
|
|
35
41
|
)(failing_endpoint)
|
|
36
42
|
app.include_router(router)
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
async with httpx.AsyncClient(transport=transport, base_url="http://test") as client:
|
|
44
|
+
async with asgi_client_factory(app) as client:
|
|
40
45
|
response: httpx.Response = await getattr(client, method)(router_path)
|
|
41
46
|
openapi_response: httpx.Response = await client.get("/openapi.json")
|
|
42
47
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
|
|
7
|
+
from fastapi_error_map import ErrorAwareRouter, rule
|
|
8
|
+
from tests.integration.conftest import AsgiClientFactory
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def has_running_loop_in_this_thread() -> bool:
|
|
15
|
+
try:
|
|
16
|
+
asyncio.get_running_loop()
|
|
17
|
+
return True
|
|
18
|
+
except RuntimeError:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.asyncio
|
|
23
|
+
async def test_fastapi_sync_handler_runs_in_threadpool(
|
|
24
|
+
app: FastAPI,
|
|
25
|
+
asgi_client_factory: AsgiClientFactory,
|
|
26
|
+
) -> None:
|
|
27
|
+
@app.get("/")
|
|
28
|
+
def index():
|
|
29
|
+
return {"in_loop": has_running_loop_in_this_thread()}
|
|
30
|
+
|
|
31
|
+
async with asgi_client_factory(app) as client:
|
|
32
|
+
index_response: httpx.Response = await client.get("/")
|
|
33
|
+
|
|
34
|
+
assert index_response.status_code == 200
|
|
35
|
+
assert index_response.json() == {"in_loop": False}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.asyncio
|
|
39
|
+
async def test_error_aware_router_sync_handler_runs_in_threadpool(
|
|
40
|
+
app: FastAPI,
|
|
41
|
+
asgi_client_factory: AsgiClientFactory,
|
|
42
|
+
) -> None:
|
|
43
|
+
router = ErrorAwareRouter()
|
|
44
|
+
|
|
45
|
+
@app.get("/")
|
|
46
|
+
def index():
|
|
47
|
+
return {"in_loop": has_running_loop_in_this_thread()}
|
|
48
|
+
|
|
49
|
+
app.include_router(router)
|
|
50
|
+
|
|
51
|
+
async with asgi_client_factory(app) as client:
|
|
52
|
+
index_response: httpx.Response = await client.get("/")
|
|
53
|
+
|
|
54
|
+
assert index_response.status_code == 200
|
|
55
|
+
assert index_response.json() == {"in_loop": False}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_error_aware_router_sync_on_error_runs_in_threadpool(
|
|
60
|
+
app: FastAPI,
|
|
61
|
+
asgi_client_factory: AsgiClientFactory,
|
|
62
|
+
) -> None:
|
|
63
|
+
router = ErrorAwareRouter()
|
|
64
|
+
seen: dict[str, bool] = {}
|
|
65
|
+
|
|
66
|
+
def on_error(_: Exception) -> None:
|
|
67
|
+
seen["in_loop"] = has_running_loop_in_this_thread()
|
|
68
|
+
|
|
69
|
+
@router.get("/err", error_map={ValueError: rule(status=400, on_error=on_error)})
|
|
70
|
+
def index():
|
|
71
|
+
raise ValueError
|
|
72
|
+
|
|
73
|
+
app.include_router(router)
|
|
74
|
+
|
|
75
|
+
async with asgi_client_factory(app) as client:
|
|
76
|
+
index_response: httpx.Response = await client.get("/err")
|
|
77
|
+
|
|
78
|
+
assert index_response.status_code == 400
|
|
79
|
+
assert seen["in_loop"] is False
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from
|
|
3
|
-
from
|
|
2
|
+
from collections.abc import Awaitable
|
|
3
|
+
from functools import partial
|
|
4
|
+
from typing import NoReturn, cast
|
|
5
|
+
from unittest.mock import AsyncMock, Mock
|
|
4
6
|
|
|
5
7
|
import pytest
|
|
6
8
|
from starlette.responses import JSONResponse
|
|
7
9
|
|
|
8
10
|
from fastapi_error_map import rule
|
|
9
|
-
from fastapi_error_map.error_handling import
|
|
11
|
+
from fastapi_error_map.error_handling import (
|
|
12
|
+
handle_with_error_map,
|
|
13
|
+
wrap_with_error_handling,
|
|
14
|
+
)
|
|
10
15
|
from fastapi_error_map.translators import (
|
|
11
16
|
DefaultClientErrorTranslator,
|
|
12
17
|
DefaultServerErrorTranslator,
|
|
@@ -126,3 +131,97 @@ async def test_wrap_calls_on_error_with_error_if_defined() -> None:
|
|
|
126
131
|
|
|
127
132
|
mock_on_error.assert_called_once()
|
|
128
133
|
assert isinstance(mock_on_error.call_args[0][0], ValidationError)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.asyncio
|
|
137
|
+
async def test_handles_sync_on_error_from_map() -> None:
|
|
138
|
+
mock_on_error = Mock()
|
|
139
|
+
error = ValidationError()
|
|
140
|
+
|
|
141
|
+
sut = await handle_with_error_map(
|
|
142
|
+
error=error,
|
|
143
|
+
error_map={ValidationError: rule(status=422, on_error=mock_on_error)},
|
|
144
|
+
warn_on_unmapped=True,
|
|
145
|
+
default_client_error_translator=DefaultClientErrorTranslator(),
|
|
146
|
+
default_server_error_translator=DefaultServerErrorTranslator(),
|
|
147
|
+
default_on_error=None,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
assert sut.status_code == 422
|
|
151
|
+
mock_on_error.assert_called_once_with(error)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.mark.asyncio
|
|
155
|
+
async def test_handles_default_on_error_from_defaults() -> None:
|
|
156
|
+
mock_on_error = AsyncMock()
|
|
157
|
+
error = ValidationError()
|
|
158
|
+
|
|
159
|
+
sut = await handle_with_error_map(
|
|
160
|
+
error=error,
|
|
161
|
+
error_map={ValidationError: 422},
|
|
162
|
+
warn_on_unmapped=True,
|
|
163
|
+
default_client_error_translator=DefaultClientErrorTranslator(),
|
|
164
|
+
default_server_error_translator=DefaultServerErrorTranslator(),
|
|
165
|
+
default_on_error=mock_on_error,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
assert sut.status_code == 422
|
|
169
|
+
mock_on_error.assert_awaited_once_with(error)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@pytest.mark.asyncio
|
|
173
|
+
async def test_handles_async_on_error() -> None:
|
|
174
|
+
mock_on_error = AsyncMock()
|
|
175
|
+
error = ValidationError()
|
|
176
|
+
|
|
177
|
+
sut = await handle_with_error_map(
|
|
178
|
+
error=error,
|
|
179
|
+
error_map={ValidationError: rule(status=422, on_error=mock_on_error)},
|
|
180
|
+
warn_on_unmapped=True,
|
|
181
|
+
default_client_error_translator=DefaultClientErrorTranslator(),
|
|
182
|
+
default_server_error_translator=DefaultServerErrorTranslator(),
|
|
183
|
+
default_on_error=None,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
assert sut.status_code == 422
|
|
187
|
+
mock_on_error.assert_awaited_once_with(error)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.mark.asyncio
|
|
191
|
+
async def test_handles_async_on_error_partial() -> None:
|
|
192
|
+
wrapped = AsyncMock()
|
|
193
|
+
mock_on_error = partial(wrapped)
|
|
194
|
+
error = ValidationError()
|
|
195
|
+
|
|
196
|
+
sut = await handle_with_error_map(
|
|
197
|
+
error=error,
|
|
198
|
+
error_map={ValidationError: rule(status=422, on_error=mock_on_error)},
|
|
199
|
+
warn_on_unmapped=True,
|
|
200
|
+
default_client_error_translator=DefaultClientErrorTranslator(),
|
|
201
|
+
default_server_error_translator=DefaultServerErrorTranslator(),
|
|
202
|
+
default_on_error=None,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
assert sut.status_code == 422
|
|
206
|
+
wrapped.assert_awaited_once_with(error)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_handles_sync_on_error_returning_awaitable() -> None:
|
|
211
|
+
coro = AsyncMock()
|
|
212
|
+
error = ValidationError()
|
|
213
|
+
|
|
214
|
+
def on_error(err: Exception) -> Awaitable[None]:
|
|
215
|
+
return cast("Awaitable[None]", coro(err))
|
|
216
|
+
|
|
217
|
+
sut = await handle_with_error_map(
|
|
218
|
+
error=error,
|
|
219
|
+
error_map={ValidationError: rule(status=422, on_error=on_error)},
|
|
220
|
+
warn_on_unmapped=True,
|
|
221
|
+
default_client_error_translator=DefaultClientErrorTranslator(),
|
|
222
|
+
default_server_error_translator=DefaultServerErrorTranslator(),
|
|
223
|
+
default_on_error=None,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
assert sut.status_code == 422
|
|
227
|
+
coro.assert_awaited_once_with(error)
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING
|
|
2
2
|
|
|
3
|
+
import pytest
|
|
4
|
+
from starlette import status
|
|
5
|
+
|
|
3
6
|
from fastapi_error_map.openapi import build_openapi_responses
|
|
4
7
|
from fastapi_error_map.rules import rule
|
|
5
8
|
from tests.unit.error_stubs import DatabaseError, ValidationError
|
|
@@ -63,3 +66,20 @@ def test_builds_response_with_custom_translator() -> None:
|
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
assert responses == {400: {"model": dict}}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.parametrize(
|
|
72
|
+
"bad_status",
|
|
73
|
+
[
|
|
74
|
+
status.HTTP_200_OK,
|
|
75
|
+
status.HTTP_300_MULTIPLE_CHOICES,
|
|
76
|
+
status.WS_1000_NORMAL_CLOSURE,
|
|
77
|
+
],
|
|
78
|
+
)
|
|
79
|
+
def test_rejects_non_error_status_codes(bad_status: int) -> None:
|
|
80
|
+
with pytest.raises(RuntimeError):
|
|
81
|
+
build_openapi_responses(
|
|
82
|
+
error_map={ValidationError: bad_status},
|
|
83
|
+
default_client_error_translator=DummyClientErrorTranslator(),
|
|
84
|
+
default_server_error_translator=DummyServerErrorTranslator(),
|
|
85
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Union
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
|
+
from starlette import status
|
|
4
5
|
|
|
5
6
|
from fastapi_error_map.rules import Rule, resolve_rule_for_error, rule
|
|
6
7
|
from tests.unit.error_stubs import DatabaseError, UnknownError, ValidationError
|
|
@@ -55,6 +56,25 @@ def test_resolves_status_code_with_server_error_translator(
|
|
|
55
56
|
assert isinstance(sut.translator, DummyServerErrorTranslator)
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
"bad_status",
|
|
61
|
+
[
|
|
62
|
+
status.HTTP_200_OK,
|
|
63
|
+
status.HTTP_300_MULTIPLE_CHOICES,
|
|
64
|
+
status.WS_1000_NORMAL_CLOSURE,
|
|
65
|
+
],
|
|
66
|
+
)
|
|
67
|
+
def test_resolver_rejects_non_error_status_codes(bad_status: int) -> None:
|
|
68
|
+
with pytest.raises(RuntimeError):
|
|
69
|
+
resolve_rule_for_error(
|
|
70
|
+
error=ValidationError(),
|
|
71
|
+
error_map={ValidationError: bad_status},
|
|
72
|
+
default_client_error_translator=DummyClientErrorTranslator(),
|
|
73
|
+
default_server_error_translator=DummyServerErrorTranslator(),
|
|
74
|
+
default_on_error=None,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
58
78
|
def test_does_not_override_explicit_translator() -> None:
|
|
59
79
|
custom_translator = CustomTranslator()
|
|
60
80
|
|
|
@@ -377,31 +377,50 @@ wheels = [
|
|
|
377
377
|
|
|
378
378
|
[[package]]
|
|
379
379
|
name = "fastapi"
|
|
380
|
-
version = "0.
|
|
380
|
+
version = "0.100.0"
|
|
381
381
|
source = { registry = "https://pypi.org/simple" }
|
|
382
382
|
dependencies = [
|
|
383
383
|
{ name = "pydantic" },
|
|
384
384
|
{ name = "starlette" },
|
|
385
385
|
{ name = "typing-extensions" },
|
|
386
386
|
]
|
|
387
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
387
|
+
sdist = { url = "https://files.pythonhosted.org/packages/65/e0/f9d77b3a1569e2217abf260b0b1462401736973d1c5d3d335f6f2009daa2/fastapi-0.100.0.tar.gz", hash = "sha256:acb5f941ea8215663283c10018323ba7ea737c571b67fc7e88e9469c7eb1d12e", size = 10440814, upload-time = "2023-07-07T17:33:19.001Z" }
|
|
388
388
|
wheels = [
|
|
389
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
389
|
+
{ url = "https://files.pythonhosted.org/packages/49/f5/048206823aae9b3a4a61ba6b7a1dd1de36bd4c0a0283f2efb1f1f2289c8a/fastapi-0.100.0-py3-none-any.whl", hash = "sha256:271662daf986da8fa98dc2b7c7f61c4abdfdccfb4786d79ed8b2878f172c6d5f", size = 65736, upload-time = "2023-07-07T17:33:17.001Z" },
|
|
390
390
|
]
|
|
391
391
|
|
|
392
392
|
[[package]]
|
|
393
393
|
name = "fastapi-error-map"
|
|
394
|
-
version = "0.9.
|
|
394
|
+
version = "0.9.7"
|
|
395
395
|
source = { editable = "." }
|
|
396
396
|
dependencies = [
|
|
397
397
|
{ name = "fastapi" },
|
|
398
398
|
{ name = "orjson" },
|
|
399
399
|
]
|
|
400
400
|
|
|
401
|
-
[package.
|
|
401
|
+
[package.dev-dependencies]
|
|
402
|
+
all = [
|
|
403
|
+
{ name = "coverage" },
|
|
404
|
+
{ name = "hatch" },
|
|
405
|
+
{ name = "httpx" },
|
|
406
|
+
{ name = "mypy" },
|
|
407
|
+
{ name = "nox" },
|
|
408
|
+
{ name = "pre-commit" },
|
|
409
|
+
{ name = "pytest" },
|
|
410
|
+
{ name = "pytest-asyncio" },
|
|
411
|
+
{ name = "pytest-cov" },
|
|
412
|
+
{ name = "ruff" },
|
|
413
|
+
{ name = "uvicorn" },
|
|
414
|
+
]
|
|
402
415
|
dev = [
|
|
416
|
+
{ name = "coverage" },
|
|
417
|
+
{ name = "httpx" },
|
|
403
418
|
{ name = "mypy" },
|
|
419
|
+
{ name = "nox" },
|
|
404
420
|
{ name = "pre-commit" },
|
|
421
|
+
{ name = "pytest" },
|
|
422
|
+
{ name = "pytest-asyncio" },
|
|
423
|
+
{ name = "pytest-cov" },
|
|
405
424
|
{ name = "ruff" },
|
|
406
425
|
]
|
|
407
426
|
examples = [
|
|
@@ -421,21 +440,45 @@ test = [
|
|
|
421
440
|
|
|
422
441
|
[package.metadata]
|
|
423
442
|
requires-dist = [
|
|
424
|
-
{ name = "
|
|
425
|
-
{ name = "
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
{ name = "
|
|
431
|
-
{ name = "
|
|
432
|
-
{ name = "
|
|
433
|
-
{ name = "
|
|
434
|
-
{ name = "
|
|
435
|
-
{ name = "
|
|
436
|
-
{ name = "
|
|
437
|
-
|
|
438
|
-
|
|
443
|
+
{ name = "fastapi", specifier = "==0.100.0" },
|
|
444
|
+
{ name = "orjson", specifier = "==3.11.1" },
|
|
445
|
+
]
|
|
446
|
+
|
|
447
|
+
[package.metadata.requires-dev]
|
|
448
|
+
all = [
|
|
449
|
+
{ name = "coverage", specifier = "==7.10.1" },
|
|
450
|
+
{ name = "hatch", specifier = "==1.14.1" },
|
|
451
|
+
{ name = "httpx", specifier = "==0.28.1" },
|
|
452
|
+
{ name = "mypy", specifier = "==1.8.0" },
|
|
453
|
+
{ name = "nox", specifier = "==2025.5.1" },
|
|
454
|
+
{ name = "pre-commit", specifier = "==4.2.0" },
|
|
455
|
+
{ name = "pytest", specifier = "==8.4.1" },
|
|
456
|
+
{ name = "pytest-asyncio", specifier = "==1.1.0" },
|
|
457
|
+
{ name = "pytest-cov", specifier = "==6.2.1" },
|
|
458
|
+
{ name = "ruff", specifier = "==0.11.2" },
|
|
459
|
+
{ name = "uvicorn", specifier = "==0.35.0" },
|
|
460
|
+
]
|
|
461
|
+
dev = [
|
|
462
|
+
{ name = "coverage", specifier = "==7.10.1" },
|
|
463
|
+
{ name = "httpx", specifier = "==0.28.1" },
|
|
464
|
+
{ name = "mypy", specifier = "==1.8.0" },
|
|
465
|
+
{ name = "nox", specifier = "==2025.5.1" },
|
|
466
|
+
{ name = "pre-commit", specifier = "==4.2.0" },
|
|
467
|
+
{ name = "pytest", specifier = "==8.4.1" },
|
|
468
|
+
{ name = "pytest-asyncio", specifier = "==1.1.0" },
|
|
469
|
+
{ name = "pytest-cov", specifier = "==6.2.1" },
|
|
470
|
+
{ name = "ruff", specifier = "==0.11.2" },
|
|
471
|
+
]
|
|
472
|
+
examples = [{ name = "uvicorn", specifier = "==0.35.0" }]
|
|
473
|
+
publish = [{ name = "hatch", specifier = "==1.14.1" }]
|
|
474
|
+
test = [
|
|
475
|
+
{ name = "coverage", specifier = "==7.10.1" },
|
|
476
|
+
{ name = "httpx", specifier = "==0.28.1" },
|
|
477
|
+
{ name = "nox", specifier = "==2025.5.1" },
|
|
478
|
+
{ name = "pytest", specifier = "==8.4.1" },
|
|
479
|
+
{ name = "pytest-asyncio", specifier = "==1.1.0" },
|
|
480
|
+
{ name = "pytest-cov", specifier = "==6.2.1" },
|
|
481
|
+
]
|
|
439
482
|
|
|
440
483
|
[[package]]
|
|
441
484
|
name = "filelock"
|
|
@@ -1225,15 +1268,15 @@ wheels = [
|
|
|
1225
1268
|
|
|
1226
1269
|
[[package]]
|
|
1227
1270
|
name = "starlette"
|
|
1228
|
-
version = "0.
|
|
1271
|
+
version = "0.27.0"
|
|
1229
1272
|
source = { registry = "https://pypi.org/simple" }
|
|
1230
1273
|
dependencies = [
|
|
1231
1274
|
{ name = "anyio" },
|
|
1232
|
-
{ name = "typing-extensions", marker = "python_full_version < '3.
|
|
1275
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
|
|
1233
1276
|
]
|
|
1234
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1277
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/68/559bed5484e746f1ab2ebbe22312f2c25ec62e4b534916d41a8c21147bf8/starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75", size = 51394, upload-time = "2023-05-16T10:59:56.286Z" }
|
|
1235
1278
|
wheels = [
|
|
1236
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1279
|
+
{ url = "https://files.pythonhosted.org/packages/58/f8/e2cca22387965584a409795913b774235752be4176d276714e15e1a58884/starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91", size = 66978, upload-time = "2023-05-16T10:59:53.927Z" },
|
|
1237
1280
|
]
|
|
1238
1281
|
|
|
1239
1282
|
[[package]]
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import nox
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
@nox.session(python=None)
|
|
5
|
-
@nox.parametrize("fastapi", ["0.100.0", "latest"])
|
|
6
|
-
def compatibility(session, fastapi):
|
|
7
|
-
if fastapi == "latest":
|
|
8
|
-
session.run("uv", "pip", "install", "fastapi", external=True)
|
|
9
|
-
else:
|
|
10
|
-
session.run("uv", "pip", "install", f"fastapi=={fastapi}", external=True)
|
|
11
|
-
|
|
12
|
-
session.run("uv", "pip", "install", "-e", ".[test]", external=True)
|
|
13
|
-
session.run("make", "code.test", external=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|