fastapi-error-map 0.9.2__tar.gz → 0.9.3__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.2 → fastapi_error_map-0.9.3}/PKG-INFO +30 -5
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/README.md +29 -4
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/fastapi_error_map/error_handling.py +5 -2
- fastapi_error_map-0.9.3/fastapi_error_map/openapi.py +34 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/fastapi_error_map/routing.py +5 -1
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/fastapi_error_map/rules.py +8 -6
- fastapi_error_map-0.9.3/fastapi_error_map/translator_policy.py +22 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/fastapi_error_map/translators.py +1 -1
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/pyproject.toml +1 -1
- fastapi_error_map-0.9.3/tests/unit/test_openapi.py +65 -0
- fastapi_error_map-0.9.3/tests/unit/translator_stubs.py +40 -0
- fastapi_error_map-0.9.2/fastapi_error_map/openapi.py +0 -26
- fastapi_error_map-0.9.2/tests/unit/test_openapi.py +0 -48
- fastapi_error_map-0.9.2/tests/unit/translator_stubs.py +0 -28
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/.github/workflows/ci.yaml +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/.github/workflows/test-compatibility.yaml +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/.gitignore +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/.pre-commit-config.yaml +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/LICENSE +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/Makefile +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/docs/example-openapi.png +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/examples/__init__.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/examples/errors.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/examples/main.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/fastapi_error_map/__init__.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/fastapi_error_map/py.typed +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/noxfile.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/__init__.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/integration/__init__.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/integration/test_example.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/integration/test_routing.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/unit/__init__.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/unit/error_stubs.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/unit/test_error_handling.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/unit/test_rules.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/tests/unit/test_translators.py +0 -0
- {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-error-map
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Elegant per-endpoint error handling in FastAPI with OpenAPI schema generation
|
|
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
|
|
@@ -41,8 +41,9 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
## FastAPI Error Map
|
|
43
43
|
|
|
44
|
-
[](https://badge.fury.io/py/fastapi-error-map)
|
|
45
45
|

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

|
|
47
48
|

|
|
48
49
|
|
|
@@ -122,7 +123,7 @@ there's no single source of truth.
|
|
|
122
123
|
`fastapi-error-map` solves this by letting you define error handling rules right in the route declaration.
|
|
123
124
|
See the [example in the 🚀 Quickstart](#-quickstart).
|
|
124
125
|
|
|
125
|
-
### ⚙
|
|
126
|
+
### ⚙ Using `error_map`
|
|
126
127
|
|
|
127
128
|
Error handling rules are defined directly in the route declaration of an `ErrorAwareRouter`, using the `error_map`
|
|
128
129
|
parameter.
|
|
@@ -151,7 +152,7 @@ In both cases, the default translator is used, which returns JSON like:
|
|
|
151
152
|
|
|
152
153
|
```json
|
|
153
154
|
{
|
|
154
|
-
"error": "
|
|
155
|
+
"error": "..."
|
|
155
156
|
}
|
|
156
157
|
```
|
|
157
158
|
|
|
@@ -177,6 +178,13 @@ Parameters of `rule(...)`, * — required:
|
|
|
177
178
|
`{ "error": "..." }`)
|
|
178
179
|
- `on_error` — function to call when an exception occurs (e.g. logging or alerting)
|
|
179
180
|
|
|
181
|
+
#### 🧩 Matching semantics
|
|
182
|
+
|
|
183
|
+
`error_map` matches **exact** exception types only (no inheritance).
|
|
184
|
+
If you map `BaseError` and raise `ChildError(BaseError)`, the rule won’t apply.
|
|
185
|
+
This is by design to keep routing explicit.
|
|
186
|
+
If there’s demand, inheritance-based resolving may be added later as an opt-in.
|
|
187
|
+
|
|
180
188
|
### 🧰 Custom Translators
|
|
181
189
|
|
|
182
190
|
If you want to change the error response format, define your own `translator` — object that implements `ErrorTranslator`
|
|
@@ -212,11 +220,16 @@ class MyTranslator(ErrorTranslator[MyErrorResponse]):
|
|
|
212
220
|
|
|
213
221
|
```
|
|
214
222
|
|
|
223
|
+
#### ⚠️ Translator robustness
|
|
224
|
+
|
|
225
|
+
Custom translators should not raise exceptions.
|
|
226
|
+
If `from_error(...)` fails at runtime, the exception will propagate to FastAPI’s global handlers.
|
|
227
|
+
|
|
215
228
|
### 🔄 Side Effects (`on_error`)
|
|
216
229
|
|
|
217
230
|
The `on_error` parameter in `rule(...)` allows specifying function to run when exception occurs, before response is
|
|
218
231
|
generated.
|
|
219
|
-
It doesn
|
|
232
|
+
It doesn’t change the response status/body when it succeeds and is useful for:
|
|
220
233
|
|
|
221
234
|
- logging
|
|
222
235
|
- sending alerts
|
|
@@ -240,6 +253,13 @@ error_map = {
|
|
|
240
253
|
|
|
241
254
|
```
|
|
242
255
|
|
|
256
|
+
#### 🚨 Strict side effects
|
|
257
|
+
|
|
258
|
+
`on_error` is executed before the response is generated.
|
|
259
|
+
If it raises, the exception propagates and the request fails.
|
|
260
|
+
This is intentional to avoid hiding errors in side effects — they should be fixed rather than silently ignored.
|
|
261
|
+
Keep `on_error` robust or wrap it on your side if you prefer soft-fail logging.
|
|
262
|
+
|
|
243
263
|
### 🧠 Parameter Precedence
|
|
244
264
|
|
|
245
265
|
Error handling and schema generation in `fastapi-error-map` are fully driven by route-level arguments to `.get()`,
|
|
@@ -320,3 +340,8 @@ This behavior is not affected by `warn_on_unmapped`.
|
|
|
320
340
|
|
|
321
341
|
If the error is declared in `error_map`, `fastapi-error-map` handles the response itself — the global handler will not
|
|
322
342
|
be triggered.
|
|
343
|
+
|
|
344
|
+
### 📚 Examples
|
|
345
|
+
|
|
346
|
+
- Minimal usage: [`examples/`](./examples/main.py)
|
|
347
|
+
- More advanced usage: [Clean Architecture example app](https://github.com/ivan-borovets/fastapi-clean-example)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
## FastAPI Error Map
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/py/fastapi-error-map)
|
|
4
4
|

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

|
|
6
7
|

|
|
7
8
|
|
|
@@ -81,7 +82,7 @@ there's no single source of truth.
|
|
|
81
82
|
`fastapi-error-map` solves this by letting you define error handling rules right in the route declaration.
|
|
82
83
|
See the [example in the 🚀 Quickstart](#-quickstart).
|
|
83
84
|
|
|
84
|
-
### ⚙
|
|
85
|
+
### ⚙ Using `error_map`
|
|
85
86
|
|
|
86
87
|
Error handling rules are defined directly in the route declaration of an `ErrorAwareRouter`, using the `error_map`
|
|
87
88
|
parameter.
|
|
@@ -110,7 +111,7 @@ In both cases, the default translator is used, which returns JSON like:
|
|
|
110
111
|
|
|
111
112
|
```json
|
|
112
113
|
{
|
|
113
|
-
"error": "
|
|
114
|
+
"error": "..."
|
|
114
115
|
}
|
|
115
116
|
```
|
|
116
117
|
|
|
@@ -136,6 +137,13 @@ Parameters of `rule(...)`, * — required:
|
|
|
136
137
|
`{ "error": "..." }`)
|
|
137
138
|
- `on_error` — function to call when an exception occurs (e.g. logging or alerting)
|
|
138
139
|
|
|
140
|
+
#### 🧩 Matching semantics
|
|
141
|
+
|
|
142
|
+
`error_map` matches **exact** exception types only (no inheritance).
|
|
143
|
+
If you map `BaseError` and raise `ChildError(BaseError)`, the rule won’t apply.
|
|
144
|
+
This is by design to keep routing explicit.
|
|
145
|
+
If there’s demand, inheritance-based resolving may be added later as an opt-in.
|
|
146
|
+
|
|
139
147
|
### 🧰 Custom Translators
|
|
140
148
|
|
|
141
149
|
If you want to change the error response format, define your own `translator` — object that implements `ErrorTranslator`
|
|
@@ -171,11 +179,16 @@ class MyTranslator(ErrorTranslator[MyErrorResponse]):
|
|
|
171
179
|
|
|
172
180
|
```
|
|
173
181
|
|
|
182
|
+
#### ⚠️ Translator robustness
|
|
183
|
+
|
|
184
|
+
Custom translators should not raise exceptions.
|
|
185
|
+
If `from_error(...)` fails at runtime, the exception will propagate to FastAPI’s global handlers.
|
|
186
|
+
|
|
174
187
|
### 🔄 Side Effects (`on_error`)
|
|
175
188
|
|
|
176
189
|
The `on_error` parameter in `rule(...)` allows specifying function to run when exception occurs, before response is
|
|
177
190
|
generated.
|
|
178
|
-
It doesn
|
|
191
|
+
It doesn’t change the response status/body when it succeeds and is useful for:
|
|
179
192
|
|
|
180
193
|
- logging
|
|
181
194
|
- sending alerts
|
|
@@ -199,6 +212,13 @@ error_map = {
|
|
|
199
212
|
|
|
200
213
|
```
|
|
201
214
|
|
|
215
|
+
#### 🚨 Strict side effects
|
|
216
|
+
|
|
217
|
+
`on_error` is executed before the response is generated.
|
|
218
|
+
If it raises, the exception propagates and the request fails.
|
|
219
|
+
This is intentional to avoid hiding errors in side effects — they should be fixed rather than silently ignored.
|
|
220
|
+
Keep `on_error` robust or wrap it on your side if you prefer soft-fail logging.
|
|
221
|
+
|
|
202
222
|
### 🧠 Parameter Precedence
|
|
203
223
|
|
|
204
224
|
Error handling and schema generation in `fastapi-error-map` are fully driven by route-level arguments to `.get()`,
|
|
@@ -279,3 +299,8 @@ This behavior is not affected by `warn_on_unmapped`.
|
|
|
279
299
|
|
|
280
300
|
If the error is declared in `error_map`, `fastapi-error-map` handles the response itself — the global handler will not
|
|
281
301
|
be triggered.
|
|
302
|
+
|
|
303
|
+
### 📚 Examples
|
|
304
|
+
|
|
305
|
+
- Minimal usage: [`examples/`](./examples/main.py)
|
|
306
|
+
- More advanced usage: [Clean Architecture example app](https://github.com/ivan-borovets/fastapi-clean-example)
|
|
@@ -18,10 +18,12 @@ def wrap_with_error_handling(
|
|
|
18
18
|
default_server_error_translator: ErrorTranslator[Any],
|
|
19
19
|
default_on_error: Optional[Callable[[Exception], None]],
|
|
20
20
|
) -> Callable[..., Any]:
|
|
21
|
+
is_coro = inspect.iscoroutinefunction(func)
|
|
22
|
+
|
|
21
23
|
@wraps(func)
|
|
22
24
|
async def wrapped(*args: Any, **kwargs: Any) -> Any:
|
|
23
25
|
try:
|
|
24
|
-
if
|
|
26
|
+
if is_coro:
|
|
25
27
|
return await func(*args, **kwargs)
|
|
26
28
|
return func(*args, **kwargs)
|
|
27
29
|
except Exception as error:
|
|
@@ -57,7 +59,8 @@ def handle_with_error_map(
|
|
|
57
59
|
except RuntimeError as exc:
|
|
58
60
|
if warn_on_unmapped:
|
|
59
61
|
raise
|
|
60
|
-
|
|
62
|
+
original = exc.__cause__ or exc
|
|
63
|
+
raise original.with_traceback(original.__traceback__) from None
|
|
61
64
|
|
|
62
65
|
if rule.on_error is not None:
|
|
63
66
|
rule.on_error(error)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import Any, Union
|
|
2
|
+
|
|
3
|
+
from fastapi_error_map.rules import ErrorMap
|
|
4
|
+
from fastapi_error_map.translator_policy import pick_translator_for_status
|
|
5
|
+
from fastapi_error_map.translators import ErrorTranslator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_openapi_responses(
|
|
9
|
+
*,
|
|
10
|
+
error_map: ErrorMap,
|
|
11
|
+
default_client_error_translator: ErrorTranslator[Any],
|
|
12
|
+
default_server_error_translator: ErrorTranslator[Any],
|
|
13
|
+
) -> dict[Union[int, str], dict[str, Any]]:
|
|
14
|
+
responses: dict[Union[int, str], dict[str, Any]] = {}
|
|
15
|
+
|
|
16
|
+
for value in error_map.values():
|
|
17
|
+
if isinstance(value, int):
|
|
18
|
+
status = value
|
|
19
|
+
translator = None
|
|
20
|
+
else:
|
|
21
|
+
status = value.status
|
|
22
|
+
translator = value.translator
|
|
23
|
+
|
|
24
|
+
if translator is None:
|
|
25
|
+
translator = pick_translator_for_status(
|
|
26
|
+
status=status,
|
|
27
|
+
default_client_error_translator=default_client_error_translator,
|
|
28
|
+
default_server_error_translator=default_server_error_translator,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
response_model = translator.error_response_model_cls
|
|
32
|
+
responses[status] = {"model": response_model}
|
|
33
|
+
|
|
34
|
+
return responses
|
|
@@ -100,7 +100,11 @@ class ErrorAwareRoute(APIRoute):
|
|
|
100
100
|
default_server_error_translator=self.default_server_error_translator,
|
|
101
101
|
)
|
|
102
102
|
responses = {
|
|
103
|
-
**build_openapi_responses(
|
|
103
|
+
**build_openapi_responses(
|
|
104
|
+
error_map=self.error_map,
|
|
105
|
+
default_client_error_translator=self.default_client_error_translator,
|
|
106
|
+
default_server_error_translator=self.default_server_error_translator,
|
|
107
|
+
),
|
|
104
108
|
**(responses if responses is not None else {}),
|
|
105
109
|
}
|
|
106
110
|
super().__init__(
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import Any, Callable, Optional, Union
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
|
|
4
|
+
from fastapi_error_map.translator_policy import pick_translator_for_status
|
|
6
5
|
from fastapi_error_map.translators import ErrorTranslator
|
|
7
6
|
|
|
8
7
|
|
|
@@ -62,6 +61,7 @@ def resolve_rule_for_error(
|
|
|
62
61
|
status_or_rule = error_map[type(error)]
|
|
63
62
|
except KeyError:
|
|
64
63
|
raise RuntimeError(f"No rule defined for {type(error).__name__}") from error
|
|
64
|
+
|
|
65
65
|
if isinstance(status_or_rule, int):
|
|
66
66
|
status, translator, on_error = status_or_rule, None, default_on_error
|
|
67
67
|
else:
|
|
@@ -70,12 +70,14 @@ def resolve_rule_for_error(
|
|
|
70
70
|
status_or_rule.translator,
|
|
71
71
|
status_or_rule.on_error or default_on_error,
|
|
72
72
|
)
|
|
73
|
+
|
|
73
74
|
if translator is None:
|
|
74
|
-
translator = (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
translator = pick_translator_for_status(
|
|
76
|
+
status=status,
|
|
77
|
+
default_client_error_translator=default_client_error_translator,
|
|
78
|
+
default_server_error_translator=default_server_error_translator,
|
|
78
79
|
)
|
|
80
|
+
|
|
79
81
|
return ResolvedRule(
|
|
80
82
|
status=status,
|
|
81
83
|
translator=translator,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from starlette import status as http_status
|
|
4
|
+
|
|
5
|
+
from fastapi_error_map.translators import ErrorTranslator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_server_error(status: int) -> bool:
|
|
9
|
+
return status >= http_status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def pick_translator_for_status(
|
|
13
|
+
*,
|
|
14
|
+
status: int,
|
|
15
|
+
default_client_error_translator: ErrorTranslator[Any],
|
|
16
|
+
default_server_error_translator: ErrorTranslator[Any],
|
|
17
|
+
) -> ErrorTranslator[Any]:
|
|
18
|
+
return (
|
|
19
|
+
default_server_error_translator
|
|
20
|
+
if is_server_error(status)
|
|
21
|
+
else default_client_error_translator
|
|
22
|
+
)
|
|
@@ -72,4 +72,4 @@ class DefaultServerErrorTranslator(ErrorTranslator[SimpleErrorResponseModel]):
|
|
|
72
72
|
return SimpleErrorResponseModel
|
|
73
73
|
|
|
74
74
|
def from_error(self, _err: Exception) -> SimpleErrorResponseModel:
|
|
75
|
-
return SimpleErrorResponseModel(error="Internal server error
|
|
75
|
+
return SimpleErrorResponseModel(error="Internal server error")
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from fastapi_error_map.openapi import build_openapi_responses
|
|
4
|
+
from fastapi_error_map.rules import rule
|
|
5
|
+
from tests.unit.error_stubs import DatabaseError, ValidationError
|
|
6
|
+
from tests.unit.translator_stubs import (
|
|
7
|
+
ClientErrorStub,
|
|
8
|
+
CustomTranslator,
|
|
9
|
+
DummyClientErrorTranslator,
|
|
10
|
+
DummyServerErrorTranslator,
|
|
11
|
+
ServerErrorStub,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from fastapi_error_map.rules import ErrorMap
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_builds_response_from_short_form_rule() -> None:
|
|
19
|
+
error_map: ErrorMap = {
|
|
20
|
+
ValidationError: 400,
|
|
21
|
+
DatabaseError: 503,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
responses = build_openapi_responses(
|
|
25
|
+
error_map=error_map,
|
|
26
|
+
default_client_error_translator=DummyClientErrorTranslator(),
|
|
27
|
+
default_server_error_translator=DummyServerErrorTranslator(),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
assert responses == {
|
|
31
|
+
400: {"model": ClientErrorStub},
|
|
32
|
+
503: {"model": ServerErrorStub},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_builds_response_from_full_form_rule() -> None:
|
|
37
|
+
error_map: ErrorMap = {
|
|
38
|
+
ValidationError: rule(status=400),
|
|
39
|
+
DatabaseError: rule(status=503),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
responses = build_openapi_responses(
|
|
43
|
+
error_map=error_map,
|
|
44
|
+
default_client_error_translator=DummyClientErrorTranslator(),
|
|
45
|
+
default_server_error_translator=DummyServerErrorTranslator(),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
assert responses == {
|
|
49
|
+
400: {"model": ClientErrorStub},
|
|
50
|
+
503: {"model": ServerErrorStub},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_builds_response_with_custom_translator() -> None:
|
|
55
|
+
error_map: ErrorMap = {
|
|
56
|
+
ValidationError: rule(status=400, translator=CustomTranslator()),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
responses = build_openapi_responses(
|
|
60
|
+
error_map=error_map,
|
|
61
|
+
default_client_error_translator=DummyClientErrorTranslator(),
|
|
62
|
+
default_server_error_translator=DummyServerErrorTranslator(),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
assert responses == {400: {"model": dict}}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from fastapi_error_map import ErrorTranslator
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class ClientErrorStub:
|
|
8
|
+
error: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ServerErrorStub:
|
|
13
|
+
error: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DummyClientErrorTranslator(ErrorTranslator[ClientErrorStub]):
|
|
17
|
+
@property
|
|
18
|
+
def error_response_model_cls(self) -> type[ClientErrorStub]:
|
|
19
|
+
return ClientErrorStub
|
|
20
|
+
|
|
21
|
+
def from_error(self, err: Exception) -> ClientErrorStub:
|
|
22
|
+
return ClientErrorStub(error=f"client:{err}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DummyServerErrorTranslator(ErrorTranslator[ServerErrorStub]):
|
|
26
|
+
@property
|
|
27
|
+
def error_response_model_cls(self) -> type[ServerErrorStub]:
|
|
28
|
+
return ServerErrorStub
|
|
29
|
+
|
|
30
|
+
def from_error(self, _err: Exception) -> ServerErrorStub:
|
|
31
|
+
return ServerErrorStub(error="internal")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class CustomTranslator(ErrorTranslator[dict[str, str]]):
|
|
35
|
+
@property
|
|
36
|
+
def error_response_model_cls(self) -> type[dict[str, str]]:
|
|
37
|
+
return dict
|
|
38
|
+
|
|
39
|
+
def from_error(self, _err: Exception) -> dict[str, str]:
|
|
40
|
+
return {"msg": "explicit"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from typing import Any, Union
|
|
2
|
-
|
|
3
|
-
from fastapi_error_map.rules import ErrorMap
|
|
4
|
-
from fastapi_error_map.translators import SimpleErrorResponseModel
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def build_openapi_responses(
|
|
8
|
-
error_map: ErrorMap,
|
|
9
|
-
default_response_model: type[Any] = SimpleErrorResponseModel,
|
|
10
|
-
) -> dict[Union[int, str], dict[str, Any]]:
|
|
11
|
-
responses: dict[Union[int, str], dict[str, Any]] = {}
|
|
12
|
-
|
|
13
|
-
for value in error_map.values():
|
|
14
|
-
if isinstance(value, int):
|
|
15
|
-
status_code = value
|
|
16
|
-
response_model: type[Any] = default_response_model
|
|
17
|
-
else:
|
|
18
|
-
status_code = value.status
|
|
19
|
-
response_model = (
|
|
20
|
-
value.translator.error_response_model_cls
|
|
21
|
-
if value.translator is not None
|
|
22
|
-
else default_response_model
|
|
23
|
-
)
|
|
24
|
-
responses[status_code] = {"model": response_model}
|
|
25
|
-
|
|
26
|
-
return responses
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
|
|
3
|
-
from fastapi_error_map import SimpleErrorResponseModel
|
|
4
|
-
from fastapi_error_map.openapi import build_openapi_responses
|
|
5
|
-
from fastapi_error_map.rules import rule
|
|
6
|
-
from tests.unit.error_stubs import DatabaseError, ValidationError
|
|
7
|
-
from tests.unit.translator_stubs import CustomTranslator
|
|
8
|
-
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from fastapi_error_map.rules import ErrorMap
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_builds_response_from_short_form_rule() -> None:
|
|
14
|
-
error_map: ErrorMap = {
|
|
15
|
-
ValidationError: 400,
|
|
16
|
-
DatabaseError: 503,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
responses = build_openapi_responses(error_map)
|
|
20
|
-
|
|
21
|
-
assert responses == {
|
|
22
|
-
400: {"model": SimpleErrorResponseModel},
|
|
23
|
-
503: {"model": SimpleErrorResponseModel},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_builds_response_from_full_form_rule() -> None:
|
|
28
|
-
error_map: ErrorMap = {
|
|
29
|
-
ValidationError: rule(status=400),
|
|
30
|
-
DatabaseError: rule(status=503),
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
responses = build_openapi_responses(error_map)
|
|
34
|
-
|
|
35
|
-
assert responses == {
|
|
36
|
-
400: {"model": SimpleErrorResponseModel},
|
|
37
|
-
503: {"model": SimpleErrorResponseModel},
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_builds_response_with_custom_translator() -> None:
|
|
42
|
-
error_map: ErrorMap = {
|
|
43
|
-
ValidationError: rule(status=400, translator=CustomTranslator()),
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
responses = build_openapi_responses(error_map)
|
|
47
|
-
|
|
48
|
-
assert responses == {400: {"model": dict}}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from fastapi_error_map import ErrorTranslator
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class DummyClientErrorTranslator(ErrorTranslator[dict[str, str]]):
|
|
5
|
-
@property
|
|
6
|
-
def error_response_model_cls(self) -> type[dict[str, str]]:
|
|
7
|
-
return dict
|
|
8
|
-
|
|
9
|
-
def from_error(self, err: Exception) -> dict[str, str]:
|
|
10
|
-
return {"error": f"client:{err}"}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class DummyServerErrorTranslator(ErrorTranslator[dict[str, str]]):
|
|
14
|
-
@property
|
|
15
|
-
def error_response_model_cls(self) -> type[dict[str, str]]:
|
|
16
|
-
return dict
|
|
17
|
-
|
|
18
|
-
def from_error(self, _err: Exception) -> dict[str, str]:
|
|
19
|
-
return {"error": "internal"}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class CustomTranslator(ErrorTranslator[dict[str, str]]):
|
|
23
|
-
@property
|
|
24
|
-
def error_response_model_cls(self) -> type[dict[str, str]]:
|
|
25
|
-
return dict
|
|
26
|
-
|
|
27
|
-
def from_error(self, _err: Exception) -> dict[str, str]:
|
|
28
|
-
return {"msg": "explicit"}
|
|
File without changes
|
{fastapi_error_map-0.9.2 → fastapi_error_map-0.9.3}/.github/workflows/test-compatibility.yaml
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|