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
django_bolt/apps.py ADDED
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoBoltConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.BigAutoField'
6
+ name = 'django_bolt'
7
+ verbose_name = 'Django Bolt'
@@ -0,0 +1,228 @@
1
+ """
2
+ High-performance async to sync collector for streaming.
3
+ Implements proven patterns for efficient async iterator batching and PyO3 boundary optimization.
4
+ """
5
+
6
+ import asyncio
7
+ from datetime import timedelta
8
+ from typing import AsyncIterable, AsyncIterator, Iterator, List, Optional, TypeVar, Union
9
+
10
+ X = TypeVar("X")
11
+
12
+
13
+ def batch_async(
14
+ aib: AsyncIterable[X],
15
+ timeout: timedelta,
16
+ batch_size: int,
17
+ loop: Optional[asyncio.AbstractEventLoop] = None,
18
+ ) -> Iterator[List[X]]:
19
+ """
20
+ Batch an async iterable synchronously without timeouts for minimal latency.
21
+
22
+ Optimized for low-latency streaming:
23
+ - No timeout overhead for immediate async generators
24
+ - Direct async iteration for maximum speed
25
+ - Simplified control flow
26
+
27
+ Args:
28
+ aib: The underlying source async iterable of items
29
+ timeout: Ignored in this optimized version
30
+ batch_size: Maximum number of items to yield in a batch
31
+ loop: Custom asyncio run loop to use, if any
32
+
33
+ Yields:
34
+ The next gathered batch of items
35
+ """
36
+ # Ensure that we have the stateful iterator of the source
37
+ ait = aib.__aiter__()
38
+
39
+ loop = loop if loop is not None else asyncio.new_event_loop()
40
+
41
+ async def get_next_batch():
42
+ batch = []
43
+ # Gather items greedily up to batch_size
44
+ # This is optimal for small iterators (SSE with 3-5 chunks)
45
+ for _ in range(batch_size):
46
+ try:
47
+ next_item = await ait.__anext__()
48
+ batch.append(next_item)
49
+ except StopAsyncIteration:
50
+ # End of iterator
51
+ break
52
+ return batch
53
+
54
+ while True:
55
+ # Direct execution without timeout wrapper for minimal overhead
56
+ batch = loop.run_until_complete(get_next_batch())
57
+ if not batch:
58
+ return
59
+ yield batch
60
+
61
+
62
+ class AsyncToSyncCollector:
63
+ """
64
+ High-performance async stream collector for Rust integration.
65
+
66
+ Optimized for streaming with efficient batching to minimize PyO3 boundary
67
+ crossings while maintaining good throughput and latency characteristics.
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ async_iterable: AsyncIterable,
73
+ batch_size: int = 50, # Optimized for OpenAI streaming
74
+ timeout_ms: int = 10, # Low latency: 10ms timeout
75
+ convert_to_bytes: bool = True
76
+ ):
77
+ """
78
+ Initialize the collector.
79
+
80
+ Args:
81
+ async_iterable: The async iterable to collect from
82
+ batch_size: Number of chunks to batch together
83
+ timeout_ms: Maximum time to wait for a full batch (milliseconds)
84
+ convert_to_bytes: Whether to convert items to bytes
85
+ """
86
+ # Assume it's an AsyncIterable and let batch_async call __aiter__() on it
87
+ self.async_gen = async_iterable
88
+
89
+ self.batch_size = batch_size
90
+ self.timeout = timedelta(milliseconds=timeout_ms)
91
+ self.convert_to_bytes = convert_to_bytes
92
+ self._iterator = None
93
+ self._loop = None
94
+
95
+ def _convert_to_bytes(self, item) -> bytes:
96
+ """Convert various types to bytes for streaming."""
97
+ if isinstance(item, bytes):
98
+ return item
99
+ elif isinstance(item, bytearray):
100
+ return bytes(item)
101
+ elif isinstance(item, memoryview):
102
+ return bytes(item)
103
+ elif isinstance(item, str):
104
+ return item.encode('utf-8')
105
+ else:
106
+ return str(item).encode('utf-8')
107
+
108
+ def __iter__(self):
109
+ """Initialize the iterator."""
110
+ # Try to use existing event loop, create new one only if needed
111
+ try:
112
+ # Try to get the running event loop first
113
+ self._loop = asyncio.get_running_loop()
114
+ except RuntimeError:
115
+ # No event loop running, create a new one
116
+ self._loop = asyncio.new_event_loop()
117
+
118
+ # Get the batch iterator
119
+ self._iterator = batch_async(
120
+ self.async_gen,
121
+ self.timeout,
122
+ self.batch_size,
123
+ self._loop
124
+ )
125
+ return self
126
+
127
+ def __next__(self) -> bytes:
128
+ """Get the next batch of chunks as a single bytes object."""
129
+ # Initialize iterator if not already done (Rust calls __next__ without __iter__)
130
+ if self._iterator is None:
131
+ self.__iter__()
132
+
133
+ try:
134
+ batch = next(self._iterator)
135
+
136
+ # Empty batch means we're done
137
+ if not batch:
138
+ if self._loop:
139
+ self._loop.close()
140
+ self._loop = None
141
+ raise StopIteration
142
+
143
+ if self.convert_to_bytes:
144
+ # Convert and join all chunks into a single bytes object
145
+ # This is critical for performance - one PyO3 crossing instead of many
146
+ converted = [self._convert_to_bytes(item) for item in batch]
147
+ return b''.join(converted)
148
+ else:
149
+ # Return raw batch if conversion not needed
150
+ return batch
151
+
152
+ except StopIteration:
153
+ # Clean up the event loop
154
+ if self._loop:
155
+ self._loop.close()
156
+ self._loop = None
157
+ raise
158
+ except Exception as e:
159
+ # Clean up on any error
160
+ if self._loop:
161
+ self._loop.close()
162
+ self._loop = None
163
+ raise
164
+
165
+ def __del__(self):
166
+ """Cleanup event loop on deletion."""
167
+ if hasattr(self, '_loop') and self._loop:
168
+ try:
169
+ if not self._loop.is_closed():
170
+ self._loop.close()
171
+ except:
172
+ pass
173
+
174
+
175
+ def wrap_async_stream(
176
+ async_gen: AsyncIterable,
177
+ batch_size: int = 50,
178
+ timeout_ms: int = 10
179
+ ) -> Iterator[bytes]:
180
+ """
181
+ Convenience function to wrap an async generator for sync iteration.
182
+
183
+ Args:
184
+ async_gen: The async generator to wrap
185
+ batch_size: Number of chunks to batch together
186
+ timeout_ms: Maximum time to wait for a full batch (milliseconds)
187
+
188
+ Returns:
189
+ A sync iterator that yields batched bytes
190
+ """
191
+ return AsyncToSyncCollector(async_gen, batch_size, timeout_ms)
192
+
193
+
194
+ # Performance-tuned configurations for different streaming scenarios
195
+ class StreamProfiles:
196
+ """Pre-configured profiles for different streaming scenarios."""
197
+
198
+ @staticmethod
199
+ def openai_streaming():
200
+ """Optimized for OpenAI-style token streaming (many small chunks)."""
201
+ return {
202
+ 'batch_size': 100, # Aggressive batching for tiny chunks
203
+ 'timeout_ms': 20 # 20ms max latency
204
+ }
205
+
206
+ @staticmethod
207
+ def large_chunks():
208
+ """Optimized for larger chunk streaming (e.g., file downloads)."""
209
+ return {
210
+ 'batch_size': 10, # Less batching needed
211
+ 'timeout_ms': 50 # Can tolerate more latency
212
+ }
213
+
214
+ @staticmethod
215
+ def realtime():
216
+ """Optimized for real-time streaming with minimal latency."""
217
+ return {
218
+ 'batch_size': 5, # Small batches
219
+ 'timeout_ms': 5 # Ultra-low latency (5ms)
220
+ }
221
+
222
+ @staticmethod
223
+ def high_throughput():
224
+ """Optimized for maximum throughput (batch processing)."""
225
+ return {
226
+ 'batch_size': 200, # Very aggressive batching
227
+ 'timeout_ms': 100 # Higher latency acceptable
228
+ }
@@ -0,0 +1,464 @@
1
+ # Django-Bolt Authentication System
2
+
3
+ High-performance authentication and authorization system where **validation happens in Rust without the GIL**, achieving 60k+ RPS with JWT authentication.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────┐
9
+ │ Python Layer (Configuration) │
10
+ │ │
11
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
12
+ │ │ JWTAuth │ │ APIKeyAuth │ │ Guards │ │
13
+ │ │ │ │ │ │ │ │
14
+ │ │ .to_metadata()│ │ .to_metadata()│ │ .to_metadata()│ │
15
+ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
16
+ │ │ │ │ │
17
+ │ └──────────────────┴──────────────────┘ │
18
+ │ │ │
19
+ │ Compile to metadata │
20
+ │ │ │
21
+ └────────────────────────────┼─────────────────────────────────────┘
22
+
23
+
24
+ ┌─────────────────────────────────────────────────────────────────┐
25
+ │ Rust Layer (Validation - NO GIL) │
26
+ │ │
27
+ │ ┌──────────────────────────────────────────────────────────┐ │
28
+ │ │ Route Registration │ │
29
+ │ │ • Parse metadata → typed Rust enums │ │
30
+ │ │ • Store auth backends & guards per route │ │
31
+ │ └──────────────────────────────────────────────────────────┘ │
32
+ │ │
33
+ │ ┌──────────────────────────────────────────────────────────┐ │
34
+ │ │ Request Processing (HOT PATH - NO GIL) │ │
35
+ │ │ │ │
36
+ │ │ 1. Extract token from header │ │
37
+ │ │ 2. Validate JWT (jsonwebtoken crate) │ │
38
+ │ │ 3. Check guards/permissions │ │
39
+ │ │ 4. Populate request.context with auth data │ │
40
+ │ │ │ │
41
+ │ │ ⚡ All happens without touching Python/GIL │ │
42
+ │ └──────────────────────────────────────────────────────────┘ │
43
+ └─────────────────────────────────────────────────────────────────┘
44
+
45
+
46
+ ┌─────────────────────────────────────────────────────────────────┐
47
+ │ Python Handler (WITH AuthContext) │
48
+ │ │
49
+ │ async def my_handler(request): │
50
+ │ user_id = request["context"]["user_id"] │
51
+ │ is_admin = request["context"]["is_admin"] │
52
+ │ permissions = request["context"]["permissions"] │
53
+ │ claims = request["context"]["auth_claims"] │
54
+ │ ... │
55
+ └─────────────────────────────────────────────────────────────────┘
56
+ ```
57
+
58
+ ## Module Structure
59
+
60
+ ```
61
+ django_bolt/auth/
62
+ ├── __init__.py # Public API exports
63
+ ├── README.md # This file
64
+ ├── backends.py # Authentication backends (JWT, API key, Session)
65
+ ├── guards.py # Permission guards (IsAuthenticated, IsAdmin, etc.)
66
+ ├── middleware.py # Middleware decorators (cors, rate_limit, etc.)
67
+ └── token.py # JWT Token dataclass with encode/decode
68
+ ```
69
+
70
+ ## Quick Start
71
+
72
+ ### 1. Define Authentication Backend
73
+
74
+ ```python
75
+ from django_bolt import BoltAPI, JWTAuthentication, IsAuthenticated, Token
76
+ from datetime import timedelta
77
+
78
+ api = BoltAPI()
79
+
80
+ # Create JWT token for a user
81
+ def create_token_for_user(user):
82
+ return Token.create(
83
+ sub=str(user.id),
84
+ expires_delta=timedelta(hours=1),
85
+ is_staff=user.is_staff,
86
+ is_admin=user.is_superuser,
87
+ permissions=list(user.get_all_permissions()),
88
+ email=user.email,
89
+ ).encode(secret=settings.SECRET_KEY)
90
+
91
+ # Protected endpoint
92
+ @api.get(
93
+ "/profile",
94
+ auth=[JWTAuthentication()],
95
+ guards=[IsAuthenticated()]
96
+ )
97
+ async def get_profile(request):
98
+ user_id = request["context"]["user_id"]
99
+ is_admin = request["context"]["is_admin"]
100
+
101
+ return {
102
+ "user_id": user_id,
103
+ "is_admin": is_admin,
104
+ "permissions": request["context"].get("permissions", [])
105
+ }
106
+ ```
107
+
108
+ ### 2. Global Authentication (Settings)
109
+
110
+ ```python
111
+ # settings.py
112
+
113
+ BOLT_AUTHENTICATION_CLASSES = [
114
+ JWTAuthentication(
115
+ secret=SECRET_KEY,
116
+ algorithms=["HS256"],
117
+ header="authorization",
118
+ audience="my-api",
119
+ )
120
+ ]
121
+
122
+ BOLT_DEFAULT_PERMISSION_CLASSES = [
123
+ IsAuthenticated()
124
+ ]
125
+ ```
126
+
127
+ Now all routes are protected by default unless you override with `guards=[AllowAny()]`.
128
+
129
+ ### 3. Per-Route Authentication Override
130
+
131
+ ```python
132
+ from django_bolt import APIKeyAuthentication, HasPermission
133
+
134
+ # Admin-only endpoint with API key auth
135
+ @api.delete(
136
+ "/users/{user_id}",
137
+ auth=[APIKeyAuthentication(api_keys={"admin-key-123"})],
138
+ guards=[HasPermission("users.delete")]
139
+ )
140
+ async def delete_user(user_id: int):
141
+ # Only callable with valid API key and users.delete permission
142
+ return {"deleted": user_id}
143
+ ```
144
+
145
+ ## Authentication Backends
146
+
147
+ ### JWTAuthentication
148
+
149
+ High-performance JWT validation in Rust using the `jsonwebtoken` crate.
150
+
151
+ ```python
152
+ from django_bolt import JWTAuthentication
153
+
154
+ auth = JWTAuthentication(
155
+ secret="your-secret-key", # Default: Django SECRET_KEY
156
+ algorithms=["HS256"], # Supported: HS256/384/512, RS256/384/512, ES256/384
157
+ header="authorization", # Header to extract token from
158
+ audience="my-api", # Optional: validate aud claim
159
+ issuer="auth-service", # Optional: validate iss claim
160
+ )
161
+ ```
162
+
163
+ **Token Format**: `Authorization: Bearer <jwt-token>`
164
+
165
+ **Performance**: ~60k RPS with JWT validation
166
+
167
+ ### APIKeyAuthentication
168
+
169
+ Simple API key validation with optional per-key permissions.
170
+
171
+ ```python
172
+ from django_bolt import APIKeyAuthentication
173
+
174
+ auth = APIKeyAuthentication(
175
+ api_keys={"key1", "key2", "admin-key"},
176
+ header="x-api-key",
177
+ key_permissions={
178
+ "admin-key": ["users.create", "users.delete", "posts.create"],
179
+ "key1": ["users.view"],
180
+ "key2": ["posts.view", "posts.create"],
181
+ }
182
+ )
183
+ ```
184
+
185
+ **Header Format**: `X-API-Key: your-api-key`
186
+
187
+ ### SessionAuthentication
188
+
189
+ Django session-based authentication (falls back to Python execution).
190
+
191
+ ```python
192
+ from django_bolt import SessionAuthentication
193
+
194
+ auth = SessionAuthentication()
195
+ ```
196
+
197
+ **Note**: This has higher overhead than JWT/API key as it requires Python execution per request.
198
+
199
+ ## Permission Guards
200
+
201
+ Guards are checked **after authentication** in Rust, providing early 403 responses without GIL overhead.
202
+
203
+ ### AllowAny
204
+
205
+ Allow unauthenticated requests (bypasses global defaults).
206
+
207
+ ```python
208
+ from django_bolt import AllowAny
209
+
210
+ @api.get("/public", guards=[AllowAny()])
211
+ async def public_endpoint():
212
+ return {"message": "Anyone can access this"}
213
+ ```
214
+
215
+ ### IsAuthenticated
216
+
217
+ Require valid authentication (any backend).
218
+
219
+ ```python
220
+ from django_bolt import IsAuthenticated
221
+
222
+ @api.get("/protected", guards=[IsAuthenticated()])
223
+ async def protected():
224
+ return {"message": "Must be authenticated"}
225
+ ```
226
+
227
+ ### IsAdminUser / IsStaff
228
+
229
+ Require admin or staff status (from JWT claims `is_superuser`, `is_admin`, or `is_staff`).
230
+
231
+ ```python
232
+ from django_bolt import IsAdminUser, IsStaff
233
+
234
+ @api.get("/admin", guards=[IsAdminUser()])
235
+ async def admin_only():
236
+ return {"message": "Admin access"}
237
+
238
+ @api.get("/staff", guards=[IsStaff()])
239
+ async def staff_only():
240
+ return {"message": "Staff access"}
241
+ ```
242
+
243
+ ### HasPermission / HasAnyPermission / HasAllPermissions
244
+
245
+ Fine-grained permission checking.
246
+
247
+ ```python
248
+ from django_bolt import HasPermission, HasAnyPermission, HasAllPermissions
249
+
250
+ # Require specific permission
251
+ @api.delete("/users/{id}", guards=[HasPermission("users.delete")])
252
+ async def delete_user(id: int):
253
+ pass
254
+
255
+ # Require at least one permission
256
+ @api.put("/users/{id}", guards=[HasAnyPermission("users.edit", "users.admin")])
257
+ async def edit_user(id: int):
258
+ pass
259
+
260
+ # Require all permissions
261
+ @api.post("/admin/reset", guards=[HasAllPermissions("admin.full", "admin.reset")])
262
+ async def reset_system():
263
+ pass
264
+ ```
265
+
266
+ ## JWT Token Class
267
+
268
+ The `Token` dataclass provides a Pythonic interface for JWT tokens with validation.
269
+
270
+ ### Creating Tokens
271
+
272
+ ```python
273
+ from django_bolt import Token
274
+ from datetime import datetime, timedelta, timezone
275
+
276
+ # Option 1: Direct instantiation
277
+ token = Token(
278
+ sub="user123",
279
+ exp=datetime.now(timezone.utc) + timedelta(hours=1),
280
+ is_staff=True,
281
+ permissions=["read", "write"],
282
+ )
283
+
284
+ # Option 2: Factory method (recommended)
285
+ token = Token.create(
286
+ sub="user123",
287
+ expires_delta=timedelta(hours=1),
288
+ is_staff=True,
289
+ is_admin=False,
290
+ permissions=["users.view", "posts.create"],
291
+ # Extra custom claims
292
+ tenant_id="acme-corp",
293
+ role="manager",
294
+ )
295
+
296
+ # Encode to JWT string
297
+ jwt_string = token.encode(secret="my-secret", algorithm="HS256")
298
+ ```
299
+
300
+ ### Decoding Tokens
301
+
302
+ ```python
303
+ # Decode and validate
304
+ token = Token.decode(
305
+ jwt_string,
306
+ secret="my-secret",
307
+ algorithm="HS256",
308
+ audience="my-api", # Optional
309
+ issuer="auth-service", # Optional
310
+ verify_exp=True, # Verify expiration
311
+ verify_nbf=True, # Verify not-before
312
+ )
313
+
314
+ print(token.sub) # "user123"
315
+ print(token.is_staff) # True
316
+ print(token.permissions) # ["users.view", "posts.create"]
317
+ print(token.extras) # {"tenant_id": "acme-corp", "role": "manager"}
318
+ ```
319
+
320
+ ### Integration with Django Users
321
+
322
+ ```python
323
+ from django_bolt.jwt_utils import create_jwt_for_user
324
+
325
+ # Create token from Django User instance
326
+ user = await User.objects.aget(username="john")
327
+ token = create_jwt_for_user(
328
+ user,
329
+ expires_in=3600, # 1 hour
330
+ extra_claims={
331
+ "permissions": ["users.view", "posts.create"],
332
+ "tenant": "acme",
333
+ }
334
+ )
335
+
336
+ # Use in login endpoint
337
+ @api.post("/login")
338
+ async def login(username: str, password: str):
339
+ # Authenticate user...
340
+ token = create_jwt_for_user(user)
341
+ return {"access_token": token, "token_type": "bearer"}
342
+ ```
343
+
344
+ ## Request Context
345
+
346
+ After authentication, the request context is populated with auth data:
347
+
348
+ ```python
349
+ @api.get("/me")
350
+ async def get_current_user_info(request):
351
+ ctx = request["context"]
352
+
353
+ # Always available after successful auth
354
+ user_id = ctx["user_id"] # str: User identifier
355
+ is_staff = ctx["is_staff"] # bool: Staff status
356
+ is_admin = ctx["is_admin"] # bool: Admin status
357
+ backend = ctx["auth_backend"] # str: "jwt", "api_key", etc.
358
+
359
+ # Available if permissions configured
360
+ permissions = ctx.get("permissions", []) # list[str]: Permission strings
361
+
362
+ # Available for JWT auth
363
+ if "auth_claims" in ctx:
364
+ claims = ctx["auth_claims"]
365
+ exp = claims["exp"] # Expiration timestamp
366
+ iat = claims["iat"] # Issued at timestamp
367
+ # Plus any custom claims
368
+
369
+ return {"user_id": user_id, "is_admin": is_admin}
370
+ ```
371
+
372
+ ### Helper Functions
373
+
374
+ ```python
375
+ from django_bolt.jwt_utils import (
376
+ get_current_user, # Fetch Django User from DB
377
+ extract_user_id_from_context,
378
+ get_auth_context,
379
+ )
380
+ from django_bolt.params import Depends
381
+
382
+ # Dependency injection to get Django User
383
+ @api.get("/profile")
384
+ async def my_profile(user=Depends(get_current_user)):
385
+ if not user:
386
+ return {"error": "Not authenticated"}
387
+
388
+ return {
389
+ "id": user.id,
390
+ "username": user.username,
391
+ "email": user.email,
392
+ }
393
+
394
+ # Extract just the user ID
395
+ @api.get("/data")
396
+ async def get_data(request):
397
+ user_id = extract_user_id_from_context(request)
398
+ # Use user_id...
399
+ ```
400
+
401
+ ## Performance Characteristics
402
+
403
+ | Operation | Performance | Notes |
404
+ |-----------|-------------|-------|
405
+ | JWT Validation | ~60k+ RPS | Entirely in Rust, no GIL |
406
+ | API Key Check | ~65k+ RPS | Simple HashSet lookup |
407
+ | Guard Check | ~65k+ RPS | In-memory permission check |
408
+ | Session Auth | ~10-15k RPS | Falls back to Python/Django |
409
+
410
+ **Key Insight**: Authentication/authorization happens in the hot path **before** calling Python handlers, so most invalid requests are rejected at ~60k RPS without ever touching the GIL.
411
+
412
+ ## Rust Implementation Details
413
+
414
+ ### Key Files
415
+
416
+ - **`src/middleware/auth.rs`**: JWT/API key validation, Claims struct, AuthContext
417
+ - **`src/metadata.rs`**: Parse Python metadata → Rust types at registration
418
+ - **`src/permissions.rs`**: Guard enum and validation logic
419
+ - **`src/lib.rs`**: Request processing pipeline
420
+
421
+ ### Performance Optimizations
422
+
423
+ 1. **Zero-copy validation**: JWT validation uses `jsonwebtoken` crate directly on request bytes
424
+ 2. **No GIL during auth**: Entire auth pipeline runs without acquiring GIL
425
+ 3. **Early rejection**: Invalid tokens/permissions rejected before Python handler call
426
+ 4. **Metadata compilation**: Auth config parsed once at registration, not per-request
427
+ 5. **Efficient data structures**: DashMap for rate limiting, HashSet for permissions
428
+
429
+ ## Testing
430
+
431
+ ```bash
432
+ # Run auth tests
433
+ uv run pytest python/django_bolt/tests/test_guards_auth.py -v
434
+ uv run pytest python/django_bolt/tests/test_jwt_token.py -v
435
+
436
+ # All tests
437
+ uv run pytest python/django_bolt/tests/ -v
438
+ ```
439
+
440
+ ## Migration from Old Code
441
+
442
+ If you had direct imports from `django_bolt.auth` or `django_bolt.permissions`, they still work:
443
+
444
+ ```python
445
+ # Old imports (still work via backward compat shim)
446
+ from django_bolt.auth import JWTAuthentication
447
+ from django_bolt.permissions import IsAuthenticated
448
+
449
+ # New organized imports (recommended)
450
+ from django_bolt.auth import JWTAuthentication, IsAuthenticated
451
+ # or
452
+ from django_bolt import JWTAuthentication, IsAuthenticated
453
+ ```
454
+
455
+ ## Examples
456
+
457
+ See `/python/examples/testproject/testproject/api.py` for real-world usage examples.
458
+
459
+ ## Benchmarks
460
+
461
+ ```bash
462
+ # Run auth-specific benchmarks
463
+ make bench-auth # TODO: Add auth benchmarks to Makefile
464
+ ```