fastapi-errors-plus 0.6.2__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/PKG-INFO +177 -52
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/README.md +173 -51
- fastapi_errors_plus-0.8.0/fastapi_errors_plus/__init__.py +17 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus/base.py +48 -56
- fastapi_errors_plus-0.8.0/fastapi_errors_plus/error_doc.py +59 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus/errors.py +244 -105
- fastapi_errors_plus-0.8.0/fastapi_errors_plus/example_utils.py +52 -0
- fastapi_errors_plus-0.8.0/fastapi_errors_plus/protocol.py +40 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus.egg-info/PKG-INFO +177 -52
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus.egg-info/SOURCES.txt +5 -1
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus.egg-info/requires.txt +3 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/pyproject.toml +19 -1
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/tests/test_app.py +26 -18
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/tests/test_base.py +106 -61
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/tests/test_errors.py +395 -170
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/tests/test_integration.py +93 -92
- fastapi_errors_plus-0.8.0/tests/test_release_08.py +131 -0
- fastapi_errors_plus-0.8.0/tests/test_release_08_edge_cases.py +243 -0
- fastapi_errors_plus-0.6.2/fastapi_errors_plus/__init__.py +0 -14
- fastapi_errors_plus-0.6.2/fastapi_errors_plus/protocol.py +0 -53
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/LICENSE +0 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus/py.typed +0 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus.egg-info/dependency_links.txt +0 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/fastapi_errors_plus.egg-info/top_level.txt +0 -0
- {fastapi_errors_plus-0.6.2 → fastapi_errors_plus-0.8.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-errors-plus
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Universal library for documenting errors in FastAPI endpoints
|
|
5
5
|
Author-email: Igor Selivanov <seligoroff@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -22,6 +22,9 @@ Provides-Extra: dev
|
|
|
22
22
|
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
23
23
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
24
24
|
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
25
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
25
28
|
Dynamic: license-file
|
|
26
29
|
|
|
27
30
|
# fastapi-errors-plus
|
|
@@ -30,12 +33,12 @@ Dynamic: license-file
|
|
|
30
33
|
[](https://opensource.org/licenses/MIT)
|
|
31
34
|
[](https://www.python.org/downloads/)
|
|
32
35
|
[](https://fastapi.tiangolo.com/)
|
|
33
|
-
[](https://github.com/seligoroff/fastapi-errors-plus)
|
|
37
|
+
[](https://github.com/seligoroff/fastapi-errors-plus)
|
|
35
38
|
|
|
36
39
|
Universal library for documenting errors in FastAPI endpoints.
|
|
37
40
|
|
|
38
|
-
> [Русская версия README](README.ru.md)
|
|
41
|
+
> [Русская версия README](https://github.com/seligoroff/fastapi-errors-plus/blob/main/README.ru.md)
|
|
39
42
|
|
|
40
43
|
## Philosophy
|
|
41
44
|
|
|
@@ -164,7 +167,7 @@ class MyErrorDTO:
|
|
|
164
167
|
status_code = 404
|
|
165
168
|
message = "Not found"
|
|
166
169
|
|
|
167
|
-
def
|
|
170
|
+
def to_examples(self):
|
|
168
171
|
return {
|
|
169
172
|
"Not found": {
|
|
170
173
|
"value": {"detail": "Not found"},
|
|
@@ -207,6 +210,39 @@ def delete_item(id: int):
|
|
|
207
210
|
pass
|
|
208
211
|
```
|
|
209
212
|
|
|
213
|
+
#### OpenAPI extras (`schema`) next to examples
|
|
214
|
+
|
|
215
|
+
ADR-style payloads (`code`, `detail`, optional `context`) need a **`schema`** in the spec besides **`examples`**. On **`BaseErrorDTO`** / **`StandardErrorDTO`** / **`ErrorDoc`** use **`openapi_json_extras`** (a dict merged under `content["application/json"]` — omit `example` / `examples` there; **`to_examples()`** still defines examples):
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from fastapi import APIRouter, status
|
|
219
|
+
from fastapi_errors_plus import BaseErrorDTO, Errors
|
|
220
|
+
|
|
221
|
+
ADR_ERROR_BODY_SCHEMA = {
|
|
222
|
+
"type": "object",
|
|
223
|
+
"required": ["code", "detail"],
|
|
224
|
+
"properties": {
|
|
225
|
+
"code": {"type": "string"},
|
|
226
|
+
"detail": {"type": "string"},
|
|
227
|
+
"context": {"type": "object"},
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
business_conflict = BaseErrorDTO(
|
|
232
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
233
|
+
message="BusinessRuleViolation",
|
|
234
|
+
openapi_json_extras={"schema": ADR_ERROR_BODY_SCHEMA},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
router = APIRouter()
|
|
238
|
+
|
|
239
|
+
@router.post("/items", responses=Errors(business_conflict, validation_error=False))
|
|
240
|
+
def create_item():
|
|
241
|
+
...
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Custom **`ErrorDTO`** classes may instead define **`to_openapi_json_media_type_extras()`** returning a **`dict`**; if present and non-empty, it overrides **`openapi_json_extras`**. Any later **`dict`** in **`Errors`** for that status still overrides the same **`application/json`** keys (same precedence as merging two dicts).
|
|
245
|
+
|
|
210
246
|
#### StandardErrorDTO
|
|
211
247
|
|
|
212
248
|
Extended implementation for errors with multiple examples (useful for standard HTTP errors):
|
|
@@ -251,6 +287,37 @@ def delete_item(id: int):
|
|
|
251
287
|
- Reusable across all endpoints
|
|
252
288
|
- Supports inheritance for custom logic
|
|
253
289
|
|
|
290
|
+
`examples` values may be **strings** (shorthand for `{"detail": text}`) or full OpenAPI Example Objects with **`summary`** and **`value`**.
|
|
291
|
+
|
|
292
|
+
#### ErrorDoc
|
|
293
|
+
|
|
294
|
+
For arbitrary response bodies (ADR `code` / `detail` / `context`, not only `detail` strings), use **`ErrorDoc`**:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from fastapi_errors_plus import ErrorDoc, Errors
|
|
298
|
+
|
|
299
|
+
permission_denied = ErrorDoc(
|
|
300
|
+
status_code=403,
|
|
301
|
+
message="Insufficient permissions",
|
|
302
|
+
examples={
|
|
303
|
+
"MissingRole": {
|
|
304
|
+
"summary": "User lacks required role",
|
|
305
|
+
"value": {
|
|
306
|
+
"code": "FORBIDDEN",
|
|
307
|
+
"detail": "Role admin required",
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
openapi_json_extras={"schema": ADR_ERROR_BODY_SCHEMA},
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
@router.delete("/{id}", responses=Errors(permission_denied))
|
|
315
|
+
def delete_item(id: int):
|
|
316
|
+
...
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
A **plain dict** as an example value is treated as the full response **body** unless it looks like an OpenAPI Example Object (keys only among `value`, `summary`, `description`, `externalValue`).
|
|
320
|
+
|
|
254
321
|
### 5. Mixed Usage
|
|
255
322
|
|
|
256
323
|
Combine flags, dict, and ErrorDTO:
|
|
@@ -297,9 +364,40 @@ def update_item(id: int):
|
|
|
297
364
|
|
|
298
365
|
The OpenAPI spec will contain both examples under the 404 status code.
|
|
299
366
|
|
|
367
|
+
When merging the same status code: a **dict** wins for **`description`** over the bundled standard-flag wording; an **ErrorDTO**’s `message` can replace the description only while it still matches the library’s default label for that code (custom descriptions coming from dicts are not overwritten by DTOs).
|
|
368
|
+
|
|
369
|
+
Under `content["application/json"]`, `example` / `examples` are merged as before; any **other** OpenAPI Media Type fields from a later **`dict`** (for example **`schema`**, **`encoding`**) are **copied in** as well—the later dict wins on conflict (**same rule as `description`**).
|
|
370
|
+
|
|
371
|
+
You can combine one **`ErrorDTO`** (examples) with a **`dict`** for the **same numeric status code** listing only **`schema`** (or other non-example keys) without repeating the boilerplate examples block:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
Errors(
|
|
375
|
+
conflict_error_doc, # implements ErrorDTO, e.g. .for_openapi() for ADR-shaped examples
|
|
376
|
+
{
|
|
377
|
+
status.HTTP_409_CONFLICT: {
|
|
378
|
+
"description": "Business rule violation",
|
|
379
|
+
"content": {
|
|
380
|
+
"application/json": {
|
|
381
|
+
"schema": {
|
|
382
|
+
"type": "object",
|
|
383
|
+
"properties": {
|
|
384
|
+
"code": {"type": "string"},
|
|
385
|
+
"detail": {"type": "string"},
|
|
386
|
+
"context": {"type": "object"},
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Order matters only for overlaps: whichever **`dict`** is applied **later** in the **`Errors`** argument list overwrites **`schema`** / **`encoding`** (and **`model`** on the outer response dict, if provided) when the same keys appear again.
|
|
397
|
+
|
|
300
398
|
## ErrorDTO Protocol
|
|
301
399
|
|
|
302
|
-
The
|
|
400
|
+
The canonical **`ErrorDTO`** protocol defines the interface for error objects compatible with the library:
|
|
303
401
|
|
|
304
402
|
```python
|
|
305
403
|
from typing import Protocol, Dict, Any
|
|
@@ -308,27 +406,30 @@ class ErrorDTO(Protocol):
|
|
|
308
406
|
status_code: int
|
|
309
407
|
message: str
|
|
310
408
|
|
|
311
|
-
def
|
|
312
|
-
"""
|
|
313
|
-
|
|
314
|
-
Returns:
|
|
315
|
-
Dict in format: {"key": {"value": {"detail": "message"}}}
|
|
316
|
-
"""
|
|
409
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
410
|
+
"""OpenAPI ``examples`` map: {"Key": {"value": {...}, "summary": "..."}}."""
|
|
317
411
|
...
|
|
318
412
|
```
|
|
319
413
|
|
|
320
414
|
Any class implementing this protocol (through structural typing) can be used with `Errors()`.
|
|
321
415
|
|
|
416
|
+
**Legacy migration:** classes that only define **`to_example()`** still work at **runtime** (`DeprecationWarning` once per class per `Errors()` call). For static typing use **`LegacyErrorDTO`** or the union alias **`ErrorDTOLike`** (`ErrorDTO | LegacyErrorDTO`).
|
|
417
|
+
|
|
322
418
|
**Best Practice:** For maximum clarity, consider making your domain exceptions implement the ErrorDTO protocol directly. See [Best Practice: Connecting Exceptions and ErrorDTO](#best-practice-connecting-exceptions-and-errordto) for details.
|
|
323
419
|
|
|
324
420
|
### When to Use Protocol vs BaseErrorDTO
|
|
325
421
|
|
|
422
|
+
Optional on custom DTOs (OpenAPI media-type extras without duplicating a status `dict`):
|
|
423
|
+
|
|
424
|
+
- attribute **`openapi_json_extras`**: fragment for **`content["application/json"]`** (often `{"schema": ...}` — not for `example` / `examples`);
|
|
425
|
+
- or method **`to_openapi_json_media_type_extras() -> Optional[dict]`** — when non-empty, overrides **`openapi_json_extras`**.
|
|
426
|
+
|
|
326
427
|
**Use Protocol (structural typing)** when:
|
|
327
428
|
- Your project already has error DTOs that implement the protocol
|
|
328
429
|
- You need maximum flexibility and custom implementations
|
|
329
430
|
- You want to keep your existing error infrastructure
|
|
330
431
|
|
|
331
|
-
**Use BaseErrorDTO/StandardErrorDTO** when:
|
|
432
|
+
**Use BaseErrorDTO/StandardErrorDTO/ErrorDoc** when:
|
|
332
433
|
- Starting a new project or adding error documentation
|
|
333
434
|
- You want a ready-to-use implementation without boilerplate
|
|
334
435
|
- You need multiple examples for standard HTTP errors (401, 403, etc.)
|
|
@@ -339,7 +440,7 @@ Both approaches work together — you can mix them in the same `Errors()` call!
|
|
|
339
440
|
|
|
340
441
|
**Note:** Pydantic is **not required** to use this library. This section is for projects that already use Pydantic and want to integrate it with the ErrorDTO protocol.
|
|
341
442
|
|
|
342
|
-
Since the library uses structural typing (Protocol), any class that implements the required attributes (`status_code`, `message`, `
|
|
443
|
+
Since the library uses structural typing (Protocol), any class that implements the required attributes (`status_code`, `message`, `to_examples()`) will work, including Pydantic models. Legacy `to_example()` is still accepted at runtime.
|
|
343
444
|
|
|
344
445
|
### Simple Pydantic Model as ErrorDTO
|
|
345
446
|
|
|
@@ -353,8 +454,8 @@ class PydanticErrorDTO(BaseModel):
|
|
|
353
454
|
status_code: int = Field(..., ge=400, le=599, description="HTTP status code")
|
|
354
455
|
message: str = Field(..., min_length=1, description="Error message")
|
|
355
456
|
|
|
356
|
-
def
|
|
357
|
-
"""Generate
|
|
457
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
458
|
+
"""Generate examples for OpenAPI."""
|
|
358
459
|
return {
|
|
359
460
|
self.message: {
|
|
360
461
|
"value": {"detail": self.message},
|
|
@@ -397,8 +498,8 @@ class DetailedErrorDTO(BaseModel):
|
|
|
397
498
|
error_code: Optional[str] = Field(None, description="Internal error code")
|
|
398
499
|
timestamp: Optional[str] = Field(None, description="Error timestamp")
|
|
399
500
|
|
|
400
|
-
def
|
|
401
|
-
"""Generate
|
|
501
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
502
|
+
"""Generate examples for OpenAPI."""
|
|
402
503
|
example = {"detail": self.message}
|
|
403
504
|
if self.error_code:
|
|
404
505
|
example["error_code"] = self.error_code
|
|
@@ -455,7 +556,7 @@ class DomainException(Exception):
|
|
|
455
556
|
status_code: int
|
|
456
557
|
message: str
|
|
457
558
|
|
|
458
|
-
def
|
|
559
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
459
560
|
return {self.message: {"value": {"detail": self.message}}}
|
|
460
561
|
|
|
461
562
|
@classmethod
|
|
@@ -498,7 +599,7 @@ See [examples/domain_exceptions.py](examples/domain_exceptions.py) for complete
|
|
|
498
599
|
|
|
499
600
|
## Compatibility with Existing Projects
|
|
500
601
|
|
|
501
|
-
If your project already has error DTOs (like `ApiErrorDTO`), they can work with `fastapi-errors-plus` if they implement the `ErrorDTO` protocol:
|
|
602
|
+
If your project already has error DTOs (like `ApiErrorDTO`), they can work with `fastapi-errors-plus` if they implement the `ErrorDTO` protocol (or legacy `to_example()` during migration):
|
|
502
603
|
|
|
503
604
|
```python
|
|
504
605
|
# Your existing ApiErrorDTO
|
|
@@ -507,7 +608,7 @@ class ApiErrorDTO:
|
|
|
507
608
|
status_code: int
|
|
508
609
|
message: str
|
|
509
610
|
|
|
510
|
-
def
|
|
611
|
+
def to_examples(self) -> dict:
|
|
511
612
|
return {
|
|
512
613
|
self.message: {
|
|
513
614
|
"value": {"detail": self.message},
|
|
@@ -568,16 +669,21 @@ Errors(
|
|
|
568
669
|
FastAPI automatically validates all parameters (Path, Query, Body), so 422 is relevant in 95%+ of endpoints. For endpoints without parameters, explicitly set `validation_error=False` or `validation_error_422=False`.
|
|
569
670
|
|
|
570
671
|
**Returns:**
|
|
571
|
-
-
|
|
572
|
-
-
|
|
672
|
+
- A dict-like `Mapping[int, …]` suitable for FastAPI’s `responses` / OpenAPI
|
|
673
|
+
- Pass the instance as-is — **do not** call it like a function: `responses=Errors(...)`
|
|
573
674
|
|
|
574
675
|
#### Usage
|
|
575
676
|
|
|
576
677
|
```python
|
|
577
|
-
#
|
|
578
|
-
|
|
678
|
+
# Instances are Mapping keyed by HTTP status codes
|
|
679
|
+
error_responses = Errors(unauthorized_401=True, forbidden_403=True)
|
|
680
|
+
documented = error_responses[401] # deep copy — safe to read, won't mutate internal state
|
|
579
681
|
```
|
|
580
682
|
|
|
683
|
+
**Isolation (0.8+):** incoming response **dicts** are **deep-copied** on ingest; **`errors[status]`** returns a **deep copy** so callers cannot corrupt shared registry templates or internal merge state.
|
|
684
|
+
|
|
685
|
+
**Descriptions:** response blocks with only **`model`** (no `description`) get a default from **`HTTPStatus.phrase`** so OpenAPI generation does not fail.
|
|
686
|
+
|
|
581
687
|
### `ErrorDTO`
|
|
582
688
|
|
|
583
689
|
Protocol for error objects compatible with the library.
|
|
@@ -587,7 +693,32 @@ Protocol for error objects compatible with the library.
|
|
|
587
693
|
- `message: str` — Error message description
|
|
588
694
|
|
|
589
695
|
**Required methods:**
|
|
590
|
-
- `
|
|
696
|
+
- `to_examples() -> Dict[str, Any]` — OpenAPI `examples` map for `application/json`
|
|
697
|
+
|
|
698
|
+
During `Errors(...)` initialization, non-`dict` objects in `*errors` missing `status_code`, `message`, or a callable **`to_examples()`** / **`to_example()`** raise **`TypeError`** naming what was missing.
|
|
699
|
+
|
|
700
|
+
### `LegacyErrorDTO` / `ErrorDTOLike`
|
|
701
|
+
|
|
702
|
+
- **`LegacyErrorDTO`** — typing helper for classes that only implement deprecated **`to_example()`**.
|
|
703
|
+
- **`ErrorDTOLike`** — `Union[ErrorDTO, LegacyErrorDTO]` for transitional annotations.
|
|
704
|
+
|
|
705
|
+
### `ErrorDoc`
|
|
706
|
+
|
|
707
|
+
Declarative DTO for arbitrary example bodies and per-example **`summary`**.
|
|
708
|
+
|
|
709
|
+
**Constructor:**
|
|
710
|
+
```python
|
|
711
|
+
ErrorDoc(
|
|
712
|
+
status_code: int,
|
|
713
|
+
message: str,
|
|
714
|
+
examples: Optional[Dict[str, str | dict]] = None,
|
|
715
|
+
body: Optional[Dict[str, Any]] = None,
|
|
716
|
+
example_key: Optional[str] = None,
|
|
717
|
+
openapi_json_extras: Optional[Dict[str, Any]] = None,
|
|
718
|
+
)
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
When **`examples`** is omitted, a single example is built from **`body`** or `{"detail": message}`.
|
|
591
722
|
|
|
592
723
|
### `BaseErrorDTO`
|
|
593
724
|
|
|
@@ -598,6 +729,7 @@ Base implementation of ErrorDTO Protocol for convenience.
|
|
|
598
729
|
BaseErrorDTO(
|
|
599
730
|
status_code: int,
|
|
600
731
|
message: str,
|
|
732
|
+
openapi_json_extras: Optional[Dict[str, Any]] = None,
|
|
601
733
|
)
|
|
602
734
|
```
|
|
603
735
|
|
|
@@ -615,6 +747,7 @@ Extended implementation for errors with multiple examples.
|
|
|
615
747
|
StandardErrorDTO(
|
|
616
748
|
status_code: int,
|
|
617
749
|
message: str,
|
|
750
|
+
openapi_json_extras: Optional[Dict[str, Any]] = None,
|
|
618
751
|
examples: Optional[Dict[str, str]] = None,
|
|
619
752
|
)
|
|
620
753
|
```
|
|
@@ -666,7 +799,7 @@ def delete_item(id: int):
|
|
|
666
799
|
```python
|
|
667
800
|
from fastapi import APIRouter
|
|
668
801
|
from fastapi_errors_plus import Errors
|
|
669
|
-
from api.exceptions.dto import
|
|
802
|
+
from api.exceptions.dto import notification_not_found_error # ErrorDTO-compatible instance
|
|
670
803
|
|
|
671
804
|
router = APIRouter()
|
|
672
805
|
|
|
@@ -675,7 +808,7 @@ router = APIRouter()
|
|
|
675
808
|
responses=Errors(
|
|
676
809
|
unauthorized=True,
|
|
677
810
|
forbidden=True,
|
|
678
|
-
|
|
811
|
+
notification_not_found_error,
|
|
679
812
|
),
|
|
680
813
|
)
|
|
681
814
|
async def delete_notification(notification_id: int):
|
|
@@ -714,40 +847,32 @@ This example shows how to use `fastapi-errors-plus` in a FastAPI project with Cl
|
|
|
714
847
|
|
|
715
848
|
**Domain Layer** (`domain/errors.py`):
|
|
716
849
|
```python
|
|
717
|
-
from typing import
|
|
850
|
+
from typing import Dict, Any
|
|
718
851
|
|
|
719
|
-
class
|
|
720
|
-
"""Domain error
|
|
852
|
+
class DomainException(Exception):
|
|
853
|
+
"""Domain exception usable as runtime error and shaped like ErrorDTO for OpenAPI."""
|
|
721
854
|
status_code: int
|
|
722
855
|
message: str
|
|
723
|
-
|
|
724
|
-
def to_example(self) -> Dict[str, Any]:
|
|
725
|
-
"""Generate example for OpenAPI."""
|
|
726
|
-
...
|
|
727
856
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
def to_example(self) -> Dict[str, Any]:
|
|
857
|
+
def __init__(self) -> None:
|
|
858
|
+
super().__init__(self.message)
|
|
859
|
+
|
|
860
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
734
861
|
return {
|
|
735
|
-
|
|
736
|
-
"value": {"detail":
|
|
862
|
+
self.message: {
|
|
863
|
+
"value": {"detail": self.message},
|
|
737
864
|
},
|
|
738
865
|
}
|
|
739
866
|
|
|
740
|
-
|
|
741
|
-
|
|
867
|
+
|
|
868
|
+
class ItemNotFoundError(DomainException):
|
|
869
|
+
status_code = 404
|
|
870
|
+
message = "Item not found"
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
class ItemAlreadyExistsError(DomainException):
|
|
742
874
|
status_code = 409
|
|
743
875
|
message = "Item already exists"
|
|
744
|
-
|
|
745
|
-
def to_example(self) -> Dict[str, Any]:
|
|
746
|
-
return {
|
|
747
|
-
"ItemAlreadyExists": {
|
|
748
|
-
"value": {"detail": "Item already exists"},
|
|
749
|
-
},
|
|
750
|
-
}
|
|
751
876
|
```
|
|
752
877
|
|
|
753
878
|
**Application Layer** (`application/use_cases.py`):
|