fastapi-error-map 0.9.2__tar.gz → 0.9.4__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 (37) hide show
  1. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/PKG-INFO +36 -6
  2. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/README.md +35 -5
  3. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/fastapi_error_map/error_handling.py +5 -2
  4. fastapi_error_map-0.9.4/fastapi_error_map/openapi.py +34 -0
  5. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/fastapi_error_map/routing.py +5 -1
  6. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/fastapi_error_map/rules.py +8 -6
  7. fastapi_error_map-0.9.4/fastapi_error_map/translator_policy.py +22 -0
  8. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/fastapi_error_map/translators.py +2 -2
  9. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/pyproject.toml +5 -1
  10. fastapi_error_map-0.9.4/tests/unit/test_openapi.py +65 -0
  11. fastapi_error_map-0.9.4/tests/unit/translator_stubs.py +40 -0
  12. fastapi_error_map-0.9.2/fastapi_error_map/openapi.py +0 -26
  13. fastapi_error_map-0.9.2/tests/unit/test_openapi.py +0 -48
  14. fastapi_error_map-0.9.2/tests/unit/translator_stubs.py +0 -28
  15. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/.github/workflows/ci.yaml +0 -0
  16. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/.github/workflows/test-compatibility.yaml +0 -0
  17. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/.gitignore +0 -0
  18. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/.pre-commit-config.yaml +0 -0
  19. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/LICENSE +0 -0
  20. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/Makefile +0 -0
  21. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/docs/example-openapi.png +0 -0
  22. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/examples/__init__.py +0 -0
  23. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/examples/errors.py +0 -0
  24. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/examples/main.py +0 -0
  25. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/fastapi_error_map/__init__.py +0 -0
  26. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/fastapi_error_map/py.typed +0 -0
  27. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/noxfile.py +0 -0
  28. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/__init__.py +0 -0
  29. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/integration/__init__.py +0 -0
  30. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/integration/test_example.py +0 -0
  31. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/integration/test_routing.py +0 -0
  32. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/unit/__init__.py +0 -0
  33. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/unit/error_stubs.py +0 -0
  34. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/unit/test_error_handling.py +0 -0
  35. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/unit/test_rules.py +0 -0
  36. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/tests/unit/test_translators.py +0 -0
  37. {fastapi_error_map-0.9.2 → fastapi_error_map-0.9.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-error-map
3
- Version: 0.9.2
3
+ Version: 0.9.4
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
- [![PyPI version](https://badge.fury.io/py/fastapi-error-map.svg?cacheBust=1)](https://badge.fury.io/py/fastapi-error-map)
44
+ [![PyPI version](https://badge.fury.io/py/fastapi-error-map.svg?cacheBust=4)](https://badge.fury.io/py/fastapi-error-map)
45
45
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/fastapi-error-map?cacheBust=1)
46
+ [![codecov](https://codecov.io/gh/ivan-borovets/fastapi-error-map/branch/master/graph/badge.svg?token=ABTVQLI0RL)](https://codecov.io/gh/ivan-borovets/fastapi-error-map)
46
47
  ![GitHub License](https://img.shields.io/github/license/ivan-borovets/fastapi-error-map?cacheBust=1)
47
48
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ivan-borovets/fastapi-error-map/ci.yaml?cacheBust=1)
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
- ### ⚙ How `error_map` Works
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": "SomeError"
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't affect the HTTP response, but it's useful for:
232
+ It doesnt change the response status/body when it succeeds and is useful for:
220
233
 
221
234
  - logging
222
235
  - sending alerts
@@ -240,6 +253,18 @@ 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
+
263
+ > [!NOTE]
264
+ > Both translator and `on_error` receive the raised exception as an argument.
265
+ > If your implementation does not use it, suppress the unused-argument warning from your linter (e.g. `# noqa: ARG002`
266
+ > for Ruff).
267
+
243
268
  ### 🧠 Parameter Precedence
244
269
 
245
270
  Error handling and schema generation in `fastapi-error-map` are fully driven by route-level arguments to `.get()`,
@@ -298,7 +323,7 @@ from `error_map` — but only for the specified status codes.
298
323
 
299
324
  ```
300
325
 
301
- #### 🚨 Handling Unmapped Exceptions (`warn_on_unmapped`)
326
+ #### Handling Unmapped Exceptions (`warn_on_unmapped`)
302
327
 
303
328
  By default (`warn_on_unmapped=True`), `fastapi-error-map` expects every exception raised in a handler to be explicitly
304
329
  listed in `error_map`.
@@ -320,3 +345,8 @@ This behavior is not affected by `warn_on_unmapped`.
320
345
 
321
346
  If the error is declared in `error_map`, `fastapi-error-map` handles the response itself — the global handler will not
322
347
  be triggered.
348
+
349
+ ### 📚 Examples
350
+
351
+ - Minimal usage: [`examples/`](./examples/main.py)
352
+ - 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
- [![PyPI version](https://badge.fury.io/py/fastapi-error-map.svg?cacheBust=1)](https://badge.fury.io/py/fastapi-error-map)
3
+ [![PyPI version](https://badge.fury.io/py/fastapi-error-map.svg?cacheBust=4)](https://badge.fury.io/py/fastapi-error-map)
4
4
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/fastapi-error-map?cacheBust=1)
5
+ [![codecov](https://codecov.io/gh/ivan-borovets/fastapi-error-map/branch/master/graph/badge.svg?token=ABTVQLI0RL)](https://codecov.io/gh/ivan-borovets/fastapi-error-map)
5
6
  ![GitHub License](https://img.shields.io/github/license/ivan-borovets/fastapi-error-map?cacheBust=1)
6
7
  ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ivan-borovets/fastapi-error-map/ci.yaml?cacheBust=1)
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
- ### ⚙ How `error_map` Works
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": "SomeError"
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't affect the HTTP response, but it's useful for:
191
+ It doesnt change the response status/body when it succeeds and is useful for:
179
192
 
180
193
  - logging
181
194
  - sending alerts
@@ -199,6 +212,18 @@ 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
+
222
+ > [!NOTE]
223
+ > Both translator and `on_error` receive the raised exception as an argument.
224
+ > If your implementation does not use it, suppress the unused-argument warning from your linter (e.g. `# noqa: ARG002`
225
+ > for Ruff).
226
+
202
227
  ### 🧠 Parameter Precedence
203
228
 
204
229
  Error handling and schema generation in `fastapi-error-map` are fully driven by route-level arguments to `.get()`,
@@ -257,7 +282,7 @@ from `error_map` — but only for the specified status codes.
257
282
 
258
283
  ```
259
284
 
260
- #### 🚨 Handling Unmapped Exceptions (`warn_on_unmapped`)
285
+ #### Handling Unmapped Exceptions (`warn_on_unmapped`)
261
286
 
262
287
  By default (`warn_on_unmapped=True`), `fastapi-error-map` expects every exception raised in a handler to be explicitly
263
288
  listed in `error_map`.
@@ -279,3 +304,8 @@ This behavior is not affected by `warn_on_unmapped`.
279
304
 
280
305
  If the error is declared in `error_map`, `fastapi-error-map` handles the response itself — the global handler will not
281
306
  be triggered.
307
+
308
+ ### 📚 Examples
309
+
310
+ - Minimal usage: [`examples/`](./examples/main.py)
311
+ - 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 inspect.iscoroutinefunction(func):
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
- raise (exc.__cause__ or exc) from None
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(self.error_map),
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 starlette import status as http_status
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
- default_server_error_translator
76
- if status >= http_status.HTTP_500_INTERNAL_SERVER_ERROR
77
- else default_client_error_translator
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
+ )
@@ -71,5 +71,5 @@ class DefaultServerErrorTranslator(ErrorTranslator[SimpleErrorResponseModel]):
71
71
  def error_response_model_cls(self) -> type[SimpleErrorResponseModel]:
72
72
  return SimpleErrorResponseModel
73
73
 
74
- def from_error(self, _err: Exception) -> SimpleErrorResponseModel:
75
- return SimpleErrorResponseModel(error="Internal server error.")
74
+ def from_error(self, err: Exception) -> SimpleErrorResponseModel:
75
+ return SimpleErrorResponseModel(error="Internal server error")
@@ -7,7 +7,7 @@ sources = ["src"]
7
7
 
8
8
  [project]
9
9
  name = "fastapi-error-map"
10
- version = "0.9.2"
10
+ version = "0.9.4"
11
11
  description = "Elegant per-endpoint error handling in FastAPI with OpenAPI schema generation"
12
12
  readme = "README.md"
13
13
  license = { text = "Apache-2.0" }
@@ -160,6 +160,7 @@ split-on-trailing-comma = true
160
160
 
161
161
  [tool.ruff.lint.per-file-ignores]
162
162
  "tests/**" = [
163
+ "ARG002", # unused-method-argument
163
164
  "PLR2004", # magic-value-comparison
164
165
  "S101", # assert
165
166
  ]
@@ -175,6 +176,9 @@ split-on-trailing-comma = true
175
176
  "UP006", # non-pep585-annotation
176
177
  "UP035", # deprecated-import
177
178
  ]
179
+ "src/fastapi_error_map/translators.py" = [
180
+ "ARG002", # unused-method-argument
181
+ ]
178
182
 
179
183
  [tool.slotscheck]
180
184
  strict-imports = true
@@ -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"}