fastapi-errors-plus 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
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/__init__.py +11 -6
- fastapi_errors_plus/base.py +49 -57
- fastapi_errors_plus/error_doc.py +65 -0
- fastapi_errors_plus/error_profile.py +31 -0
- fastapi_errors_plus/errors.py +293 -232
- fastapi_errors_plus/example_utils.py +52 -0
- fastapi_errors_plus/merge_utils.py +61 -0
- fastapi_errors_plus/protocol.py +26 -46
- {fastapi_errors_plus-0.7.0.dist-info → fastapi_errors_plus-0.9.0.dist-info}/METADATA +152 -30
- fastapi_errors_plus-0.9.0.dist-info/RECORD +14 -0
- {fastapi_errors_plus-0.7.0.dist-info → fastapi_errors_plus-0.9.0.dist-info}/WHEEL +1 -1
- fastapi_errors_plus-0.7.0.dist-info/RECORD +0 -10
- {fastapi_errors_plus-0.7.0.dist-info → fastapi_errors_plus-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {fastapi_errors_plus-0.7.0.dist-info → fastapi_errors_plus-0.9.0.dist-info}/top_level.txt +0 -0
fastapi_errors_plus/__init__.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
"""Universal library for documenting errors in FastAPI endpoints."""
|
|
2
2
|
|
|
3
3
|
from fastapi_errors_plus.base import BaseErrorDTO, StandardErrorDTO
|
|
4
|
+
from fastapi_errors_plus.error_doc import ErrorDoc
|
|
5
|
+
from fastapi_errors_plus.error_profile import ErrorProfile
|
|
4
6
|
from fastapi_errors_plus.errors import Errors
|
|
5
|
-
from fastapi_errors_plus.protocol import ErrorDTO
|
|
7
|
+
from fastapi_errors_plus.protocol import ErrorDTO, ErrorDTOLike, LegacyErrorDTO
|
|
6
8
|
|
|
7
9
|
__all__ = [
|
|
8
10
|
"Errors",
|
|
9
|
-
"ErrorDTO",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
11
|
+
"ErrorDTO",
|
|
12
|
+
"LegacyErrorDTO",
|
|
13
|
+
"ErrorDTOLike",
|
|
14
|
+
"ErrorDoc",
|
|
15
|
+
"ErrorProfile",
|
|
16
|
+
"BaseErrorDTO",
|
|
17
|
+
"StandardErrorDTO",
|
|
12
18
|
]
|
|
13
|
-
__version__ = "0.
|
|
14
|
-
|
|
19
|
+
__version__ = "0.9.0"
|
fastapi_errors_plus/base.py
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
"""Base implementations of ErrorDTO protocol for convenience."""
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from typing import Any, Dict, Optional
|
|
5
6
|
|
|
7
|
+
from fastapi_errors_plus.example_utils import ExampleSpec, _normalize_example_specs
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
@dataclass
|
|
8
11
|
class BaseErrorDTO:
|
|
9
12
|
"""Base implementation of ErrorDTO Protocol.
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
Projects can use this class directly or create their own implementation
|
|
12
15
|
that implements ErrorDTO Protocol through structural typing.
|
|
13
|
-
|
|
16
|
+
|
|
14
17
|
This class provides a simple way to create error DTOs without writing
|
|
15
18
|
boilerplate code in every project.
|
|
16
|
-
|
|
19
|
+
|
|
17
20
|
Example:
|
|
18
21
|
```python
|
|
19
22
|
from fastapi_errors_plus import Errors, BaseErrorDTO
|
|
20
|
-
|
|
23
|
+
|
|
21
24
|
notification_error = BaseErrorDTO(
|
|
22
25
|
status_code=404,
|
|
23
26
|
message="Notification not found",
|
|
24
27
|
)
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
@router.delete(
|
|
27
30
|
"/{id}",
|
|
28
31
|
responses=Errors(notification_error),
|
|
@@ -31,51 +34,54 @@ class BaseErrorDTO:
|
|
|
31
34
|
pass
|
|
32
35
|
```
|
|
33
36
|
"""
|
|
37
|
+
|
|
34
38
|
status_code: int
|
|
35
39
|
"""HTTP status code for the error."""
|
|
36
|
-
|
|
40
|
+
|
|
37
41
|
message: str
|
|
38
42
|
"""Error message description."""
|
|
39
|
-
|
|
43
|
+
|
|
44
|
+
model: Any = field(default=None, repr=False)
|
|
45
|
+
"""Optional Pydantic model for FastAPI ``responses`` (outer ``model=`` key)."""
|
|
46
|
+
|
|
47
|
+
schema: Optional[Dict[str, Any]] = field(default=None, repr=False)
|
|
48
|
+
"""Optional JSON Schema under ``content['application/json']['schema']``."""
|
|
49
|
+
|
|
40
50
|
openapi_json_extras: Optional[Dict[str, Any]] = field(default=None)
|
|
41
51
|
"""Optional OpenAPI fragment under ``content['application/json']`` besides examples,
|
|
42
52
|
typically ``{\"schema\": ...}`` or ``encoding``. Do **not** put ``example`` / ``examples``
|
|
43
53
|
here — use :meth:`to_example`. Arbitrary implementations may instead implement
|
|
44
54
|
:meth:`to_openapi_json_media_type_extras`; that return value wins over this attribute."""
|
|
45
|
-
|
|
46
|
-
def
|
|
47
|
-
"""Generate
|
|
48
|
-
|
|
49
|
-
Returns:
|
|
50
|
-
Dict in format: {"key": {"value": {"detail": "message"}}}
|
|
51
|
-
|
|
52
|
-
Example:
|
|
53
|
-
```python
|
|
54
|
-
{
|
|
55
|
-
"Notification not found": {
|
|
56
|
-
"value": {"detail": "Notification not found"},
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
"""
|
|
55
|
+
|
|
56
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
57
|
+
"""Generate OpenAPI ``examples`` for this error."""
|
|
61
58
|
return {
|
|
62
59
|
self.message: {
|
|
63
60
|
"value": {"detail": self.message},
|
|
64
61
|
},
|
|
65
62
|
}
|
|
66
63
|
|
|
64
|
+
def to_example(self) -> Dict[str, Any]:
|
|
65
|
+
"""Deprecated alias for :meth:`to_examples`."""
|
|
66
|
+
warnings.warn(
|
|
67
|
+
"to_example() is deprecated; use to_examples() instead.",
|
|
68
|
+
DeprecationWarning,
|
|
69
|
+
stacklevel=2,
|
|
70
|
+
)
|
|
71
|
+
return self.to_examples()
|
|
72
|
+
|
|
67
73
|
|
|
68
74
|
@dataclass
|
|
69
75
|
class StandardErrorDTO(BaseErrorDTO):
|
|
70
76
|
"""Extended implementation for errors with multiple examples.
|
|
71
|
-
|
|
77
|
+
|
|
72
78
|
Useful for standard HTTP errors (401, 403) that can have different
|
|
73
79
|
causes with different messages.
|
|
74
|
-
|
|
80
|
+
|
|
75
81
|
Example:
|
|
76
82
|
```python
|
|
77
83
|
from fastapi_errors_plus import Errors, StandardErrorDTO
|
|
78
|
-
|
|
84
|
+
|
|
79
85
|
unauthorized_error = StandardErrorDTO(
|
|
80
86
|
status_code=401,
|
|
81
87
|
message="Unauthorized",
|
|
@@ -84,7 +90,7 @@ class StandardErrorDTO(BaseErrorDTO):
|
|
|
84
90
|
"SessionNotFound": "Сессия пользователя не была найдена.",
|
|
85
91
|
},
|
|
86
92
|
)
|
|
87
|
-
|
|
93
|
+
|
|
88
94
|
@router.delete(
|
|
89
95
|
"/{id}",
|
|
90
96
|
responses=Errors(unauthorized_error),
|
|
@@ -93,39 +99,25 @@ class StandardErrorDTO(BaseErrorDTO):
|
|
|
93
99
|
pass
|
|
94
100
|
```
|
|
95
101
|
"""
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
|
|
103
|
+
examples: Optional[Dict[str, ExampleSpec]] = field(default=None)
|
|
104
|
+
"""Examples: ``str`` detail text or full OpenAPI example object (with ``summary``)."""
|
|
105
|
+
|
|
99
106
|
def __post_init__(self) -> None:
|
|
100
107
|
"""Initialize examples with default value if not provided."""
|
|
101
108
|
if self.examples is None:
|
|
102
109
|
self.examples = {self.message: self.message}
|
|
103
|
-
|
|
104
|
-
def to_example(self) -> Dict[str, Any]:
|
|
105
|
-
"""Generate examples for OpenAPI with multiple examples.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
Dict in format: {"key": {"value": {"detail": "message"}}, ...}
|
|
109
|
-
|
|
110
|
-
Example:
|
|
111
|
-
```python
|
|
112
|
-
{
|
|
113
|
-
"InvalidToken": {
|
|
114
|
-
"value": {"detail": "Ошибка декодирования токена."},
|
|
115
|
-
},
|
|
116
|
-
"SessionNotFound": {
|
|
117
|
-
"value": {"detail": "Сессия пользователя не была найдена."},
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
"""
|
|
122
|
-
return {
|
|
123
|
-
key: {
|
|
124
|
-
"value": {"detail": message},
|
|
125
|
-
}
|
|
126
|
-
for key, message in self.examples.items()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
110
|
|
|
111
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
112
|
+
"""Generate OpenAPI ``examples`` with optional summaries."""
|
|
113
|
+
assert self.examples is not None
|
|
114
|
+
return _normalize_example_specs(self.examples)
|
|
131
115
|
|
|
116
|
+
def to_example(self) -> Dict[str, Any]:
|
|
117
|
+
"""Deprecated alias for :meth:`to_examples`."""
|
|
118
|
+
warnings.warn(
|
|
119
|
+
"to_example() is deprecated; use to_examples() instead.",
|
|
120
|
+
DeprecationWarning,
|
|
121
|
+
stacklevel=2,
|
|
122
|
+
)
|
|
123
|
+
return self.to_examples()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Declarative error documentation for OpenAPI."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from fastapi_errors_plus.example_utils import ExampleSpec, _normalize_example_specs
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ErrorDoc:
|
|
12
|
+
"""Declarative ErrorDTO with arbitrary example bodies and optional summaries.
|
|
13
|
+
|
|
14
|
+
``examples`` values:
|
|
15
|
+
|
|
16
|
+
* **str** — shorthand ``{"detail": text}`` (same as bundled DTOs).
|
|
17
|
+
* **dict** with only OpenAPI Example Object keys (``value``, ``summary``, …) —
|
|
18
|
+
used as the Example Object.
|
|
19
|
+
* **any other dict** — treated as the full response **body** (e.g.
|
|
20
|
+
``{"code": "...", "detail": "...", "value": 150}``).
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
```python
|
|
24
|
+
ErrorDoc(
|
|
25
|
+
status_code=403,
|
|
26
|
+
message="Insufficient permissions",
|
|
27
|
+
examples={
|
|
28
|
+
"MissingRole": {
|
|
29
|
+
"summary": "User lacks required role",
|
|
30
|
+
"value": {"code": "FORBIDDEN", "detail": "Role admin required"},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
status_code: int
|
|
38
|
+
message: str
|
|
39
|
+
examples: Optional[Dict[str, ExampleSpec]] = None
|
|
40
|
+
body: Optional[Dict[str, Any]] = None
|
|
41
|
+
example_key: Optional[str] = None
|
|
42
|
+
model: Any = field(default=None, repr=False)
|
|
43
|
+
"""Optional Pydantic model for FastAPI ``responses`` (outer ``model=`` key)."""
|
|
44
|
+
|
|
45
|
+
schema: Optional[Dict[str, Any]] = field(default=None, repr=False)
|
|
46
|
+
"""Optional JSON Schema under ``content['application/json']['schema']``."""
|
|
47
|
+
|
|
48
|
+
openapi_json_extras: Optional[Dict[str, Any]] = field(default=None)
|
|
49
|
+
|
|
50
|
+
def to_examples(self) -> Dict[str, Any]:
|
|
51
|
+
"""Build OpenAPI ``examples`` map for ``application/json``."""
|
|
52
|
+
if self.examples is not None:
|
|
53
|
+
return _normalize_example_specs(self.examples)
|
|
54
|
+
key = self.example_key or self.message
|
|
55
|
+
value = self.body if self.body is not None else {"detail": self.message}
|
|
56
|
+
return {key: {"value": value}}
|
|
57
|
+
|
|
58
|
+
def to_example(self) -> Dict[str, Any]:
|
|
59
|
+
"""Deprecated alias for :meth:`to_examples`."""
|
|
60
|
+
warnings.warn(
|
|
61
|
+
"to_example() is deprecated; use to_examples() instead.",
|
|
62
|
+
DeprecationWarning,
|
|
63
|
+
stacklevel=2,
|
|
64
|
+
)
|
|
65
|
+
return self.to_examples()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Project-wide defaults for :class:`Errors` (release 0.9)."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class ErrorProfile:
|
|
9
|
+
"""Immutable project defaults applied before per-endpoint ``Errors(...)`` args.
|
|
10
|
+
|
|
11
|
+
Explicit ``Errors`` keyword flags override profile values. Positional dict/DTO
|
|
12
|
+
errors are merged after profile-driven standard statuses.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
```python
|
|
16
|
+
ADR = ErrorProfile(
|
|
17
|
+
validation_error_422=False,
|
|
18
|
+
unauthorized_401=True,
|
|
19
|
+
internal_server_error_500=True,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
@router.post("/items", responses=Errors(business_conflict, profile=ADR))
|
|
23
|
+
def create_item():
|
|
24
|
+
...
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
unauthorized_401: bool = False
|
|
29
|
+
forbidden_403: bool = False
|
|
30
|
+
validation_error_422: Optional[bool] = None
|
|
31
|
+
internal_server_error_500: bool = False
|