django-bolt 0.4.2__cp312-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.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.
Files changed (114) hide show
  1. django_bolt/__init__.py +256 -0
  2. django_bolt/_core.abi3.so +0 -0
  3. django_bolt/_json.py +174 -0
  4. django_bolt/admin/__init__.py +24 -0
  5. django_bolt/admin/admin_detection.py +182 -0
  6. django_bolt/admin/asgi_bridge.py +274 -0
  7. django_bolt/admin/routes.py +91 -0
  8. django_bolt/admin/static.py +152 -0
  9. django_bolt/admin/static_routes.py +113 -0
  10. django_bolt/analysis.py +412 -0
  11. django_bolt/api.py +2191 -0
  12. django_bolt/apps.py +7 -0
  13. django_bolt/auth/__init__.py +96 -0
  14. django_bolt/auth/anonymous.py +27 -0
  15. django_bolt/auth/backends.py +357 -0
  16. django_bolt/auth/guards.py +225 -0
  17. django_bolt/auth/jwt_utils.py +217 -0
  18. django_bolt/auth/revocation.py +284 -0
  19. django_bolt/auth/token.py +329 -0
  20. django_bolt/auth/user_loader.py +139 -0
  21. django_bolt/binding.py +554 -0
  22. django_bolt/bootstrap.py +84 -0
  23. django_bolt/cli.py +141 -0
  24. django_bolt/concurrency.py +61 -0
  25. django_bolt/decorators.py +172 -0
  26. django_bolt/dependencies.py +176 -0
  27. django_bolt/error_handlers.py +387 -0
  28. django_bolt/exceptions.py +295 -0
  29. django_bolt/health.py +136 -0
  30. django_bolt/logging/__init__.py +6 -0
  31. django_bolt/logging/config.py +360 -0
  32. django_bolt/logging/middleware.py +298 -0
  33. django_bolt/management/__init__.py +1 -0
  34. django_bolt/management/commands/__init__.py +0 -0
  35. django_bolt/management/commands/runbolt.py +521 -0
  36. django_bolt/middleware/__init__.py +101 -0
  37. django_bolt/middleware/compiler.py +188 -0
  38. django_bolt/middleware/compression.py +70 -0
  39. django_bolt/middleware/django_adapter.py +964 -0
  40. django_bolt/middleware/django_loader.py +147 -0
  41. django_bolt/middleware/middleware.py +587 -0
  42. django_bolt/middleware_response.py +53 -0
  43. django_bolt/openapi/__init__.py +23 -0
  44. django_bolt/openapi/config.py +196 -0
  45. django_bolt/openapi/plugins.py +449 -0
  46. django_bolt/openapi/routes.py +168 -0
  47. django_bolt/openapi/schema_generator.py +799 -0
  48. django_bolt/openapi/spec/__init__.py +68 -0
  49. django_bolt/openapi/spec/base.py +74 -0
  50. django_bolt/openapi/spec/callback.py +24 -0
  51. django_bolt/openapi/spec/components.py +72 -0
  52. django_bolt/openapi/spec/contact.py +21 -0
  53. django_bolt/openapi/spec/discriminator.py +25 -0
  54. django_bolt/openapi/spec/encoding.py +67 -0
  55. django_bolt/openapi/spec/enums.py +41 -0
  56. django_bolt/openapi/spec/example.py +36 -0
  57. django_bolt/openapi/spec/external_documentation.py +21 -0
  58. django_bolt/openapi/spec/header.py +132 -0
  59. django_bolt/openapi/spec/info.py +50 -0
  60. django_bolt/openapi/spec/license.py +28 -0
  61. django_bolt/openapi/spec/link.py +66 -0
  62. django_bolt/openapi/spec/media_type.py +51 -0
  63. django_bolt/openapi/spec/oauth_flow.py +36 -0
  64. django_bolt/openapi/spec/oauth_flows.py +28 -0
  65. django_bolt/openapi/spec/open_api.py +87 -0
  66. django_bolt/openapi/spec/operation.py +105 -0
  67. django_bolt/openapi/spec/parameter.py +147 -0
  68. django_bolt/openapi/spec/path_item.py +103 -0
  69. django_bolt/openapi/spec/paths.py +27 -0
  70. django_bolt/openapi/spec/reference.py +38 -0
  71. django_bolt/openapi/spec/request_body.py +38 -0
  72. django_bolt/openapi/spec/response.py +48 -0
  73. django_bolt/openapi/spec/responses.py +44 -0
  74. django_bolt/openapi/spec/schema.py +680 -0
  75. django_bolt/openapi/spec/security_requirement.py +27 -0
  76. django_bolt/openapi/spec/security_scheme.py +69 -0
  77. django_bolt/openapi/spec/server.py +34 -0
  78. django_bolt/openapi/spec/server_variable.py +32 -0
  79. django_bolt/openapi/spec/tag.py +32 -0
  80. django_bolt/openapi/spec/xml.py +44 -0
  81. django_bolt/pagination.py +669 -0
  82. django_bolt/param_functions.py +63 -0
  83. django_bolt/params.py +338 -0
  84. django_bolt/request.py +87 -0
  85. django_bolt/request_parsing.py +134 -0
  86. django_bolt/responses.py +245 -0
  87. django_bolt/router.py +218 -0
  88. django_bolt/serialization.py +379 -0
  89. django_bolt/serializers/__init__.py +144 -0
  90. django_bolt/serializers/base.py +1607 -0
  91. django_bolt/serializers/decorators.py +263 -0
  92. django_bolt/serializers/fields.py +328 -0
  93. django_bolt/serializers/helpers.py +168 -0
  94. django_bolt/serializers/nested.py +255 -0
  95. django_bolt/serializers/types.py +540 -0
  96. django_bolt/shortcuts.py +14 -0
  97. django_bolt/status_codes.py +321 -0
  98. django_bolt/testing/__init__.py +34 -0
  99. django_bolt/testing/client.py +398 -0
  100. django_bolt/testing/helpers.py +50 -0
  101. django_bolt/testing/websocket.py +508 -0
  102. django_bolt/types.py +591 -0
  103. django_bolt/typing.py +429 -0
  104. django_bolt/views.py +1116 -0
  105. django_bolt/websocket/__init__.py +78 -0
  106. django_bolt/websocket/close_codes.py +35 -0
  107. django_bolt/websocket/exceptions.py +28 -0
  108. django_bolt/websocket/handlers.py +140 -0
  109. django_bolt/websocket/state.py +13 -0
  110. django_bolt/websocket/types.py +231 -0
  111. django_bolt-0.4.2.dist-info/METADATA +618 -0
  112. django_bolt-0.4.2.dist-info/RECORD +114 -0
  113. django_bolt-0.4.2.dist-info/WHEEL +6 -0
  114. django_bolt-0.4.2.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,256 @@
