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,400 @@
1
+ """
2
+ Integration tests for parameter validation with real-world use cases.
3
+
4
+ Tests the complete flow from route registration to request handling.
5
+ """
6
+ import pytest
7
+ import msgspec
8
+ from django_bolt import BoltAPI
9
+ from django_bolt.params import Query, Body, Header, Cookie, Form, Path
10
+
11
+
12
+ class User(msgspec.Struct):
13
+ id: int
14
+ name: str
15
+ email: str
16
+
17
+
18
+ class UserCreate(msgspec.Struct):
19
+ name: str
20
+ email: str
21
+ password: str
22
+
23
+
24
+ class UserUpdate(msgspec.Struct):
25
+ name: str | None = None
26
+ email: str | None = None
27
+
28
+
29
+ class SearchFilters(msgspec.Struct):
30
+ query: str
31
+ tags: list[str] = []
32
+ min_score: float = 0.0
33
+
34
+
35
+ # ============================================================================
36
+ # Use Case 1: CRUD Operations with Proper HTTP Methods
37
+ # ============================================================================
38
+
39
+ def test_crud_operations_validation():
40
+ """Test that CRUD operations use correct HTTP methods and parameters."""
41
+ api = BoltAPI()
42
+
43
+ # ✓ List (GET with query params)
44
+ @api.get("/users")
45
+ async def list_users(page: int = 1, limit: int = 10, search: str = ""):
46
+ return {"users": [], "page": page, "limit": limit}
47
+
48
+ # ✓ Get single (GET with path param)
49
+ @api.get("/users/{user_id}")
50
+ async def get_user(user_id: int):
51
+ return {"id": user_id, "name": "John"}
52
+
53
+ # ✓ Create (POST with body)
54
+ @api.post("/users")
55
+ async def create_user(user: UserCreate):
56
+ return {"id": 1, "name": user.name}
57
+
58
+ # ✓ Update (PUT with path + body)
59
+ @api.put("/users/{user_id}")
60
+ async def update_user(user_id: int, user: UserUpdate):
61
+ return {"id": user_id, "updated": True}
62
+
63
+ # ✓ Partial update (PATCH with path + body)
64
+ @api.patch("/users/{user_id}")
65
+ async def patch_user(user_id: int, user: UserUpdate):
66
+ return {"id": user_id, "patched": True}
67
+
68
+ # ✓ Delete (DELETE with path param, no body)
69
+ @api.delete("/users/{user_id}")
70
+ async def delete_user(user_id: int):
71
+ return {"deleted": True}
72
+
73
+ assert len(api._routes) == 6
74
+
75
+ # Verify parameter sources
76
+ for method, path, handler_id, fn in api._routes:
77
+ meta = api._handler_meta[fn]
78
+
79
+ if method == "GET" and path == "/users":
80
+ # Query params should be inferred
81
+ for field in meta["fields"]:
82
+ assert field["source"] == "query"
83
+
84
+ elif method == "GET" and "user_id" in path:
85
+ # Path param should be detected
86
+ user_id_field = next(f for f in meta["fields"] if f["name"] == "user_id")
87
+ assert user_id_field["source"] == "path"
88
+
89
+ elif method in ("POST", "PUT", "PATCH"):
90
+ # Should have body params
91
+ body_fields = [f for f in meta["fields"] if f["source"] == "body"]
92
+ if "POST" in method or "PUT" in method or "PATCH" in method:
93
+ # At least one body field expected
94
+ assert len(body_fields) >= 1 or any(
95
+ f["source"] == "path" for f in meta["fields"]
96
+ )
97
+
98
+
99
+ # ============================================================================
100
+ # Use Case 2: Search/Filter Endpoints
101
+ # ============================================================================
102
+
103
+ def test_search_endpoints_with_complex_filters():
104
+ """Test search endpoints with multiple query parameters."""
105
+ api = BoltAPI()
106
+
107
+ # ✓ Simple search with auto-inference
108
+ @api.get("/search")
109
+ async def simple_search(q: str, limit: int = 10):
110
+ return {"results": [], "query": q}
111
+
112
+ # ✓ Advanced search with explicit markers
113
+ @api.get("/advanced-search")
114
+ async def advanced_search(
115
+ query: str = Query(min_length=3),
116
+ category: str = Query(default="all"),
117
+ min_price: float = Query(ge=0, default=0),
118
+ max_price: float = Query(le=10000, default=10000),
119
+ page: int = Query(ge=1, default=1)
120
+ ):
121
+ return {"results": []}
122
+
123
+ # ✓ Search with struct body (POST for complex filters)
124
+ @api.post("/search/advanced")
125
+ async def complex_search(filters: SearchFilters):
126
+ return {"results": [], "filters": filters}
127
+
128
+ assert len(api._routes) == 3
129
+
130
+
131
+ # ============================================================================
132
+ # Use Case 3: Authentication/Authorization Headers
133
+ # ============================================================================
134
+
135
+ def test_authentication_headers():
136
+ """Test endpoints with authentication headers."""
137
+ api = BoltAPI()
138
+
139
+ @api.get("/protected")
140
+ async def protected_endpoint(
141
+ authorization: str = Header(alias="Authorization"),
142
+ user_agent: str = Header(alias="User-Agent", default="unknown")
143
+ ):
144
+ return {"authenticated": True}
145
+
146
+ @api.get("/api-key")
147
+ async def api_key_endpoint(
148
+ api_key: str = Header(alias="X-API-Key")
149
+ ):
150
+ return {"valid": True}
151
+
152
+ assert len(api._routes) == 2
153
+
154
+ # Verify header sources
155
+ for method, path, handler_id, fn in api._routes:
156
+ meta = api._handler_meta[fn]
157
+ for field in meta["fields"]:
158
+ assert field["source"] == "header"
159
+
160
+
161
+ # ============================================================================
162
+ # Use Case 4: File Upload Endpoints
163
+ # ============================================================================
164
+
165
+ def test_file_upload_endpoints():
166
+ """Test file upload with form data."""
167
+ from django_bolt.params import File
168
+
169
+ api = BoltAPI()
170
+
171
+ @api.post("/upload")
172
+ async def upload_file(
173
+ file: bytes = File(),
174
+ description: str = Form(default="")
175
+ ):
176
+ return {"uploaded": True, "size": len(file)}
177
+
178
+ @api.post("/upload/multiple")
179
+ async def upload_multiple(
180
+ files: list[bytes] = File(),
181
+ category: str = Form()
182
+ ):
183
+ return {"uploaded": len(files)}
184
+
185
+ assert len(api._routes) == 2
186
+
187
+
188
+ # ============================================================================
189
+ # Use Case 5: Mixed Parameter Sources
190
+ # ============================================================================
191
+
192
+ def test_mixed_parameter_sources():
193
+ """Test endpoints with parameters from multiple sources."""
194
+ api = BoltAPI()
195
+
196
+ @api.get("/items/{item_id}")
197
+ async def get_item_with_options(
198
+ item_id: int, # Path
199
+ include_details: bool = False, # Query (inferred)
200
+ user_agent: str = Header(alias="User-Agent", default="unknown"), # Header
201
+ session_id: str = Cookie(default="") # Cookie
202
+ ):
203
+ return {"item_id": item_id, "details": include_details}
204
+
205
+ meta = api._handler_meta[api._routes[0][3]]
206
+
207
+ sources = {f["name"]: f["source"] for f in meta["fields"]}
208
+ assert sources["item_id"] == "path"
209
+ assert sources["include_details"] == "query"
210
+ assert sources["user_agent"] == "header"
211
+ assert sources["session_id"] == "cookie"
212
+
213
+
214
+ # ============================================================================
215
+ # Use Case 6: Validation Error Cases
216
+ # ============================================================================
217
+
218
+ def test_invalid_get_with_body():
219
+ """Test that GET with body parameter is rejected."""
220
+ api = BoltAPI()
221
+
222
+ with pytest.raises(TypeError) as exc_info:
223
+ @api.get("/invalid")
224
+ async def invalid_get(data: UserCreate):
225
+ return {"error": "should not register"}
226
+
227
+ assert "GET /invalid cannot have body parameters" in str(exc_info.value)
228
+ assert "data" in str(exc_info.value)
229
+
230
+
231
+ def test_invalid_delete_with_body():
232
+ """Test that DELETE with body parameter is rejected."""
233
+ api = BoltAPI()
234
+
235
+ with pytest.raises(TypeError) as exc_info:
236
+ @api.delete("/items/{item_id}")
237
+ async def invalid_delete(item_id: int, data: UserCreate):
238
+ return {"deleted": True}
239
+
240
+ assert "DELETE" in str(exc_info.value)
241
+ assert "cannot have body parameters" in str(exc_info.value)
242
+
243
+
244
+ # ============================================================================
245
+ # Use Case 7: Nested Routes with Path Parameters
246
+ # ============================================================================
247
+
248
+ def test_nested_resources_with_path_params():
249
+ """Test nested resource routes with multiple path parameters."""
250
+ api = BoltAPI()
251
+
252
+ @api.get("/users/{user_id}/posts/{post_id}")
253
+ async def get_user_post(user_id: int, post_id: int):
254
+ return {"user_id": user_id, "post_id": post_id}
255
+
256
+ @api.get("/organizations/{org_id}/teams/{team_id}/members/{member_id}")
257
+ async def get_team_member(org_id: int, team_id: int, member_id: int):
258
+ return {"org": org_id, "team": team_id, "member": member_id}
259
+
260
+ # Verify all path params detected
261
+ for method, path, handler_id, fn in api._routes:
262
+ meta = api._handler_meta[fn]
263
+ path_fields = [f for f in meta["fields"] if f["source"] == "path"]
264
+
265
+ # Count expected path params
266
+ import re
267
+ expected_params = len(re.findall(r'\{(\w+)\}', path))
268
+ assert len(path_fields) == expected_params
269
+
270
+
271
+ # ============================================================================
272
+ # Use Case 8: Optional vs Required Parameters
273
+ # ============================================================================
274
+
275
+ def test_optional_and_required_parameters():
276
+ """Test proper handling of optional and required parameters."""
277
+ api = BoltAPI()
278
+
279
+ @api.get("/items")
280
+ async def list_items(
281
+ category: str, # Required (no default)
282
+ min_price: float = 0, # Optional (has default)
283
+ max_price: float | None = None, # Optional (nullable)
284
+ tags: list[str] = [] # Optional (default empty list)
285
+ ):
286
+ return {"items": []}
287
+
288
+ meta = api._handler_meta[api._routes[0][3]]
289
+
290
+ category_field = next(f for f in meta["fields"] if f["name"] == "category")
291
+ min_price_field = next(f for f in meta["fields"] if f["name"] == "min_price")
292
+ max_price_field = next(f for f in meta["fields"] if f["name"] == "max_price")
293
+
294
+ # Check field definitions
295
+ assert category_field["field_def"].is_required
296
+ assert not min_price_field["field_def"].is_required
297
+ assert not max_price_field["field_def"].is_required
298
+
299
+
300
+ # ============================================================================
301
+ # Use Case 9: Explicit Path() Marker (Edge Case)
302
+ # ============================================================================
303
+
304
+ def test_explicit_path_marker():
305
+ """Test explicit Path() marker (rarely needed but supported)."""
306
+ api = BoltAPI()
307
+
308
+ @api.get("/items/{item_id}")
309
+ async def get_item(item_id: int = Path()):
310
+ return {"id": item_id}
311
+
312
+ meta = api._handler_meta[api._routes[0][3]]
313
+ item_id_field = next(f for f in meta["fields"] if f["name"] == "item_id")
314
+ assert item_id_field["source"] == "path"
315
+
316
+
317
+ # ============================================================================
318
+ # Use Case 10: Complex Real-World API
319
+ # ============================================================================
320
+
321
+ def test_real_world_api_structure():
322
+ """Test a realistic API with multiple endpoint types."""
323
+ api = BoltAPI(prefix="/api/v1")
324
+
325
+ # Auth endpoints
326
+ @api.post("/auth/login")
327
+ async def login(email: str = Form(), password: str = Form()):
328
+ return {"token": "abc123"}
329
+
330
+ @api.post("/auth/logout")
331
+ async def logout(authorization: str = Header(alias="Authorization")):
332
+ return {"logged_out": True}
333
+
334
+ # User management
335
+ @api.get("/users/{user_id}")
336
+ async def get_user(user_id: int, include_posts: bool = False):
337
+ return {"id": user_id}
338
+
339
+ @api.put("/users/{user_id}")
340
+ async def update_user(user_id: int, user: UserUpdate):
341
+ return {"updated": True}
342
+
343
+ # Search
344
+ @api.get("/search")
345
+ async def search(q: str, type: str = "all", page: int = 1):
346
+ return {"results": []}
347
+
348
+ # Analytics (complex filters via POST)
349
+ @api.post("/analytics/report")
350
+ async def generate_report(filters: SearchFilters):
351
+ return {"report_id": "r123"}
352
+
353
+ assert len(api._routes) == 6
354
+
355
+ # Verify no GET endpoints have body params
356
+ for method, path, handler_id, fn in api._routes:
357
+ if method == "GET":
358
+ meta = api._handler_meta[fn]
359
+ body_fields = [f for f in meta["fields"] if f["source"] == "body"]
360
+ assert len(body_fields) == 0, f"GET {path} should not have body params"
361
+
362
+
363
+ # ============================================================================
364
+ # Use Case 11: Error Messages Quality
365
+ # ============================================================================
366
+
367
+ def test_error_message_includes_all_solutions():
368
+ """Verify error messages provide all necessary information."""
369
+ api = BoltAPI()
370
+
371
+ with pytest.raises(TypeError) as exc_info:
372
+ @api.get("/bad-endpoint")
373
+ async def bad_handler(complex_data: User):
374
+ return {"data": complex_data}
375
+
376
+ error_msg = str(exc_info.value)
377
+
378
+ # Should include handler name
379
+ assert "bad_handler" in error_msg
380
+
381
+ # Should include method and path
382
+ assert "GET" in error_msg
383
+ assert "/bad-endpoint" in error_msg
384
+
385
+ # Should list problematic parameters
386
+ assert "complex_data" in error_msg
387
+
388
+ # Should provide solutions
389
+ assert "Solutions:" in error_msg
390
+ assert "POST/PUT/PATCH" in error_msg
391
+ assert "Query()" in error_msg
392
+ assert "simple types" in error_msg
393
+
394
+
395
+ # ============================================================================
396
+ # Run all tests
397
+ # ============================================================================
398
+
399
+ if __name__ == "__main__":
400
+ pytest.main([__file__, "-v"])