django-bolt 0.1.0__cp310-abi3-win_amd64.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.pyd +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,294 @@
1
+ from typing import Any, Dict, Optional, List, Sequence
2
+ from http import HTTPStatus
3
+ import re
4
+
5
+
6
+ class BoltException(Exception):
7
+ """Base exception class for all Django-Bolt exceptions."""
8
+
9
+ detail: str
10
+
11
+ def __init__(self, *args: Any, detail: str = "") -> None:
12
+ """Initialize BoltException.
13
+
14
+ Args:
15
+ *args: Additional arguments
16
+ detail: Exception detail message
17
+ """
18
+ str_args = [str(arg) for arg in args if arg]
19
+ if not detail:
20
+ if str_args:
21
+ detail, *str_args = str_args
22
+ elif hasattr(self, "detail"):
23
+ detail = self.detail
24
+ self.detail = detail
25
+ super().__init__(*str_args)
26
+
27
+ def __repr__(self) -> str:
28
+ if self.detail:
29
+ return f"{self.__class__.__name__} - {self.detail}"
30
+ return self.__class__.__name__
31
+
32
+ def __str__(self) -> str:
33
+ return " ".join((*self.args, self.detail)).strip()
34
+
35
+
36
+ class HTTPException(BoltException):
37
+ """Base exception for HTTP error responses.
38
+
39
+ These exceptions carry information to construct an HTTP response.
40
+ """
41
+
42
+ status_code: int = 500
43
+ """HTTP status code for this exception."""
44
+ detail: str = ""
45
+ """Exception details or message."""
46
+ headers: Dict[str, str]
47
+ """Headers to attach to the response."""
48
+ extra: Dict[str, Any] | List[Any] | None = None
49
+ """Additional data to include in the response."""
50
+
51
+ def __init__(
52
+ self,
53
+ status_code: int | None = None,
54
+ detail: Any | None = None,
55
+ headers: Optional[Dict[str, str]] = None,
56
+ extra: Dict[str, Any] | List[Any] | None = None,
57
+ ):
58
+ """Initialize HTTPException.
59
+
60
+ Args:
61
+ status_code: HTTP status code (defaults to class status_code)
62
+ detail: Exception details or message
63
+ headers: HTTP headers to include in response
64
+ extra: Additional data to include in response
65
+ """
66
+ # Handle detail
67
+ if detail is None:
68
+ detail = ""
69
+ detail_str = str(detail) if detail else ""
70
+
71
+ super().__init__(detail=detail_str)
72
+
73
+ self.status_code = status_code if status_code is not None else self.status_code
74
+ self.detail = detail_str if detail_str else HTTPStatus(self.status_code).phrase
75
+ self.headers = headers or {}
76
+ self.extra = extra
77
+
78
+ # Update args for better error messages
79
+ self.args = (f"{self.status_code}: {self.detail}",)
80
+
81
+ def __repr__(self) -> str:
82
+ return f"{self.status_code} - {self.__class__.__name__} - {self.detail}"
83
+
84
+
85
+ class ValidationException(BoltException, ValueError):
86
+ """Base exception for validation errors."""
87
+
88
+ def __init__(self, errors: Sequence[Any]) -> None:
89
+ """Initialize ValidationException.
90
+
91
+ Args:
92
+ errors: Sequence of validation errors
93
+ """
94
+ self._errors = errors
95
+ super().__init__(detail="Validation error")
96
+
97
+ def errors(self) -> Sequence[Any]:
98
+ """Return validation errors."""
99
+ return self._errors
100
+
101
+
102
+ class RequestValidationError(ValidationException):
103
+ """Request data validation error.
104
+
105
+ Raised when request data (body, query params, headers, etc.) fails validation.
106
+ """
107
+
108
+ def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
109
+ """Initialize RequestValidationError.
110
+
111
+ Args:
112
+ errors: Sequence of validation errors
113
+ body: The request body that failed validation
114
+ """
115
+ super().__init__(errors)
116
+ self.body = body
117
+
118
+
119
+ class ResponseValidationError(ValidationException):
120
+ """Response data validation error.
121
+
122
+ Raised when handler return value fails validation against response_model.
123
+ """
124
+
125
+ def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None:
126
+ """Initialize ResponseValidationError.
127
+
128
+ Args:
129
+ errors: Sequence of validation errors
130
+ body: The response body that failed validation
131
+ """
132
+ super().__init__(errors)
133
+ self.body = body
134
+
135
+ def __str__(self) -> str:
136
+ message = f"{len(self._errors)} validation error(s):\n"
137
+ for err in self._errors:
138
+ message += f" {err}\n"
139
+ return message
140
+
141
+
142
+ # HTTP 4xx Client Error Exceptions
143
+
144
+ class ClientException(HTTPException):
145
+ """Base class for 4xx client errors."""
146
+ status_code: int = 400
147
+
148
+
149
+ class BadRequest(ClientException):
150
+ """400 Bad Request - Invalid request data."""
151
+ status_code = 400
152
+
153
+
154
+ class Unauthorized(ClientException):
155
+ """401 Unauthorized - Authentication required or failed."""
156
+ status_code = 401
157
+
158
+
159
+ class Forbidden(ClientException):
160
+ """403 Forbidden - Insufficient permissions."""
161
+ status_code = 403
162
+
163
+
164
+ class NotFound(ClientException):
165
+ """404 Not Found - Resource not found."""
166
+ status_code = 404
167
+
168
+
169
+ class MethodNotAllowed(ClientException):
170
+ """405 Method Not Allowed - HTTP method not supported for this endpoint."""
171
+ status_code = 405
172
+
173
+
174
+ class NotAcceptable(ClientException):
175
+ """406 Not Acceptable - Cannot produce response in requested format."""
176
+ status_code = 406
177
+
178
+
179
+ class Conflict(ClientException):
180
+ """409 Conflict - Request conflicts with current state."""
181
+ status_code = 409
182
+
183
+
184
+ class Gone(ClientException):
185
+ """410 Gone - Resource permanently deleted."""
186
+ status_code = 410
187
+
188
+
189
+ class UnprocessableEntity(ClientException):
190
+ """422 Unprocessable Entity - Semantic validation error."""
191
+ status_code = 422
192
+
193
+
194
+ class TooManyRequests(ClientException):
195
+ """429 Too Many Requests - Rate limit exceeded."""
196
+ status_code = 429
197
+
198
+
199
+ # HTTP 5xx Server Error Exceptions
200
+
201
+ class ServerException(HTTPException):
202
+ """Base class for 5xx server errors."""
203
+ status_code: int = 500
204
+
205
+
206
+ class InternalServerError(ServerException):
207
+ """500 Internal Server Error - Unexpected server error."""
208
+ status_code = 500
209
+
210
+
211
+ class NotImplemented(ServerException):
212
+ """501 Not Implemented - Endpoint not implemented."""
213
+ status_code = 501
214
+
215
+
216
+ class BadGateway(ServerException):
217
+ """502 Bad Gateway - Invalid response from upstream server."""
218
+ status_code = 502
219
+
220
+
221
+ class ServiceUnavailable(ServerException):
222
+ """503 Service Unavailable - Server temporarily unavailable."""
223
+ status_code = 503
224
+
225
+
226
+ class GatewayTimeout(ServerException):
227
+ """504 Gateway Timeout - Upstream server timeout."""
228
+ status_code = 504
229
+
230
+
231
+ # Helper functions for better error messages
232
+
233
+ def parse_msgspec_decode_error(error: Exception, body_bytes: bytes) -> Dict[str, Any]:
234
+ """Parse msgspec.DecodeError to extract line/column information.
235
+
236
+ Args:
237
+ error: The msgspec.DecodeError exception
238
+ body_bytes: The JSON bytes that failed to parse
239
+
240
+ Returns:
241
+ Dict with error details including line/column information
242
+ """
243
+ error_msg = str(error)
244
+
245
+ # Try to extract byte position from error message
246
+ # Format: "JSON is malformed: invalid character (byte 78)"
247
+ match = re.search(r'byte (\d+)', error_msg)
248
+
249
+ if match:
250
+ byte_pos = int(match.group(1))
251
+
252
+ # Calculate line and column from byte position
253
+ lines = body_bytes.split(b'\n')
254
+ current_pos = 0
255
+ line_num = 1
256
+ col_num = 0
257
+ error_line_content = ""
258
+
259
+ for i, line in enumerate(lines, 1):
260
+ line_len = len(line) + 1 # +1 for newline
261
+ if current_pos + line_len > byte_pos:
262
+ line_num = i
263
+ col_num = byte_pos - current_pos + 1 # +1 for human-readable column (1-indexed)
264
+ error_line_content = line.decode('utf-8', errors='replace')
265
+ break
266
+ current_pos += line_len
267
+
268
+ # Build descriptive error message
269
+ msg = f"Invalid JSON at line {line_num}, column {col_num}: {error_msg}"
270
+ if error_line_content:
271
+ msg += f"\n {error_line_content}\n {' ' * (col_num - 1)}^"
272
+
273
+ return {
274
+ "type": "json_invalid",
275
+ "loc": ("body", line_num, col_num),
276
+ "msg": msg,
277
+ "input": body_bytes.decode("utf-8", errors="replace")[:200] if body_bytes else "",
278
+ "ctx": {
279
+ "line": line_num,
280
+ "column": col_num,
281
+ "byte_position": byte_pos,
282
+ "error": error_msg,
283
+ }
284
+ }
285
+
286
+ # Fallback if we can't parse the byte position
287
+ return {
288
+ "type": "json_invalid",
289
+ "loc": ("body",),
290
+ "msg": error_msg,
291
+ "input": body_bytes.decode("utf-8", errors="replace")[:100] if body_bytes else "",
292
+ }
293
+
294
+
django_bolt/health.py ADDED
@@ -0,0 +1,129 @@
1
+ """Health check endpoints for Django-Bolt.
2
+
3
+ Provides standard health check endpoints for monitoring and load balancers.
4
+ """
5
+
6
+ from typing import Dict, Any, Callable, List, Optional
7
+ import asyncio
8
+
9
+
10
+ class HealthCheck:
11
+ """Health check configuration and helpers."""
12
+
13
+ def __init__(self):
14
+ self._checks: List[Callable] = []
15
+
16
+ def add_check(self, check_func: Callable) -> None:
17
+ """Add a custom health check.
18
+
19
+ Args:
20
+ check_func: Async function that returns (bool, str) - (is_healthy, message)
21
+ """
22
+ self._checks.append(check_func)
23
+
24
+ async def run_checks(self) -> Dict[str, Any]:
25
+ """Run all configured health checks.
26
+
27
+ Returns:
28
+ Dictionary with health check results
29
+ """
30
+ results = {}
31
+ all_healthy = True
32
+
33
+ for check in self._checks:
34
+ try:
35
+ is_healthy, message = await check()
36
+ results[check.__name__] = {
37
+ "healthy": is_healthy,
38
+ "message": message,
39
+ }
40
+ if not is_healthy:
41
+ all_healthy = False
42
+ except Exception as e:
43
+ results[check.__name__] = {
44
+ "healthy": False,
45
+ "message": f"Check failed: {str(e)}",
46
+ }
47
+ all_healthy = False
48
+
49
+ return {
50
+ "status": "healthy" if all_healthy else "unhealthy",
51
+ "checks": results,
52
+ }
53
+
54
+
55
+ # Global health check instance
56
+ _health_check = HealthCheck()
57
+
58
+
59
+ async def check_database() -> tuple[bool, str]:
60
+ """Check database connectivity.
61
+
62
+ Returns:
63
+ Tuple of (is_healthy, message)
64
+ """
65
+ try:
66
+ from django.db import connection
67
+ from asgiref.sync import sync_to_async
68
+
69
+ # Try a simple query
70
+ await sync_to_async(connection.ensure_connection)()
71
+
72
+ return True, "Database connection OK"
73
+ except Exception as e:
74
+ return False, f"Database error: {str(e)}"
75
+
76
+
77
+ # Health endpoint handlers
78
+
79
+ async def health_handler() -> Dict[str, str]:
80
+ """Simple liveness check.
81
+
82
+ Returns:
83
+ Dictionary with status
84
+ """
85
+ return {"status": "ok"}
86
+
87
+
88
+ async def ready_handler() -> Dict[str, Any]:
89
+ """Readiness check with dependency checks.
90
+
91
+ Returns:
92
+ Dictionary with readiness status and checks
93
+ """
94
+ # Add database check by default
95
+ if not _health_check._checks:
96
+ _health_check.add_check(check_database)
97
+
98
+ results = await _health_check.run_checks()
99
+ return results
100
+
101
+
102
+ def register_health_checks(api) -> None:
103
+ """Register health check endpoints on a BoltAPI instance.
104
+
105
+ Args:
106
+ api: BoltAPI instance
107
+ """
108
+ api.get("/health")(health_handler)
109
+ api.get("/ready")(ready_handler)
110
+
111
+
112
+ def add_health_check(check_func: Callable) -> None:
113
+ """Add a custom health check.
114
+
115
+ Args:
116
+ check_func: Async function that returns (bool, str)
117
+
118
+ Example:
119
+ ```python
120
+ from django_bolt.health import add_health_check
121
+
122
+ async def check_redis():
123
+ # Check redis connection
124
+ return True, "Redis OK"
125
+
126
+ add_health_check(check_redis)
127
+ ```
128
+ """
129
+ _health_check.add_check(check_func)
@@ -0,0 +1,6 @@
1
+ """Logging utilities for Django-Bolt."""
2
+
3
+ from .config import LoggingConfig
4
+ from .middleware import LoggingMiddleware, create_logging_middleware
5
+
6
+ __all__ = ["LoggingConfig", "LoggingMiddleware", "create_logging_middleware"]