1
+ """
2
+ Django-Bolt: High-performance API framework for Django.
3
+
4
+ Provides Rust-powered API endpoints with 60k+ RPS performance, integrating
5
+ with existing Django projects via Actix Web, PyO3, and msgspec.
6
+
7
+ Quick Start:
8
+ from django_bolt import BoltAPI, Request
9
+
10
+ api = BoltAPI()
11
+
12
+ @api.get("/hello")
13
+ async def hello(request: Request) -> dict:
14
+ return {"message": "Hello, World!"}
15
+
16
+ Type-Safe Requests:
17
+ from django_bolt import BoltAPI, Request
18
+ from django_bolt.types import JWTClaims
19
+ from myapp.models import User
20
+
21
+ api = BoltAPI()
22
+
23
+ @api.get("/profile", guards=[IsAuthenticated()])
24
+ async def profile(request: Request[User, JWTClaims, dict]) -> dict:
25
+ return {"email": request.user.email} # IDE knows User has email
26
+
27
+ Middleware:
28
+ from django_bolt import BoltAPI
29
+ from django_bolt.middleware import (
30
+ DjangoMiddleware,
31
+ TimingMiddleware,
32
+ LoggingMiddleware,
33
+ )
34
+ from django.contrib.sessions.middleware import SessionMiddleware
35
+ from django.contrib.auth.middleware import AuthenticationMiddleware
36
+
37
+ api = BoltAPI(
38
+ middleware=[
39
+ DjangoMiddleware(SessionMiddleware),
40
+ DjangoMiddleware(AuthenticationMiddleware),
41
+ TimingMiddleware(),
42
+ LoggingMiddleware(),
43
+ ]
44
+ )
45
+ """
46
+
47
+ from .api import BoltAPI
48
+
49
+ # Auth module
50
+ from .auth import (
51
+ # Guards/Permissions
52
+ AllowAny,
53
+ APIKeyAuthentication,
54
+ AuthContext,
55
+ HasAllPermissions,
56
+ HasAnyPermission,
57
+ HasPermission,
58
+ IsAdminUser,
59
+ IsAuthenticated,
60
+ IsStaff,
61
+ # Authentication backends
62
+ JWTAuthentication,
63
+ SessionAuthentication,
64
+ # JWT Token & Utilities
65
+ Token,
66
+ create_jwt_for_user,
67
+ extract_user_id_from_context,
68
+ get_auth_context,
69
+ get_current_user,
70
+ )
71
+
72
+ # Decorators module
73
+ from .decorators import action
74
+
75
+ # Middleware module
76
+ from .middleware import (
77
+ BaseMiddleware,
78
+ CompressionConfig,
79
+ # Django compatibility
80
+ DjangoMiddleware,
81
+ ErrorHandlerMiddleware,
82
+ LoggingMiddleware,
83
+ Middleware,
84
+ # Protocols and base classes
85
+ MiddlewareProtocol,
86
+ # Built-in middleware (Python)
87
+ TimingMiddleware,
88
+ cors,
89
+ # Decorators
90
+ middleware,
91
+ no_compress,
92
+ rate_limit,
93
+ skip_middleware,
94
+ )
95
+
96
+ # OpenAPI module
97
+ from .openapi import (
98
+ JsonRenderPlugin,
99
+ OpenAPIConfig,
100
+ RapidocRenderPlugin,
101
+ RedocRenderPlugin,
102
+ ScalarRenderPlugin,
103
+ StoplightRenderPlugin,
104
+ SwaggerRenderPlugin,
105
+ YamlRenderPlugin,
106
+ )
107
+
108
+ # Pagination module
109
+ from .pagination import (
110
+ CursorPagination,
111
+ LimitOffsetPagination,
112
+ PageNumberPagination,
113
+ PaginatedResponse,
114
+ PaginationBase,
115
+ paginate,
116
+ )
117
+ from .params import Depends
118
+
119
+ # Type-safe Request object
120
+ from .request import Request
121
+ from .responses import JSON, Response, StreamingResponse
122
+ from .router import Router
123
+ from .types import (
124
+ APIKeyAuth,
125
+ DjangoModel,
126
+ JWTClaims,
127
+ SessionAuth,
128
+ TimingState,
129
+ TracingState,
130
+ UserType,
131
+ )
132
+
133
+ # Types and protocols
134
+ from .types import (
135
+ Request as RequestProtocol, # Protocol for type checking
136
+ )
137
+
138
+ # Views module
139
+ from .views import (
140
+ APIView,
141
+ CreateMixin,
142
+ DestroyMixin,
143
+ ListMixin,
144
+ ModelViewSet,
145
+ PartialUpdateMixin,
146
+ ReadOnlyModelViewSet,
147
+ RetrieveMixin,
148
+ UpdateMixin,
149
+ ViewSet,
150
+ )
151
+
152
+ # WebSocket module
153
+ from .websocket import (
154
+ CloseCode,
155
+ WebSocket,
156
+ WebSocketClose,
157
+ WebSocketDisconnect,
158
+ WebSocketException,
159
+ WebSocketState,
160
+ )
161
+
162
+ __all__ = [
163
+ # Core
164
+ "BoltAPI",
165
+ "Request",
166
+ "Response",
167
+ "JSON",
168
+ "StreamingResponse",
169
+ "CompressionConfig",
170
+ "Depends",
171
+ # Router
172
+ "Router",
173
+ # Types
174
+ "RequestProtocol",
175
+ "UserType",
176
+ "AuthContext",
177
+ "DjangoModel",
178
+ "JWTClaims",
179
+ "APIKeyAuth",
180
+ "SessionAuth",
181
+ "TimingState",
182
+ "TracingState",
183
+ # Views
184
+ "APIView",
185
+ "ViewSet",
186
+ "ModelViewSet",
187
+ "ReadOnlyModelViewSet",
188
+ "ListMixin",
189
+ "RetrieveMixin",
190
+ "CreateMixin",
191
+ "UpdateMixin",
192
+ "PartialUpdateMixin",
193
+ "DestroyMixin",
194
+ # Pagination
195
+ "PaginationBase",
196
+ "PageNumberPagination",
197
+ "LimitOffsetPagination",
198
+ "CursorPagination",
199
+ "PaginatedResponse",
200
+ "paginate",
201
+ # Decorators
202
+ "action",
203
+ # Auth - Authentication
204
+ "JWTAuthentication",
205
+ "APIKeyAuthentication",
206
+ "SessionAuthentication",
207
+ "AuthContext",
208
+ # Auth - Guards/Permissions
209
+ "AllowAny",
210
+ "IsAuthenticated",
211
+ "IsAdminUser",
212
+ "IsStaff",
213
+ "HasPermission",
214
+ "HasAnyPermission",
215
+ "HasAllPermissions",
216
+ # Middleware - Protocols and base classes
217
+ "MiddlewareProtocol",
218
+ "BaseMiddleware",
219
+ "Middleware",
220
+ # Middleware - Decorators
221
+ "middleware",
222
+ "rate_limit",
223
+ "cors",
224
+ "skip_middleware",
225
+ "no_compress",
226
+ # Middleware - Built-in (Python)
227
+ "TimingMiddleware",
228
+ "LoggingMiddleware",
229
+ "ErrorHandlerMiddleware",
230
+ # Middleware - Django compatibility
231
+ "DjangoMiddleware",
232
+ # Auth - JWT Token & Utilities
233
+ "Token",
234
+ "create_jwt_for_user",
235
+ "get_current_user",
236
+ "extract_user_id_from_context",
237
+ "get_auth_context",
238
+ # OpenAPI
239
+ "OpenAPIConfig",
240
+ "SwaggerRenderPlugin",
241
+ "RedocRenderPlugin",
242
+ "ScalarRenderPlugin",
243
+ "RapidocRenderPlugin",
244
+ "StoplightRenderPlugin",
245
+ "JsonRenderPlugin",
246
+ "YamlRenderPlugin",
247
+ # WebSocket
248
+ "WebSocket",
249
+ "WebSocketState",
250
+ "WebSocketDisconnect",
251
+ "WebSocketClose",
252
+ "WebSocketException",
253
+ "CloseCode",
254
+ ]
255
+
256
+ default_app_config = 'django_bolt.apps.DjangoBoltConfig'
Binary file
django_bolt/_json.py ADDED
@@ -0,0 +1,174 @@
1
+ """Fast JSON helpers backed by cached msgspec Encoder/Decoder.
2
+
3
+ This module provides optimized JSON encoding/decoding using msgspec with:
4
+ - Thread-local cached encoder/decoder instances (thread-safe buffer reuse)
5
+ - Support for common non-JSON-native types (datetime, Path, Decimal, UUID, IP addresses)
6
+ - Automatic Serializer.dump() for write_only, computed_field support
7
+ - Type-safe decoding with validation
8
+ - Custom encoder/decoder hooks
9
+
10
+ Inspired by Litestar's serialization approach.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import threading
16
+ from collections.abc import Callable
17
+ from datetime import date, datetime, time
18
+ from decimal import Decimal
19
+ from ipaddress import (
20
+ IPv4Address,
21
+ IPv4Interface,
22
+ IPv4Network,
23
+ IPv6Address,
24
+ IPv6Interface,
25
+ IPv6Network,
26
+ )
27
+ from pathlib import Path, PurePath
28
+ from typing import Any, TypeVar
29
+ from uuid import UUID
30
+
31
+ import msgspec
32
+
33
+ T = TypeVar("T")
34
+
35
+ # Thread-local storage for encoder/decoder instances
36
+ _thread_local = threading.local()
37
+
38
+ # Default type encoders for non-JSON-native types
39
+ # Maps type -> encoder function
40
+ DEFAULT_TYPE_ENCODERS: dict[type, Callable[[Any], Any]] = {
41
+ # Paths
42
+ Path: str,
43
+ PurePath: str,
44
+ # Dates/Times -> ISO format
45
+ datetime: lambda v: v.isoformat(),
46
+ date: lambda v: v.isoformat(),
47
+ time: lambda v: v.isoformat(),
48
+ # Decimals -> int or float
49
+ Decimal: lambda v: int(v) if v.as_tuple().exponent >= 0 else float(v),
50
+ # IP addresses
51
+ IPv4Address: str,
52
+ IPv4Interface: str,
53
+ IPv4Network: str,
54
+ IPv6Address: str,
55
+ IPv6Interface: str,
56
+ IPv6Network: str,
57
+ # UUID
58
+ UUID: str,
59
+
60
+ }
61
+
62
+
63
+ def default_serializer(value: Any) -> Any:
64
+ """Transform values non-natively supported by msgspec.
65
+
66
+ Walks the MRO (Method Resolution Order) to support subclasses.
67
+ Raises TypeError if type is unsupported.
68
+
69
+ Note: Serializer instances are handled in serialization.py before reaching
70
+ this hook, since msgspec.Struct is natively supported by msgspec.
71
+ """
72
+ # Walk MRO to support polymorphic types
73
+ for base in value.__class__.__mro__[:-1]: # Skip 'object'
74
+ encoder = DEFAULT_TYPE_ENCODERS.get(base)
75
+ if encoder is not None:
76
+ return encoder(value)
77
+
78
+ raise TypeError(f"Unsupported type: {type(value)!r}")
79
+
80
+
81
+ def _get_encoder() -> msgspec.json.Encoder:
82
+ """Return a thread-local msgspec JSON Encoder instance.
83
+
84
+ Using a per-thread encoder is thread-safe and avoids cross-thread contention
85
+ while still reusing the internal buffer for repeated encodes on the same thread.
86
+ """
87
+ encoder = getattr(_thread_local, "encoder", None)
88
+ if encoder is None:
89
+ encoder = msgspec.json.Encoder(enc_hook=default_serializer)
90
+ _thread_local.encoder = encoder
91
+ return encoder
92
+
93
+
94
+ def _get_decoder() -> msgspec.json.Decoder:
95
+ """Return a thread-local msgspec JSON Decoder instance.
96
+
97
+ Using a per-thread decoder is thread-safe and reuses the internal buffer.
98
+ """
99
+ decoder = getattr(_thread_local, "decoder", None)
100
+ if decoder is None:
101
+ decoder = msgspec.json.Decoder()
102
+ _thread_local.decoder = decoder
103
+ return decoder
104
+
105
+
106
+ def encode(value: Any, serializer: Callable[[Any], Any] | None = None) -> bytes:
107
+ """Encode a Python object to JSON bytes.
108
+
109
+ Args:
110
+ value: Object to encode
111
+ serializer: Optional custom encoder hook (overrides default)
112
+
113
+ Returns:
114
+ JSON bytes
115
+
116
+ Raises:
117
+ TypeError: If value contains unsupported types
118
+ msgspec.EncodeError: If encoding fails
119
+ """
120
+ if serializer is not None:
121
+ # Custom serializer provided - use one-off encoder
122
+ return msgspec.json.encode(value, enc_hook=serializer)
123
+
124
+ # Use thread-local cached encoder with default serializer
125
+ return _get_encoder().encode(value)
126
+
127
+
128
+ def decode(value: bytes | str) -> Any:
129
+ """Decode JSON bytes/string to Python object.
130
+
131
+ Args:
132
+ value: JSON bytes or string
133
+
134
+ Returns:
135
+ Decoded Python object
136
+
137
+ Raises:
138
+ msgspec.DecodeError: If decoding fails
139
+ """
140
+ return _get_decoder().decode(value)
141
+
142
+
143
+ def decode_typed[T](
144
+ value: bytes | str,
145
+ target_type: type[T],
146
+ strict: bool = True,
147
+ ) -> T:
148
+ """Decode JSON with type validation and coercion.
149
+
150
+ Args:
151
+ value: JSON bytes or string
152
+ target_type: Expected type (e.g., msgspec.Struct subclass)
153
+ strict: If False, enables lenient type coercion (e.g., "123" -> 123)
154
+
155
+ Returns:
156
+ Decoded and validated object of target_type
157
+
158
+ Raises:
159
+ msgspec.DecodeError: If decoding or validation fails
160
+ msgspec.ValidationError: If type validation fails
161
+
162
+ Examples:
163
+ >>> class User(msgspec.Struct):
164
+ ... id: int
165
+ ... name: str
166
+ >>> decode_typed(b'{"id": 1, "name": "Alice"}', User)
167
+ User(id=1, name='Alice')
168
+ """
169
+ return msgspec.json.decode(value, type=target_type, strict=strict)
170
+
171
+
172
+ __all__ = ["encode", "decode", "decode_typed", "DEFAULT_TYPE_ENCODERS", "default_serializer"]
173
+
174
+
@@ -0,0 +1,24 @@
1
+ """
2
+ Django admin integration for django-bolt.
3
+
4
+ This module provides ASGI bridge functionality to integrate Django's admin
5
+ interface with django-bolt's high-performance routing system.
6
+ """
7
+
8
+ from .admin_detection import (
9
+ detect_admin_url_prefix,
10
+ get_admin_info,
11
+ get_admin_route_patterns,
12
+ is_admin_installed,
13
+ should_enable_admin,
14
+ )
15
+ from .asgi_bridge import ASGIFallbackHandler
16
+
17
+ __all__ = [
18
+ "is_admin_installed",
19
+ "detect_admin_url_prefix",
20
+ "get_admin_route_patterns",
21
+ "should_enable_admin",
22
+ "get_admin_info",
23
+ "ASGIFallbackHandler",
24
+ ]
@@ -0,0 +1,182 @@
1
+ """
2
+ Utilities for detecting and configuring Django admin integration.
3
+ """
4
+
5
+ import logging
6
+ import sys
7
+
8
+ from django.conf import settings
9
+ from django.urls import get_resolver
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def is_admin_installed() -> bool:
15
+ """
16
+ Check if Django admin is installed and configured.
17
+
18
+ Returns:
19
+ True if admin is in INSTALLED_APPS, False otherwise
20
+ """
21
+ try:
22
+ return 'django.contrib.admin' in settings.INSTALLED_APPS
23
+ except Exception:
24
+ return False
25
+
26
+
27
+ def detect_admin_url_prefix() -> str | None:
28
+ """
29
+ Detect the URL prefix for Django admin by parsing urlpatterns.
30
+
31
+ Returns:
32
+ Admin URL prefix (e.g., 'admin' or 'dashboard') or None if not found
33
+ """
34
+ if not is_admin_installed():
35
+ return None
36
+
37
+ try:
38
+ # Get root URL resolver
39
+ resolver = get_resolver(getattr(settings, 'ROOT_URLCONF', None))
40
+
41
+ # Search for admin patterns
42
+ for url_pattern in resolver.url_patterns:
43
+ # Check if this is admin.site.urls
44
+ if hasattr(url_pattern, 'app_name') and url_pattern.app_name == 'admin':
45
+ # Extract the pattern prefix
46
+ pattern_str = str(url_pattern.pattern)
47
+ # Remove trailing slash and special regex chars
48
+ prefix = pattern_str.rstrip('/^$')
49
+ return prefix if prefix else 'admin'
50
+
51
+ # Also check URLResolver with admin urlconf
52
+ if hasattr(url_pattern, 'urlconf_name'):
53
+ urlconf = url_pattern.urlconf_name
54
+ # Check if urlconf module contains admin.site
55
+ if hasattr(urlconf, '__name__') and 'admin' in str(urlconf.__name__):
56
+ pattern_str = str(url_pattern.pattern)
57
+ prefix = pattern_str.rstrip('/^$')
58
+ return prefix if prefix else 'admin'
59
+
60
+ # Check if urlconf is a list containing admin patterns
61
+ if isinstance(urlconf, (list, tuple)):
62
+ for sub_pattern in urlconf:
63
+ if hasattr(sub_pattern, 'callback') and hasattr(sub_pattern.callback, '__module__') and 'admin' in sub_pattern.callback.__module__:
64
+ pattern_str = str(url_pattern.pattern)
65
+ prefix = pattern_str.rstrip('/^$')
66
+ return prefix if prefix else 'admin'
67
+
68
+ except Exception as e:
69
+ # If detection fails, log warning and return default
70
+ print(f"[django-bolt] Warning: Could not auto-detect admin URL prefix: {e}", file=sys.stderr)
71
+
72
+ # Default fallback
73
+ return 'admin'
74
+
75
+
76
+ def get_admin_route_patterns() -> list[tuple[str, list[str]]]:
77
+ """
78
+ Get route patterns to register for Django admin.
79
+
80
+ Returns:
81
+ List of (path_pattern, methods) tuples for admin routes
82
+ Example: [('/admin/{path:path}', ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])]
83
+ """
84
+ if not is_admin_installed():
85
+ return []
86
+
87
+ # Detect admin URL prefix
88
+ admin_prefix = detect_admin_url_prefix()
89
+ if not admin_prefix:
90
+ return []
91
+
92
+ # Build catch-all pattern for admin routes
93
+ # Use {path:path} syntax for catch-all parameter
94
+ admin_pattern = f'/{admin_prefix}/{{path:path}}'
95
+
96
+ # Admin needs to handle common HTTP methods
97
+ # Only use methods supported by django-bolt's router (GET, POST, PUT, PATCH, DELETE)
98
+ methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
99
+
100
+ # Also add exact /admin route (without trailing slash)
101
+ # NOTE: NormalizePath::trim() in Rust strips trailing slashes from incoming requests,
102
+ # so we register routes WITHOUT trailing slashes to match the normalized paths.
103
+ # However, for TestClient with use_http_layer=True, we also need the trailing slash version.
104
+ admin_root = f'/{admin_prefix}'
105
+ admin_root_slash = f'/{admin_prefix}/'
106
+
107
+ return [
108
+ (admin_root, methods),
109
+ (admin_root_slash, methods),
110
+ (admin_pattern, methods),
111
+ ]
112
+
113
+
114
+ def get_static_url_prefix() -> str | None:
115
+ """
116
+ Get the STATIC_URL prefix from Django settings.
117
+
118
+ Returns:
119
+ Static URL prefix (e.g., 'static') or None if not configured
120
+ """
121
+ try:
122
+ if hasattr(settings, 'STATIC_URL') and settings.STATIC_URL:
123
+ static_url = settings.STATIC_URL
124
+ # Remove leading/trailing slashes
125
+ return static_url.strip('/')
126
+ except Exception as e:
127
+ logger.warning(
128
+ "Failed to get STATIC_URL from Django settings. "
129
+ "Static file serving may not work correctly. Error: %s",
130
+ e
131
+ )
132
+
133
+ return None
134
+
135
+
136
+ def should_enable_admin() -> bool:
137
+ """
138
+ Determine if admin should be auto-enabled.
139
+
140
+ Returns:
141
+ True if admin is installed and can be enabled, False otherwise
142
+ """
143
+ if not is_admin_installed():
144
+ return False
145
+
146
+ # Check if required dependencies are installed
147
+ try:
148
+ required_apps = [
149
+ 'django.contrib.auth',
150
+ 'django.contrib.contenttypes',
151
+ 'django.contrib.sessions',
152
+ ]
153
+
154
+ for app in required_apps:
155
+ if app not in settings.INSTALLED_APPS:
156
+ print(
157
+ f"[django-bolt] Warning: Django admin is installed but {app} is missing. "
158
+ f"Admin integration disabled.",
159
+ file=sys.stderr
160
+ )
161
+ return False
162
+
163
+ return True
164
+
165
+ except Exception as e:
166
+ print(f"[django-bolt] Warning: Could not check admin dependencies: {e}", file=sys.stderr)
167
+ return False
168
+
169
+
170
+ def get_admin_info() -> dict:
171
+ """
172
+ Get information about Django admin configuration.
173
+
174
+ Returns:
175
+ Dict with admin configuration details
176
+ """
177
+ return {
178
+ 'installed': is_admin_installed(),
179
+ 'enabled': should_enable_admin(),
180
+ 'url_prefix': detect_admin_url_prefix(),
181
+ 'static_url': get_static_url_prefix(),
182
+ }