django-bolt 0.1.0__cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
Potentially problematic release.
This version of django-bolt might be problematic. Click here for more details.
- django_bolt/__init__.py +147 -0
- django_bolt/_core.abi3.so +0 -0
- django_bolt/admin/__init__.py +25 -0
- django_bolt/admin/admin_detection.py +179 -0
- django_bolt/admin/asgi_bridge.py +267 -0
- django_bolt/admin/routes.py +91 -0
- django_bolt/admin/static.py +155 -0
- django_bolt/admin/static_routes.py +111 -0
- django_bolt/api.py +1011 -0
- django_bolt/apps.py +7 -0
- django_bolt/async_collector.py +228 -0
- django_bolt/auth/README.md +464 -0
- django_bolt/auth/REVOCATION_EXAMPLE.md +391 -0
- django_bolt/auth/__init__.py +84 -0
- django_bolt/auth/backends.py +236 -0
- django_bolt/auth/guards.py +224 -0
- django_bolt/auth/jwt_utils.py +212 -0
- django_bolt/auth/revocation.py +286 -0
- django_bolt/auth/token.py +335 -0
- django_bolt/binding.py +363 -0
- django_bolt/bootstrap.py +77 -0
- django_bolt/cli.py +133 -0
- django_bolt/compression.py +104 -0
- django_bolt/decorators.py +159 -0
- django_bolt/dependencies.py +128 -0
- django_bolt/error_handlers.py +305 -0
- django_bolt/exceptions.py +294 -0
- django_bolt/health.py +129 -0
- django_bolt/logging/__init__.py +6 -0
- django_bolt/logging/config.py +357 -0
- django_bolt/logging/middleware.py +296 -0
- django_bolt/management/__init__.py +1 -0
- django_bolt/management/commands/__init__.py +0 -0
- django_bolt/management/commands/runbolt.py +427 -0
- django_bolt/middleware/__init__.py +32 -0
- django_bolt/middleware/compiler.py +131 -0
- django_bolt/middleware/middleware.py +247 -0
- django_bolt/openapi/__init__.py +23 -0
- django_bolt/openapi/config.py +196 -0
- django_bolt/openapi/plugins.py +439 -0
- django_bolt/openapi/routes.py +152 -0
- django_bolt/openapi/schema_generator.py +581 -0
- django_bolt/openapi/spec/__init__.py +68 -0
- django_bolt/openapi/spec/base.py +74 -0
- django_bolt/openapi/spec/callback.py +24 -0
- django_bolt/openapi/spec/components.py +72 -0
- django_bolt/openapi/spec/contact.py +21 -0
- django_bolt/openapi/spec/discriminator.py +25 -0
- django_bolt/openapi/spec/encoding.py +67 -0
- django_bolt/openapi/spec/enums.py +41 -0
- django_bolt/openapi/spec/example.py +36 -0
- django_bolt/openapi/spec/external_documentation.py +21 -0
- django_bolt/openapi/spec/header.py +132 -0
- django_bolt/openapi/spec/info.py +50 -0
- django_bolt/openapi/spec/license.py +28 -0
- django_bolt/openapi/spec/link.py +66 -0
- django_bolt/openapi/spec/media_type.py +51 -0
- django_bolt/openapi/spec/oauth_flow.py +36 -0
- django_bolt/openapi/spec/oauth_flows.py +28 -0
- django_bolt/openapi/spec/open_api.py +87 -0
- django_bolt/openapi/spec/operation.py +105 -0
- django_bolt/openapi/spec/parameter.py +147 -0
- django_bolt/openapi/spec/path_item.py +78 -0
- django_bolt/openapi/spec/paths.py +27 -0
- django_bolt/openapi/spec/reference.py +38 -0
- django_bolt/openapi/spec/request_body.py +38 -0
- django_bolt/openapi/spec/response.py +48 -0
- django_bolt/openapi/spec/responses.py +44 -0
- django_bolt/openapi/spec/schema.py +678 -0
- django_bolt/openapi/spec/security_requirement.py +28 -0
- django_bolt/openapi/spec/security_scheme.py +69 -0
- django_bolt/openapi/spec/server.py +34 -0
- django_bolt/openapi/spec/server_variable.py +32 -0
- django_bolt/openapi/spec/tag.py +32 -0
- django_bolt/openapi/spec/xml.py +44 -0
- django_bolt/pagination.py +669 -0
- django_bolt/param_functions.py +49 -0
- django_bolt/params.py +337 -0
- django_bolt/request_parsing.py +128 -0
- django_bolt/responses.py +214 -0
- django_bolt/router.py +48 -0
- django_bolt/serialization.py +193 -0
- django_bolt/status_codes.py +321 -0
- django_bolt/testing/__init__.py +10 -0
- django_bolt/testing/client.py +274 -0
- django_bolt/testing/helpers.py +93 -0
- django_bolt/tests/__init__.py +0 -0
- django_bolt/tests/admin_tests/__init__.py +1 -0
- django_bolt/tests/admin_tests/conftest.py +6 -0
- django_bolt/tests/admin_tests/test_admin_with_django.py +278 -0
- django_bolt/tests/admin_tests/urls.py +9 -0
- django_bolt/tests/cbv/__init__.py +0 -0
- django_bolt/tests/cbv/test_class_views.py +570 -0
- django_bolt/tests/cbv/test_class_views_django_orm.py +703 -0
- django_bolt/tests/cbv/test_class_views_features.py +1173 -0
- django_bolt/tests/cbv/test_class_views_with_client.py +622 -0
- django_bolt/tests/conftest.py +165 -0
- django_bolt/tests/test_action_decorator.py +399 -0
- django_bolt/tests/test_auth_secret_key.py +83 -0
- django_bolt/tests/test_decorator_syntax.py +159 -0
- django_bolt/tests/test_error_handling.py +481 -0
- django_bolt/tests/test_file_response.py +192 -0
- django_bolt/tests/test_global_cors.py +172 -0
- django_bolt/tests/test_guards_auth.py +441 -0
- django_bolt/tests/test_guards_integration.py +303 -0
- django_bolt/tests/test_health.py +283 -0
- django_bolt/tests/test_integration_validation.py +400 -0
- django_bolt/tests/test_json_validation.py +536 -0
- django_bolt/tests/test_jwt_auth.py +327 -0
- django_bolt/tests/test_jwt_token.py +458 -0
- django_bolt/tests/test_logging.py +837 -0
- django_bolt/tests/test_logging_merge.py +419 -0
- django_bolt/tests/test_middleware.py +492 -0
- django_bolt/tests/test_middleware_server.py +230 -0
- django_bolt/tests/test_model_viewset.py +323 -0
- django_bolt/tests/test_models.py +24 -0
- django_bolt/tests/test_pagination.py +1258 -0
- django_bolt/tests/test_parameter_validation.py +178 -0
- django_bolt/tests/test_syntax.py +626 -0
- django_bolt/tests/test_testing_utilities.py +163 -0
- django_bolt/tests/test_testing_utilities_simple.py +123 -0
- django_bolt/tests/test_viewset_unified.py +346 -0
- django_bolt/typing.py +273 -0
- django_bolt/views.py +1110 -0
- django_bolt-0.1.0.dist-info/METADATA +629 -0
- django_bolt-0.1.0.dist-info/RECORD +128 -0
- django_bolt-0.1.0.dist-info/WHEEL +4 -0
- django_bolt-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Middleware system for Django-Bolt.
|
|
3
|
+
|
|
4
|
+
Provides both decorator-based and class-based middleware approaches.
|
|
5
|
+
Middleware can be applied globally to all routes or selectively to specific routes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
|
11
|
+
import inspect
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Middleware(ABC):
|
|
15
|
+
"""Base class for middleware implementations."""
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def process_request(self, request: Any, call_next: Callable) -> Any:
|
|
19
|
+
"""
|
|
20
|
+
Process the request and optionally call the next middleware/handler.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
request: The incoming request object
|
|
24
|
+
call_next: Callable to invoke the next middleware or handler
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Response object or result from call_next
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MiddlewareGroup:
|
|
33
|
+
"""Groups multiple middleware for reuse across routes."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, *middleware: Union[Middleware, 'MiddlewareConfig']):
|
|
36
|
+
self.middleware = list(middleware)
|
|
37
|
+
|
|
38
|
+
def __add__(self, other: 'MiddlewareGroup') -> 'MiddlewareGroup':
|
|
39
|
+
"""Combine middleware groups."""
|
|
40
|
+
return MiddlewareGroup(*(self.middleware + other.middleware))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class MiddlewareConfig:
|
|
45
|
+
"""Configuration for a middleware instance with metadata."""
|
|
46
|
+
|
|
47
|
+
middleware: Union[type, Middleware]
|
|
48
|
+
config: Dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
skip_routes: Set[str] = field(default_factory=set)
|
|
50
|
+
only_routes: Optional[Set[str]] = None
|
|
51
|
+
|
|
52
|
+
def applies_to_route(self, route_key: str) -> bool:
|
|
53
|
+
"""Check if middleware should apply to a specific route."""
|
|
54
|
+
if route_key in self.skip_routes:
|
|
55
|
+
return False
|
|
56
|
+
if self.only_routes is not None and route_key not in self.only_routes:
|
|
57
|
+
return False
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def middleware(*args, **kwargs):
|
|
62
|
+
"""
|
|
63
|
+
Decorator to attach middleware to a route handler.
|
|
64
|
+
|
|
65
|
+
Can be used as:
|
|
66
|
+
- @middleware(RateLimitMiddleware(rps=100))
|
|
67
|
+
- @middleware(cors={"origins": ["*"]})
|
|
68
|
+
- @middleware(skip=["auth"])
|
|
69
|
+
"""
|
|
70
|
+
def decorator(func):
|
|
71
|
+
if not hasattr(func, '__bolt_middleware__'):
|
|
72
|
+
func.__bolt_middleware__ = []
|
|
73
|
+
|
|
74
|
+
for arg in args:
|
|
75
|
+
if isinstance(arg, (Middleware, MiddlewareConfig, type)):
|
|
76
|
+
func.__bolt_middleware__.append(arg)
|
|
77
|
+
elif isinstance(arg, MiddlewareGroup):
|
|
78
|
+
func.__bolt_middleware__.extend(arg.middleware)
|
|
79
|
+
|
|
80
|
+
if kwargs:
|
|
81
|
+
func.__bolt_middleware__.append(kwargs)
|
|
82
|
+
|
|
83
|
+
return func
|
|
84
|
+
|
|
85
|
+
# Support both @middleware and @middleware()
|
|
86
|
+
if len(args) == 1 and callable(args[0]) and not isinstance(args[0], (Middleware, type)):
|
|
87
|
+
return decorator(args[0])
|
|
88
|
+
return decorator
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def rate_limit(rps: int = 100, burst: int = None, key: str = "ip"):
|
|
92
|
+
"""
|
|
93
|
+
Rate limiting decorator.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
rps: Requests per second limit
|
|
97
|
+
burst: Burst capacity (defaults to 2x rps)
|
|
98
|
+
key: Rate limit key strategy ("ip", "user", "api_key", or header name)
|
|
99
|
+
"""
|
|
100
|
+
def decorator(func):
|
|
101
|
+
if not hasattr(func, '__bolt_middleware__'):
|
|
102
|
+
func.__bolt_middleware__ = []
|
|
103
|
+
func.__bolt_middleware__.append({
|
|
104
|
+
'type': 'rate_limit',
|
|
105
|
+
'rps': rps,
|
|
106
|
+
'burst': burst or rps * 2,
|
|
107
|
+
'key': key
|
|
108
|
+
})
|
|
109
|
+
return func
|
|
110
|
+
return decorator
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def cors(
|
|
114
|
+
origins: Union[List[str], str] = None,
|
|
115
|
+
methods: List[str] = None,
|
|
116
|
+
headers: List[str] = None,
|
|
117
|
+
credentials: bool = False,
|
|
118
|
+
max_age: int = 3600
|
|
119
|
+
):
|
|
120
|
+
"""
|
|
121
|
+
CORS configuration decorator.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
origins: Allowed origins. Use Django setting BOLT_CORS_ALLOWED_ORIGINS for global config.
|
|
125
|
+
Default is empty list (no origins allowed) for security.
|
|
126
|
+
methods: Allowed methods
|
|
127
|
+
headers: Allowed headers
|
|
128
|
+
credentials: Allow credentials (cannot be combined with wildcard "*")
|
|
129
|
+
max_age: Preflight cache duration
|
|
130
|
+
|
|
131
|
+
Security Notes:
|
|
132
|
+
- Default changed from ["*"] to [] (empty) for better security
|
|
133
|
+
- Wildcard "*" with credentials=True is not allowed (violates CORS spec)
|
|
134
|
+
- Configure BOLT_CORS_ALLOWED_ORIGINS in Django settings for global origins
|
|
135
|
+
"""
|
|
136
|
+
def decorator(func):
|
|
137
|
+
if not hasattr(func, '__bolt_middleware__'):
|
|
138
|
+
func.__bolt_middleware__ = []
|
|
139
|
+
|
|
140
|
+
# Parse origins
|
|
141
|
+
origin_list = origins if isinstance(origins, list) else [origins] if origins else []
|
|
142
|
+
|
|
143
|
+
# SECURITY: Validate wildcard + credentials
|
|
144
|
+
if "*" in origin_list and credentials:
|
|
145
|
+
import warnings
|
|
146
|
+
warnings.warn(
|
|
147
|
+
"CORS misconfiguration: Cannot use wildcard '*' with credentials=True. "
|
|
148
|
+
"This violates the CORS specification. Please specify explicit origins.",
|
|
149
|
+
RuntimeWarning,
|
|
150
|
+
stacklevel=2
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
func.__bolt_middleware__.append({
|
|
154
|
+
'type': 'cors',
|
|
155
|
+
'origins': origin_list,
|
|
156
|
+
'methods': methods or ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
157
|
+
'headers': headers,
|
|
158
|
+
'credentials': credentials,
|
|
159
|
+
'max_age': max_age
|
|
160
|
+
})
|
|
161
|
+
return func
|
|
162
|
+
return decorator
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def skip_middleware(*middleware_names: str):
|
|
168
|
+
"""
|
|
169
|
+
Skip specific global middleware for this route.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
middleware_names: Names of middleware to skip (e.g., "cors", "rate_limit", "compression")
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
@api.get("/no-compression")
|
|
176
|
+
@skip_middleware("compression")
|
|
177
|
+
async def no_compress():
|
|
178
|
+
return {"data": "large response without compression"}
|
|
179
|
+
|
|
180
|
+
@api.get("/minimal")
|
|
181
|
+
@skip_middleware("cors", "compression")
|
|
182
|
+
async def minimal():
|
|
183
|
+
return {"fast": True}
|
|
184
|
+
"""
|
|
185
|
+
def decorator(func):
|
|
186
|
+
if not hasattr(func, '__bolt_skip_middleware__'):
|
|
187
|
+
func.__bolt_skip_middleware__ = set()
|
|
188
|
+
func.__bolt_skip_middleware__.update(middleware_names)
|
|
189
|
+
return func
|
|
190
|
+
return decorator
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def no_compress(func):
|
|
194
|
+
"""
|
|
195
|
+
Disable compression for this route.
|
|
196
|
+
|
|
197
|
+
Shorthand for @skip_middleware("compression").
|
|
198
|
+
|
|
199
|
+
Examples:
|
|
200
|
+
@api.get("/stream")
|
|
201
|
+
@no_compress
|
|
202
|
+
async def stream_data():
|
|
203
|
+
# Compression would slow down streaming
|
|
204
|
+
return StreamingResponse(...)
|
|
205
|
+
"""
|
|
206
|
+
return skip_middleware("compression")(func)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class CORSMiddleware(Middleware):
|
|
210
|
+
"""Built-in CORS middleware implementation."""
|
|
211
|
+
|
|
212
|
+
def __init__(
|
|
213
|
+
self,
|
|
214
|
+
origins: List[str] = None,
|
|
215
|
+
methods: List[str] = None,
|
|
216
|
+
headers: List[str] = None,
|
|
217
|
+
credentials: bool = False,
|
|
218
|
+
max_age: int = 3600
|
|
219
|
+
):
|
|
220
|
+
self.origins = origins or ["*"]
|
|
221
|
+
self.methods = methods or ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
222
|
+
self.headers = headers or ["Content-Type", "Authorization"]
|
|
223
|
+
self.credentials = credentials
|
|
224
|
+
self.max_age = max_age
|
|
225
|
+
|
|
226
|
+
async def process_request(self, request: Any, call_next: Callable) -> Any:
|
|
227
|
+
# This will be handled in Rust for performance
|
|
228
|
+
# Python implementation is for compatibility
|
|
229
|
+
response = await call_next(request)
|
|
230
|
+
return response
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class RateLimitMiddleware(Middleware):
|
|
234
|
+
"""Built-in rate limiting middleware implementation."""
|
|
235
|
+
|
|
236
|
+
def __init__(self, rps: int = 100, burst: int = None, key: str = "ip"):
|
|
237
|
+
self.rps = rps
|
|
238
|
+
self.burst = burst or rps * 2
|
|
239
|
+
self.key = key
|
|
240
|
+
|
|
241
|
+
async def process_request(self, request: Any, call_next: Callable) -> Any:
|
|
242
|
+
# This will be handled in Rust for performance
|
|
243
|
+
# Python implementation is for compatibility/fallback
|
|
244
|
+
response = await call_next(request)
|
|
245
|
+
return response
|
|
246
|
+
|
|
247
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# OpenAPI module used from litestar (https://github.com/litestar-org/litestar) adapted for django-bolt
|
|
2
|
+
|
|
3
|
+
from .config import OpenAPIConfig
|
|
4
|
+
from .plugins import (
|
|
5
|
+
SwaggerRenderPlugin,
|
|
6
|
+
RedocRenderPlugin,
|
|
7
|
+
ScalarRenderPlugin,
|
|
8
|
+
RapidocRenderPlugin,
|
|
9
|
+
StoplightRenderPlugin,
|
|
10
|
+
JsonRenderPlugin,
|
|
11
|
+
YamlRenderPlugin,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"OpenAPIConfig",
|
|
16
|
+
"SwaggerRenderPlugin",
|
|
17
|
+
"RedocRenderPlugin",
|
|
18
|
+
"ScalarRenderPlugin",
|
|
19
|
+
"RapidocRenderPlugin",
|
|
20
|
+
"StoplightRenderPlugin",
|
|
21
|
+
"JsonRenderPlugin",
|
|
22
|
+
"YamlRenderPlugin",
|
|
23
|
+
]
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional, List, Dict
|
|
5
|
+
|
|
6
|
+
from .spec import (
|
|
7
|
+
Components,
|
|
8
|
+
Contact,
|
|
9
|
+
ExternalDocumentation,
|
|
10
|
+
Info,
|
|
11
|
+
License,
|
|
12
|
+
OpenAPI,
|
|
13
|
+
PathItem,
|
|
14
|
+
Reference,
|
|
15
|
+
SecurityRequirement,
|
|
16
|
+
Server,
|
|
17
|
+
Tag,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .plugins import OpenAPIRenderPlugin
|
|
22
|
+
|
|
23
|
+
__all__ = ("OpenAPIConfig",)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class OpenAPIConfig:
|
|
28
|
+
"""Configuration for OpenAPI documentation generation.
|
|
29
|
+
|
|
30
|
+
Pass an instance of this class to BoltAPI to enable OpenAPI schema
|
|
31
|
+
generation and interactive documentation UIs.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
```python
|
|
35
|
+
from django_bolt import BoltAPI
|
|
36
|
+
from django_bolt.openapi import OpenAPIConfig, SwaggerRenderPlugin
|
|
37
|
+
|
|
38
|
+
api = BoltAPI(
|
|
39
|
+
openapi_config=OpenAPIConfig(
|
|
40
|
+
title="My API",
|
|
41
|
+
version="1.0.0",
|
|
42
|
+
render_plugins=[SwaggerRenderPlugin()]
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
title: str
|
|
49
|
+
"""Title of API documentation."""
|
|
50
|
+
|
|
51
|
+
version: str
|
|
52
|
+
"""API version, e.g. '1.0.0'."""
|
|
53
|
+
|
|
54
|
+
contact: Optional[Contact] = field(default=None)
|
|
55
|
+
"""API contact information."""
|
|
56
|
+
|
|
57
|
+
description: Optional[str] = field(default=None)
|
|
58
|
+
"""API description."""
|
|
59
|
+
|
|
60
|
+
external_docs: Optional[ExternalDocumentation] = field(default=None)
|
|
61
|
+
"""Links to external documentation."""
|
|
62
|
+
|
|
63
|
+
license: Optional[License] = field(default=None)
|
|
64
|
+
"""API licensing information."""
|
|
65
|
+
|
|
66
|
+
security: Optional[List[SecurityRequirement]] = field(default=None)
|
|
67
|
+
"""API security requirements."""
|
|
68
|
+
|
|
69
|
+
components: Components = field(default_factory=Components)
|
|
70
|
+
"""API components (schemas, security schemes, etc.)."""
|
|
71
|
+
|
|
72
|
+
servers: List[Server] = field(default_factory=lambda: [Server(url="/")])
|
|
73
|
+
"""A list of Server instances."""
|
|
74
|
+
|
|
75
|
+
summary: Optional[str] = field(default=None)
|
|
76
|
+
"""A summary text."""
|
|
77
|
+
|
|
78
|
+
tags: Optional[List[Tag]] = field(default=None)
|
|
79
|
+
"""A list of Tag instances for grouping operations."""
|
|
80
|
+
|
|
81
|
+
terms_of_service: Optional[str] = field(default=None)
|
|
82
|
+
"""URL to page that contains terms of service."""
|
|
83
|
+
|
|
84
|
+
use_handler_docstrings: bool = field(default=True)
|
|
85
|
+
"""Draw operation description from route handler docstring if not otherwise provided."""
|
|
86
|
+
|
|
87
|
+
webhooks: Optional[Dict[str, PathItem | Reference]] = field(default=None)
|
|
88
|
+
"""A mapping of webhook name to PathItem or Reference."""
|
|
89
|
+
|
|
90
|
+
path: str = "/schema"
|
|
91
|
+
"""Base path for the OpenAPI documentation endpoints."""
|
|
92
|
+
|
|
93
|
+
render_plugins: List[OpenAPIRenderPlugin] = field(default_factory=lambda: [])
|
|
94
|
+
"""Plugins for rendering OpenAPI documentation UIs.
|
|
95
|
+
|
|
96
|
+
If empty, ScalarRenderPlugin will be used by default.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
exclude_paths: List[str] = field(default_factory=lambda: ["/admin", "/static"])
|
|
100
|
+
"""Path prefixes to exclude from OpenAPI schema generation.
|
|
101
|
+
|
|
102
|
+
By default excludes Django admin (/admin) and static files (/static).
|
|
103
|
+
The OpenAPI docs path (self.path) is always excluded automatically.
|
|
104
|
+
Set to empty list [] to include all routes, or customize as needed.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
```python
|
|
108
|
+
# Exclude additional paths
|
|
109
|
+
OpenAPIConfig(
|
|
110
|
+
title="My API",
|
|
111
|
+
version="1.0.0",
|
|
112
|
+
exclude_paths=["/admin", "/static", "/internal"]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Include everything except docs
|
|
116
|
+
OpenAPIConfig(
|
|
117
|
+
title="My API",
|
|
118
|
+
version="1.0.0",
|
|
119
|
+
exclude_paths=[]
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
include_error_responses: bool = field(default=True)
|
|
125
|
+
"""Include 422 validation error responses in OpenAPI schema.
|
|
126
|
+
|
|
127
|
+
When enabled, the schema will automatically document 422 Unprocessable Entity
|
|
128
|
+
responses for endpoints that accept request bodies (JSON, form data, file uploads).
|
|
129
|
+
|
|
130
|
+
The 422 response includes detailed validation error information with field-level
|
|
131
|
+
error messages, making it easier for API consumers to understand what went wrong.
|
|
132
|
+
|
|
133
|
+
Set to False to only document successful responses.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
```python
|
|
137
|
+
# Include 422 validation errors (default)
|
|
138
|
+
OpenAPIConfig(
|
|
139
|
+
title="My API",
|
|
140
|
+
version="1.0.0",
|
|
141
|
+
include_error_responses=True
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Only show successful responses
|
|
145
|
+
OpenAPIConfig(
|
|
146
|
+
title="My API",
|
|
147
|
+
version="1.0.0",
|
|
148
|
+
include_error_responses=False
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __post_init__(self) -> None:
|
|
154
|
+
"""Initialize default render plugin if none provided."""
|
|
155
|
+
if not self.render_plugins:
|
|
156
|
+
from .plugins import ScalarRenderPlugin
|
|
157
|
+
self.render_plugins = [ScalarRenderPlugin()]
|
|
158
|
+
|
|
159
|
+
# Normalize path
|
|
160
|
+
self.path = "/" + self.path.strip("/")
|
|
161
|
+
|
|
162
|
+
# Find default plugin (one that serves root path)
|
|
163
|
+
self.default_plugin: Optional[OpenAPIRenderPlugin] = None
|
|
164
|
+
for plugin in self.render_plugins:
|
|
165
|
+
if plugin.has_path("/"):
|
|
166
|
+
self.default_plugin = plugin
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
# If no root plugin, use first plugin as default
|
|
170
|
+
if not self.default_plugin and self.render_plugins:
|
|
171
|
+
self.default_plugin = self.render_plugins[0]
|
|
172
|
+
|
|
173
|
+
def to_openapi_schema(self) -> OpenAPI:
|
|
174
|
+
"""Convert config to OpenAPI schema object.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
An OpenAPI instance with info populated from config.
|
|
178
|
+
"""
|
|
179
|
+
return OpenAPI(
|
|
180
|
+
external_docs=self.external_docs,
|
|
181
|
+
security=self.security,
|
|
182
|
+
components=self.components,
|
|
183
|
+
servers=self.servers,
|
|
184
|
+
tags=self.tags,
|
|
185
|
+
webhooks=self.webhooks,
|
|
186
|
+
info=Info(
|
|
187
|
+
title=self.title,
|
|
188
|
+
version=self.version,
|
|
189
|
+
description=self.description,
|
|
190
|
+
contact=self.contact,
|
|
191
|
+
license=self.license,
|
|
192
|
+
summary=self.summary,
|
|
193
|
+
terms_of_service=self.terms_of_service,
|
|
194
|
+
),
|
|
195
|
+
paths={}, # Will be populated by schema generator
|
|
196
|
+
)
|