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.

Files changed (128) hide show
  1. django_bolt/__init__.py +147 -0
  2. django_bolt/_core.abi3.so +0 -0
  3. django_bolt/admin/__init__.py +25 -0
  4. django_bolt/admin/admin_detection.py +179 -0
  5. django_bolt/admin/asgi_bridge.py +267 -0
  6. django_bolt/admin/routes.py +91 -0
  7. django_bolt/admin/static.py +155 -0
  8. django_bolt/admin/static_routes.py +111 -0
  9. django_bolt/api.py +1011 -0
  10. django_bolt/apps.py +7 -0
  11. django_bolt/async_collector.py +228 -0
  12. django_bolt/auth/README.md +464 -0
  13. django_bolt/auth/REVOCATION_EXAMPLE.md +391 -0
  14. django_bolt/auth/__init__.py +84 -0
  15. django_bolt/auth/backends.py +236 -0
  16. django_bolt/auth/guards.py +224 -0
  17. django_bolt/auth/jwt_utils.py +212 -0
  18. django_bolt/auth/revocation.py +286 -0
  19. django_bolt/auth/token.py +335 -0
  20. django_bolt/binding.py +363 -0
  21. django_bolt/bootstrap.py +77 -0
  22. django_bolt/cli.py +133 -0
  23. django_bolt/compression.py +104 -0
  24. django_bolt/decorators.py +159 -0
  25. django_bolt/dependencies.py +128 -0
  26. django_bolt/error_handlers.py +305 -0
  27. django_bolt/exceptions.py +294 -0
  28. django_bolt/health.py +129 -0
  29. django_bolt/logging/__init__.py +6 -0
  30. django_bolt/logging/config.py +357 -0
  31. django_bolt/logging/middleware.py +296 -0
  32. django_bolt/management/__init__.py +1 -0
  33. django_bolt/management/commands/__init__.py +0 -0
  34. django_bolt/management/commands/runbolt.py +427 -0
  35. django_bolt/middleware/__init__.py +32 -0
  36. django_bolt/middleware/compiler.py +131 -0
  37. django_bolt/middleware/middleware.py +247 -0
  38. django_bolt/openapi/__init__.py +23 -0
  39. django_bolt/openapi/config.py +196 -0
  40. django_bolt/openapi/plugins.py +439 -0
  41. django_bolt/openapi/routes.py +152 -0
  42. django_bolt/openapi/schema_generator.py +581 -0
  43. django_bolt/openapi/spec/__init__.py +68 -0
  44. django_bolt/openapi/spec/base.py +74 -0
  45. django_bolt/openapi/spec/callback.py +24 -0
  46. django_bolt/openapi/spec/components.py +72 -0
  47. django_bolt/openapi/spec/contact.py +21 -0
  48. django_bolt/openapi/spec/discriminator.py +25 -0
  49. django_bolt/openapi/spec/encoding.py +67 -0
  50. django_bolt/openapi/spec/enums.py +41 -0
  51. django_bolt/openapi/spec/example.py +36 -0
  52. django_bolt/openapi/spec/external_documentation.py +21 -0
  53. django_bolt/openapi/spec/header.py +132 -0
  54. django_bolt/openapi/spec/info.py +50 -0
  55. django_bolt/openapi/spec/license.py +28 -0
  56. django_bolt/openapi/spec/link.py +66 -0
  57. django_bolt/openapi/spec/media_type.py +51 -0
  58. django_bolt/openapi/spec/oauth_flow.py +36 -0
  59. django_bolt/openapi/spec/oauth_flows.py +28 -0
  60. django_bolt/openapi/spec/open_api.py +87 -0
  61. django_bolt/openapi/spec/operation.py +105 -0
  62. django_bolt/openapi/spec/parameter.py +147 -0
  63. django_bolt/openapi/spec/path_item.py +78 -0
  64. django_bolt/openapi/spec/paths.py +27 -0
  65. django_bolt/openapi/spec/reference.py +38 -0
  66. django_bolt/openapi/spec/request_body.py +38 -0
  67. django_bolt/openapi/spec/response.py +48 -0
  68. django_bolt/openapi/spec/responses.py +44 -0
  69. django_bolt/openapi/spec/schema.py +678 -0
  70. django_bolt/openapi/spec/security_requirement.py +28 -0
  71. django_bolt/openapi/spec/security_scheme.py +69 -0
  72. django_bolt/openapi/spec/server.py +34 -0
  73. django_bolt/openapi/spec/server_variable.py +32 -0
  74. django_bolt/openapi/spec/tag.py +32 -0
  75. django_bolt/openapi/spec/xml.py +44 -0
  76. django_bolt/pagination.py +669 -0
  77. django_bolt/param_functions.py +49 -0
  78. django_bolt/params.py +337 -0
  79. django_bolt/request_parsing.py +128 -0
  80. django_bolt/responses.py +214 -0
  81. django_bolt/router.py +48 -0
  82. django_bolt/serialization.py +193 -0
  83. django_bolt/status_codes.py +321 -0
  84. django_bolt/testing/__init__.py +10 -0
  85. django_bolt/testing/client.py +274 -0
  86. django_bolt/testing/helpers.py +93 -0
  87. django_bolt/tests/__init__.py +0 -0
  88. django_bolt/tests/admin_tests/__init__.py +1 -0
  89. django_bolt/tests/admin_tests/conftest.py +6 -0
  90. django_bolt/tests/admin_tests/test_admin_with_django.py +278 -0
  91. django_bolt/tests/admin_tests/urls.py +9 -0
  92. django_bolt/tests/cbv/__init__.py +0 -0
  93. django_bolt/tests/cbv/test_class_views.py +570 -0
  94. django_bolt/tests/cbv/test_class_views_django_orm.py +703 -0
  95. django_bolt/tests/cbv/test_class_views_features.py +1173 -0
  96. django_bolt/tests/cbv/test_class_views_with_client.py +622 -0
  97. django_bolt/tests/conftest.py +165 -0
  98. django_bolt/tests/test_action_decorator.py +399 -0
  99. django_bolt/tests/test_auth_secret_key.py +83 -0
  100. django_bolt/tests/test_decorator_syntax.py +159 -0
  101. django_bolt/tests/test_error_handling.py +481 -0
  102. django_bolt/tests/test_file_response.py +192 -0
  103. django_bolt/tests/test_global_cors.py +172 -0
  104. django_bolt/tests/test_guards_auth.py +441 -0
  105. django_bolt/tests/test_guards_integration.py +303 -0
  106. django_bolt/tests/test_health.py +283 -0
  107. django_bolt/tests/test_integration_validation.py +400 -0
  108. django_bolt/tests/test_json_validation.py +536 -0
  109. django_bolt/tests/test_jwt_auth.py +327 -0
  110. django_bolt/tests/test_jwt_token.py +458 -0
  111. django_bolt/tests/test_logging.py +837 -0
  112. django_bolt/tests/test_logging_merge.py +419 -0
  113. django_bolt/tests/test_middleware.py +492 -0
  114. django_bolt/tests/test_middleware_server.py +230 -0
  115. django_bolt/tests/test_model_viewset.py +323 -0
  116. django_bolt/tests/test_models.py +24 -0
  117. django_bolt/tests/test_pagination.py +1258 -0
  118. django_bolt/tests/test_parameter_validation.py +178 -0
  119. django_bolt/tests/test_syntax.py +626 -0
  120. django_bolt/tests/test_testing_utilities.py +163 -0
  121. django_bolt/tests/test_testing_utilities_simple.py +123 -0
  122. django_bolt/tests/test_viewset_unified.py +346 -0
  123. django_bolt/typing.py +273 -0
  124. django_bolt/views.py +1110 -0
  125. django_bolt-0.1.0.dist-info/METADATA +629 -0
  126. django_bolt-0.1.0.dist-info/RECORD +128 -0
  127. django_bolt-0.1.0.dist-info/WHEEL +4 -0
  128. 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
+ )