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.
@@ -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", # Protocol (for structural typing)
10
- "BaseErrorDTO", # Base implementation (optional)
11
- "StandardErrorDTO", # Extended implementation (optional)
11
+ "ErrorDTO",
12
+ "LegacyErrorDTO",
13
+ "ErrorDTOLike",
14
+ "ErrorDoc",
15
+ "ErrorProfile",
16
+ "BaseErrorDTO",
17
+ "StandardErrorDTO",
12
18
  ]
13
- __version__ = "0.7.0"
14
-
19
+ __version__ = "0.9.0"
@@ -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 to_example(self) -> Dict[str, Any]:
47
- """Generate example for OpenAPI.
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
- examples: Optional[Dict[str, str]] = field(default=None)
97
- """Dictionary of examples: {"key": "message"}."""
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