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,224 @@
1
+ """
2
+ Permission/guard system for Django-Bolt.
3
+
4
+ Provides DRF-inspired permission classes (called "guards" in Litestar terminology)
5
+ that are compiled to Rust types for zero-GIL performance.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class BasePermission(ABC):
13
+ """
14
+ Base class for permission guards.
15
+
16
+ Guards are evaluated in Rust after authentication to determine if
17
+ a request should be allowed. This happens before the Python handler
18
+ is called, enabling early 403 responses without GIL overhead.
19
+ """
20
+
21
+ @property
22
+ @abstractmethod
23
+ def guard_name(self) -> str:
24
+ """Return the guard type name for Rust compilation"""
25
+ pass
26
+
27
+ @abstractmethod
28
+ def to_metadata(self) -> Dict[str, Any]:
29
+ """
30
+ Compile this permission guard into metadata for Rust.
31
+
32
+ Returns a dict that will be parsed by Rust into typed enums.
33
+ """
34
+ pass
35
+
36
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
37
+ """
38
+ Check if the authenticated user has permission (Python fallback).
39
+
40
+ This is primarily for documentation/compatibility. The actual check
41
+ happens in Rust for performance.
42
+ """
43
+ return True
44
+
45
+
46
+ class AllowAny(BasePermission):
47
+ """
48
+ Allow any request, authenticated or not.
49
+
50
+ This is the default permission when no guards are specified.
51
+ Using this explicitly bypasses any global default permissions.
52
+ """
53
+
54
+ @property
55
+ def guard_name(self) -> str:
56
+ return "allow_any"
57
+
58
+ def to_metadata(self) -> Dict[str, Any]:
59
+ return {"type": "allow_any"}
60
+
61
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
62
+ return True
63
+
64
+
65
+ class IsAuthenticated(BasePermission):
66
+ """
67
+ Require that the request is authenticated.
68
+
69
+ Returns 401 if no authentication was successful.
70
+ """
71
+
72
+ @property
73
+ def guard_name(self) -> str:
74
+ return "is_authenticated"
75
+
76
+ def to_metadata(self) -> Dict[str, Any]:
77
+ return {"type": "is_authenticated"}
78
+
79
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
80
+ return auth_context is not None and auth_context.user_id is not None
81
+
82
+
83
+ class IsAdminUser(BasePermission):
84
+ """
85
+ Require that the authenticated user is an admin/superuser.
86
+
87
+ Returns 403 if user is not admin.
88
+ Requires JWT token to include 'is_superuser' or 'is_admin' claim.
89
+ """
90
+
91
+ @property
92
+ def guard_name(self) -> str:
93
+ return "is_admin"
94
+
95
+ def to_metadata(self) -> Dict[str, Any]:
96
+ return {"type": "is_admin"}
97
+
98
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
99
+ return auth_context is not None and auth_context.is_admin
100
+
101
+
102
+ class IsStaff(BasePermission):
103
+ """
104
+ Require that the authenticated user is staff.
105
+
106
+ Returns 403 if user is not staff.
107
+ Requires JWT token to include 'is_staff' claim.
108
+ """
109
+
110
+ @property
111
+ def guard_name(self) -> str:
112
+ return "is_staff"
113
+
114
+ def to_metadata(self) -> Dict[str, Any]:
115
+ return {"type": "is_staff"}
116
+
117
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
118
+ return auth_context is not None and auth_context.is_staff
119
+
120
+
121
+ class HasPermission(BasePermission):
122
+ """
123
+ Require that the authenticated user has a specific permission.
124
+
125
+ Args:
126
+ permission: Permission string (e.g., "app.view_model", "api.create_resource")
127
+
128
+ For JWT: token should include "permissions" claim as list of strings
129
+ For API keys: configured via key_permissions mapping in APIKeyAuthentication
130
+ """
131
+
132
+ def __init__(self, permission: str):
133
+ self.permission = permission
134
+
135
+ @property
136
+ def guard_name(self) -> str:
137
+ return "has_permission"
138
+
139
+ def to_metadata(self) -> Dict[str, Any]:
140
+ return {
141
+ "type": "has_permission",
142
+ "permission": self.permission,
143
+ }
144
+
145
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
146
+ if auth_context is None or auth_context.permissions is None:
147
+ return False
148
+ return self.permission in auth_context.permissions
149
+
150
+
151
+ class HasAnyPermission(BasePermission):
152
+ """
153
+ Require that the authenticated user has at least one of the specified permissions.
154
+
155
+ Args:
156
+ permissions: List of permission strings
157
+ """
158
+
159
+ def __init__(self, *permissions: str):
160
+ self.permissions = list(permissions)
161
+
162
+ @property
163
+ def guard_name(self) -> str:
164
+ return "has_any_permission"
165
+
166
+ def to_metadata(self) -> Dict[str, Any]:
167
+ return {
168
+ "type": "has_any_permission",
169
+ "permissions": self.permissions,
170
+ }
171
+
172
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
173
+ if auth_context is None or auth_context.permissions is None:
174
+ return False
175
+ return any(perm in auth_context.permissions for perm in self.permissions)
176
+
177
+
178
+ class HasAllPermissions(BasePermission):
179
+ """
180
+ Require that the authenticated user has all of the specified permissions.
181
+
182
+ Args:
183
+ permissions: List of permission strings
184
+ """
185
+
186
+ def __init__(self, *permissions: str):
187
+ self.permissions = list(permissions)
188
+
189
+ @property
190
+ def guard_name(self) -> str:
191
+ return "has_all_permissions"
192
+
193
+ def to_metadata(self) -> Dict[str, Any]:
194
+ return {
195
+ "type": "has_all_permissions",
196
+ "permissions": self.permissions,
197
+ }
198
+
199
+ def has_permission(self, auth_context: Optional[Any]) -> bool:
200
+ if auth_context is None or auth_context.permissions is None:
201
+ return False
202
+ return all(perm in auth_context.permissions for perm in self.permissions)
203
+
204
+
205
+ def get_default_permission_classes() -> List[BasePermission]:
206
+ """
207
+ Get default permission classes from Django settings.
208
+
209
+ Looks for BOLT_DEFAULT_PERMISSION_CLASSES in settings. If not found,
210
+ returns [AllowAny()] (no restrictions by default).
211
+ """
212
+ try:
213
+ from django.conf import settings
214
+ from django.core.exceptions import ImproperlyConfigured
215
+ try:
216
+ if hasattr(settings, 'BOLT_DEFAULT_PERMISSION_CLASSES'):
217
+ return settings.BOLT_DEFAULT_PERMISSION_CLASSES
218
+ except ImproperlyConfigured:
219
+ # Settings not configured, return default
220
+ pass
221
+ except (ImportError, AttributeError):
222
+ pass
223
+
224
+ return [AllowAny()]
@@ -0,0 +1,212 @@
1
+ """
2
+ JWT utility functions for Django-Bolt.
3
+
4
+ Provides helper functions to create JWT tokens for Django users and
5
+ extract user information from request context.
6
+ """
7
+ import time
8
+ import jwt
9
+ from typing import Any, Dict, Optional
10
+ from django.contrib.auth import get_user_model
11
+
12
+
13
+ def create_jwt_for_user(
14
+ user,
15
+ secret: Optional[str] = None,
16
+ algorithm: str = "HS256",
17
+ expires_in: int = 3600,
18
+ extra_claims: Optional[Dict[str, Any]] = None
19
+ ) -> str:
20
+ """
21
+ Create a JWT token for a Django User.
22
+
23
+ Args:
24
+ user: Django User model instance
25
+ secret: JWT secret key. If None, uses Django's SECRET_KEY
26
+ algorithm: JWT algorithm (default: "HS256")
27
+ expires_in: Token expiration time in seconds (default: 3600 = 1 hour)
28
+ extra_claims: Additional claims to include in the token
29
+
30
+ Returns:
31
+ JWT token string
32
+
33
+ Standard claims included:
34
+ - sub: user.id (subject - user primary key)
35
+ - exp: expiration time (current time + expires_in)
36
+ - iat: issued at time (current timestamp)
37
+ - is_staff: user.is_staff
38
+ - is_superuser: user.is_superuser
39
+ - username: user.username (for reference)
40
+ - email: user.email (if available)
41
+
42
+ Example:
43
+ ```python
44
+ from django.contrib.auth import get_user_model
45
+ from django_bolt.jwt_utils import create_jwt_for_user
46
+
47
+ User = get_user_model()
48
+ user = await User.objects.aget(username="john")
49
+
50
+ # Create a basic token
51
+ token = create_jwt_for_user(user)
52
+
53
+ # Create token with custom expiration and extra claims
54
+ token = create_jwt_for_user(
55
+ user,
56
+ expires_in=7200, # 2 hours
57
+ extra_claims={
58
+ "permissions": ["read", "write"],
59
+ "role": "admin",
60
+ "tenant_id": "acme-corp"
61
+ }
62
+ )
63
+ ```
64
+ """
65
+ # Use Django SECRET_KEY if no secret provided
66
+ if secret is None:
67
+ from django.conf import settings
68
+ secret = settings.SECRET_KEY
69
+
70
+ # Build standard claims
71
+ now = int(time.time())
72
+ payload = {
73
+ "sub": str(user.id), # Subject: user ID as string
74
+ "exp": now + expires_in, # Expiration time
75
+ "iat": now, # Issued at
76
+ "is_staff": user.is_staff,
77
+ "is_superuser": user.is_superuser,
78
+ "username": user.username,
79
+ }
80
+
81
+ # Add email if available
82
+ if hasattr(user, 'email') and user.email:
83
+ payload["email"] = user.email
84
+
85
+ # Add first/last name if available
86
+ if hasattr(user, 'first_name') and user.first_name:
87
+ payload["first_name"] = user.first_name
88
+ if hasattr(user, 'last_name') and user.last_name:
89
+ payload["last_name"] = user.last_name
90
+
91
+ # Merge extra claims
92
+ if extra_claims:
93
+ payload.update(extra_claims)
94
+
95
+ return jwt.encode(payload, secret, algorithm=algorithm)
96
+
97
+
98
+ async def get_current_user(request: Dict[str, Any]):
99
+ """
100
+ Dependency function to extract and fetch Django User from request context.
101
+
102
+ This is a reusable dependency that can be used with Depends() to inject
103
+ the current authenticated Django User into your handlers.
104
+
105
+ Args:
106
+ request: Request dictionary with context
107
+
108
+ Returns:
109
+ Django User instance or None if not authenticated or not found
110
+
111
+ Example:
112
+ ```python
113
+ from django_bolt import BoltAPI
114
+ from django_bolt.auth import JWTAuthentication
115
+ from django_bolt.permissions import IsAuthenticated
116
+ from django_bolt.params import Depends
117
+ from django_bolt.jwt_utils import get_current_user
118
+
119
+ api = BoltAPI()
120
+
121
+ @api.get(
122
+ "/me",
123
+ auth=[JWTAuthentication()],
124
+ guards=[IsAuthenticated()]
125
+ )
126
+ async def get_my_profile(user=Depends(get_current_user)):
127
+ return {
128
+ "id": user.id,
129
+ "username": user.username,
130
+ "email": user.email,
131
+ "is_staff": user.is_staff,
132
+ }
133
+ ```
134
+ """
135
+ User = get_user_model()
136
+ context = request.get("context", {})
137
+ user_id = context.get("user_id")
138
+
139
+ if not user_id:
140
+ return None
141
+
142
+ try:
143
+ # Fetch User from database using async ORM
144
+ user = await User.objects.aget(pk=user_id)
145
+ return user
146
+ except (User.DoesNotExist, ValueError, TypeError):
147
+ return None
148
+
149
+
150
+ def extract_user_id_from_context(request: Dict[str, Any]) -> Optional[str]:
151
+ """
152
+ Extract user_id from request context.
153
+
154
+ Args:
155
+ request: Request dictionary with context
156
+
157
+ Returns:
158
+ User ID as string or None if not present
159
+
160
+ Example:
161
+ ```python
162
+ @api.get("/data")
163
+ async def get_data(request: dict):
164
+ user_id = extract_user_id_from_context(request)
165
+ if user_id:
166
+ # Use user_id for filtering, logging, etc.
167
+ data = await MyModel.objects.filter(user_id=user_id).all()
168
+ return {"data": data}
169
+ return {"error": "Not authenticated"}
170
+ ```
171
+ """
172
+ context = request.get("context", {})
173
+ return context.get("user_id")
174
+
175
+
176
+ def get_auth_context(request: Dict[str, Any]) -> Dict[str, Any]:
177
+ """
178
+ Get the full authentication context from request.
179
+
180
+ Args:
181
+ request: Request dictionary with context
182
+
183
+ Returns:
184
+ Authentication context dictionary containing:
185
+ - user_id: User identifier
186
+ - is_staff: Staff status boolean
187
+ - is_admin: Admin/superuser status boolean
188
+ - auth_backend: Authentication backend used (jwt, api_key, etc.)
189
+ - permissions: List of permissions (if available)
190
+ - auth_claims: JWT claims dict (if JWT auth was used)
191
+
192
+ Example:
193
+ ```python
194
+ @api.get("/admin/stats")
195
+ async def admin_stats(request: dict):
196
+ auth_ctx = get_auth_context(request)
197
+
198
+ if not auth_ctx.get("is_admin"):
199
+ return {"error": "Admin access required"}
200
+
201
+ # Use user_id for audit logging
202
+ user_id = auth_ctx["user_id"]
203
+ backend = auth_ctx["auth_backend"]
204
+
205
+ return {
206
+ "authenticated_as": user_id,
207
+ "via": backend,
208
+ "stats": {...}
209
+ }
210
+ ```
211
+ """
212
+ return request.get("context", {})