django-bolt 0.1.0__cp310-abi3-win_amd64.whl → 0.1.2__cp310-abi3-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-bolt might be problematic. Click here for more details.

Files changed (56) hide show
  1. django_bolt/__init__.py +2 -2
  2. django_bolt/_core.pyd +0 -0
  3. django_bolt/_json.py +169 -0
  4. django_bolt/admin/static_routes.py +15 -21
  5. django_bolt/api.py +181 -61
  6. django_bolt/auth/__init__.py +2 -2
  7. django_bolt/decorators.py +15 -3
  8. django_bolt/dependencies.py +30 -24
  9. django_bolt/error_handlers.py +2 -1
  10. django_bolt/openapi/plugins.py +3 -2
  11. django_bolt/openapi/schema_generator.py +65 -20
  12. django_bolt/pagination.py +2 -1
  13. django_bolt/responses.py +3 -2
  14. django_bolt/serialization.py +5 -4
  15. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/METADATA +181 -201
  16. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/RECORD +18 -55
  17. django_bolt/auth/README.md +0 -464
  18. django_bolt/auth/REVOCATION_EXAMPLE.md +0 -391
  19. django_bolt/tests/__init__.py +0 -0
  20. django_bolt/tests/admin_tests/__init__.py +0 -1
  21. django_bolt/tests/admin_tests/conftest.py +0 -6
  22. django_bolt/tests/admin_tests/test_admin_with_django.py +0 -278
  23. django_bolt/tests/admin_tests/urls.py +0 -9
  24. django_bolt/tests/cbv/__init__.py +0 -0
  25. django_bolt/tests/cbv/test_class_views.py +0 -570
  26. django_bolt/tests/cbv/test_class_views_django_orm.py +0 -703
  27. django_bolt/tests/cbv/test_class_views_features.py +0 -1173
  28. django_bolt/tests/cbv/test_class_views_with_client.py +0 -622
  29. django_bolt/tests/conftest.py +0 -165
  30. django_bolt/tests/test_action_decorator.py +0 -399
  31. django_bolt/tests/test_auth_secret_key.py +0 -83
  32. django_bolt/tests/test_decorator_syntax.py +0 -159
  33. django_bolt/tests/test_error_handling.py +0 -481
  34. django_bolt/tests/test_file_response.py +0 -192
  35. django_bolt/tests/test_global_cors.py +0 -172
  36. django_bolt/tests/test_guards_auth.py +0 -441
  37. django_bolt/tests/test_guards_integration.py +0 -303
  38. django_bolt/tests/test_health.py +0 -283
  39. django_bolt/tests/test_integration_validation.py +0 -400
  40. django_bolt/tests/test_json_validation.py +0 -536
  41. django_bolt/tests/test_jwt_auth.py +0 -327
  42. django_bolt/tests/test_jwt_token.py +0 -458
  43. django_bolt/tests/test_logging.py +0 -837
  44. django_bolt/tests/test_logging_merge.py +0 -419
  45. django_bolt/tests/test_middleware.py +0 -492
  46. django_bolt/tests/test_middleware_server.py +0 -230
  47. django_bolt/tests/test_model_viewset.py +0 -323
  48. django_bolt/tests/test_models.py +0 -24
  49. django_bolt/tests/test_pagination.py +0 -1258
  50. django_bolt/tests/test_parameter_validation.py +0 -178
  51. django_bolt/tests/test_syntax.py +0 -626
  52. django_bolt/tests/test_testing_utilities.py +0 -163
  53. django_bolt/tests/test_testing_utilities_simple.py +0 -123
  54. django_bolt/tests/test_viewset_unified.py +0 -346
  55. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/WHEEL +0 -0
  56. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,419 +0,0 @@
1
- """Integration tests for logging with merged APIs.
2
-
3
- Tests that per-API logging configurations are preserved when multiple
4
- BoltAPI instances are merged (like during autodiscovery in runbolt).
5
- """
6
-
7
- import pytest
8
- import logging
9
- from django_bolt import BoltAPI
10
- from django_bolt.logging import LoggingConfig
11
-
12
-
13
- class TestMergedAPILoggingPreservation:
14
- """Test that merged APIs preserve per-API logging configs."""
15
-
16
- def test_handler_maps_to_original_api_after_merge(self):
17
- """Each handler should map back to its original API instance after merge."""
18
- # Create API 1 with custom logging
19
- logging_config_1 = LoggingConfig(
20
- logger_name="api1_logger",
21
- request_log_fields={"path", "client_ip"},
22
- response_log_fields={"status_code"},
23
- )
24
- api1 = BoltAPI(logging_config=logging_config_1)
25
-
26
- @api1.get("/api1")
27
- async def handler1():
28
- return {"api": 1}
29
-
30
- # Create API 2 with different logging
31
- logging_config_2 = LoggingConfig(
32
- logger_name="api2_logger",
33
- request_log_fields={"method", "path"},
34
- response_log_fields={"duration"},
35
- )
36
- api2 = BoltAPI(logging_config=logging_config_2)
37
-
38
- @api2.get("/api2")
39
- async def handler2():
40
- return {"api": 2}
41
-
42
- # Simulate merge (like runbolt does with handler_id renumbering)
43
- merged = BoltAPI(enable_logging=False)
44
- merged._handler_api_map = {}
45
-
46
- next_handler_id = 0
47
-
48
- # Merge routes from api1 (renumber handler_ids to avoid collisions)
49
- for method, path, old_handler_id, handler in api1._routes:
50
- new_handler_id = next_handler_id
51
- next_handler_id += 1
52
- merged._routes.append((method, path, new_handler_id, handler))
53
- merged._handlers[new_handler_id] = handler
54
- merged._handler_api_map[new_handler_id] = api1
55
- if handler in api1._handler_meta:
56
- merged._handler_meta[handler] = api1._handler_meta[handler]
57
-
58
- # Merge routes from api2 (renumber handler_ids to avoid collisions)
59
- for method, path, old_handler_id, handler in api2._routes:
60
- new_handler_id = next_handler_id
61
- next_handler_id += 1
62
- merged._routes.append((method, path, new_handler_id, handler))
63
- merged._handlers[new_handler_id] = handler
64
- merged._handler_api_map[new_handler_id] = api2
65
- if handler in api2._handler_meta:
66
- merged._handler_meta[handler] = api2._handler_meta[handler]
67
-
68
- merged._next_handler_id = next_handler_id
69
-
70
- # Verify handler 0 (from api1) maps to api1
71
- handler_id_api1 = 0
72
- assert handler_id_api1 in merged._handler_api_map, \
73
- f"handler_id {handler_id_api1} must exist in map"
74
-
75
- mapped_api1 = merged._handler_api_map[handler_id_api1]
76
- assert id(mapped_api1) == id(api1), \
77
- f"Handler 0 must map to api1 (expected id={id(api1)}, got id={id(mapped_api1)})"
78
-
79
- # Verify api1's logging config is preserved
80
- assert mapped_api1._logging_middleware is not None, \
81
- "api1 must have logging middleware"
82
- assert mapped_api1._logging_middleware.config.logger_name == "api1_logger", \
83
- "api1's logger name must be preserved"
84
- assert mapped_api1._logging_middleware.config.request_log_fields == {"path", "client_ip"}, \
85
- "api1's request log fields must be preserved"
86
-
87
- # Verify handler 1 (from api2) maps to api2
88
- handler_id_api2 = 1
89
- assert handler_id_api2 in merged._handler_api_map, \
90
- f"handler_id {handler_id_api2} must exist in map"
91
-
92
- mapped_api2 = merged._handler_api_map[handler_id_api2]
93
- assert id(mapped_api2) == id(api2), \
94
- f"Handler 1 must map to api2 (expected id={id(api2)}, got id={id(mapped_api2)})"
95
-
96
- # Verify api2's logging config is preserved
97
- assert mapped_api2._logging_middleware is not None, \
98
- "api2 must have logging middleware"
99
- assert mapped_api2._logging_middleware.config.logger_name == "api2_logger", \
100
- "api2's logger name must be preserved"
101
- assert mapped_api2._logging_middleware.config.request_log_fields == {"method", "path"}, \
102
- "api2's request log fields must be preserved"
103
-
104
- def test_merged_api_with_different_skip_paths(self):
105
- """Each API's skip_paths should be preserved independently."""
106
- # API 1 skips /health
107
- config1 = LoggingConfig(skip_paths={"/health"})
108
- api1 = BoltAPI(logging_config=config1)
109
-
110
- @api1.get("/api1")
111
- async def handler1():
112
- return {"api": 1}
113
-
114
- # API 2 skips /metrics
115
- config2 = LoggingConfig(skip_paths={"/metrics"})
116
- api2 = BoltAPI(logging_config=config2)
117
-
118
- @api2.get("/api2")
119
- async def handler2():
120
- return {"api": 2}
121
-
122
- # Merge
123
- merged = BoltAPI(enable_logging=False)
124
- merged._handler_api_map = {}
125
- next_handler_id = 0
126
-
127
- for method, path, old_handler_id, handler in api1._routes:
128
- new_handler_id = next_handler_id
129
- next_handler_id += 1
130
- merged._routes.append((method, path, new_handler_id, handler))
131
- merged._handlers[new_handler_id] = handler
132
- merged._handler_api_map[new_handler_id] = api1
133
-
134
- for method, path, old_handler_id, handler in api2._routes:
135
- new_handler_id = next_handler_id
136
- next_handler_id += 1
137
- merged._routes.append((method, path, new_handler_id, handler))
138
- merged._handlers[new_handler_id] = handler
139
- merged._handler_api_map[new_handler_id] = api2
140
-
141
- # Verify each API retains its own skip_paths
142
- api1_from_map = merged._handler_api_map[0]
143
- api2_from_map = merged._handler_api_map[1]
144
-
145
- assert "/health" in api1_from_map._logging_middleware.config.skip_paths
146
- assert "/health" not in api2_from_map._logging_middleware.config.skip_paths
147
- assert "/metrics" not in api1_from_map._logging_middleware.config.skip_paths
148
- assert "/metrics" in api2_from_map._logging_middleware.config.skip_paths
149
-
150
- def test_merged_api_with_different_sample_rates(self):
151
- """Each API's sample_rate should be preserved independently."""
152
- # API 1 samples at 0.05
153
- config1 = LoggingConfig(sample_rate=0.05)
154
- api1 = BoltAPI(logging_config=config1)
155
-
156
- @api1.get("/api1")
157
- async def handler1():
158
- return {"api": 1}
159
-
160
- # API 2 has no sampling
161
- config2 = LoggingConfig(sample_rate=None)
162
- api2 = BoltAPI(logging_config=config2)
163
-
164
- @api2.get("/api2")
165
- async def handler2():
166
- return {"api": 2}
167
-
168
- # Merge
169
- merged = BoltAPI(enable_logging=False)
170
- merged._handler_api_map = {}
171
- next_handler_id = 0
172
-
173
- for method, path, old_handler_id, handler in api1._routes:
174
- new_handler_id = next_handler_id
175
- next_handler_id += 1
176
- merged._routes.append((method, path, new_handler_id, handler))
177
- merged._handlers[new_handler_id] = handler
178
- merged._handler_api_map[new_handler_id] = api1
179
-
180
- for method, path, old_handler_id, handler in api2._routes:
181
- new_handler_id = next_handler_id
182
- next_handler_id += 1
183
- merged._routes.append((method, path, new_handler_id, handler))
184
- merged._handlers[new_handler_id] = handler
185
- merged._handler_api_map[new_handler_id] = api2
186
-
187
- # Verify each API retains its own sample_rate
188
- api1_from_map = merged._handler_api_map[0]
189
- api2_from_map = merged._handler_api_map[1]
190
-
191
- assert api1_from_map._logging_middleware.config.sample_rate == 0.05
192
- assert api2_from_map._logging_middleware.config.sample_rate is None
193
-
194
- def test_merged_api_with_different_min_duration_thresholds(self):
195
- """Each API's min_duration_ms should be preserved independently."""
196
- # API 1 logs only slow requests (500ms+)
197
- config1 = LoggingConfig(min_duration_ms=500)
198
- api1 = BoltAPI(logging_config=config1)
199
-
200
- @api1.get("/api1")
201
- async def handler1():
202
- return {"api": 1}
203
-
204
- # API 2 logs all requests (no threshold)
205
- config2 = LoggingConfig(min_duration_ms=None)
206
- api2 = BoltAPI(logging_config=config2)
207
-
208
- @api2.get("/api2")
209
- async def handler2():
210
- return {"api": 2}
211
-
212
- # Merge
213
- merged = BoltAPI(enable_logging=False)
214
- merged._handler_api_map = {}
215
- next_handler_id = 0
216
-
217
- for method, path, old_handler_id, handler in api1._routes:
218
- new_handler_id = next_handler_id
219
- next_handler_id += 1
220
- merged._routes.append((method, path, new_handler_id, handler))
221
- merged._handlers[new_handler_id] = handler
222
- merged._handler_api_map[new_handler_id] = api1
223
-
224
- for method, path, old_handler_id, handler in api2._routes:
225
- new_handler_id = next_handler_id
226
- next_handler_id += 1
227
- merged._routes.append((method, path, new_handler_id, handler))
228
- merged._handlers[new_handler_id] = handler
229
- merged._handler_api_map[new_handler_id] = api2
230
-
231
- # Verify each API retains its own min_duration_ms
232
- api1_from_map = merged._handler_api_map[0]
233
- api2_from_map = merged._handler_api_map[1]
234
-
235
- assert api1_from_map._logging_middleware.config.min_duration_ms == 500
236
- assert api2_from_map._logging_middleware.config.min_duration_ms is None
237
-
238
- def test_merged_api_one_with_logging_one_without(self):
239
- """API with logging and API without logging should coexist."""
240
- # API 1 with logging
241
- config1 = LoggingConfig(logger_name="api1_logger")
242
- api1 = BoltAPI(logging_config=config1)
243
-
244
- @api1.get("/api1")
245
- async def handler1():
246
- return {"api": 1}
247
-
248
- # API 2 without logging
249
- api2 = BoltAPI(enable_logging=False)
250
-
251
- @api2.get("/api2")
252
- async def handler2():
253
- return {"api": 2}
254
-
255
- # Merge
256
- merged = BoltAPI(enable_logging=False)
257
- merged._handler_api_map = {}
258
- next_handler_id = 0
259
-
260
- for method, path, old_handler_id, handler in api1._routes:
261
- new_handler_id = next_handler_id
262
- next_handler_id += 1
263
- merged._routes.append((method, path, new_handler_id, handler))
264
- merged._handlers[new_handler_id] = handler
265
- merged._handler_api_map[new_handler_id] = api1
266
-
267
- for method, path, old_handler_id, handler in api2._routes:
268
- new_handler_id = next_handler_id
269
- next_handler_id += 1
270
- merged._routes.append((method, path, new_handler_id, handler))
271
- merged._handlers[new_handler_id] = handler
272
- merged._handler_api_map[new_handler_id] = api2
273
-
274
- # Verify api1 has logging, api2 doesn't
275
- api1_from_map = merged._handler_api_map[0]
276
- api2_from_map = merged._handler_api_map[1]
277
-
278
- assert api1_from_map._logging_middleware is not None, \
279
- "api1 must have logging middleware"
280
- assert api2_from_map._logging_middleware is None, \
281
- "api2 must not have logging middleware"
282
-
283
-
284
- class TestAPIDeduplication:
285
- """Test that duplicate API instances are properly deduplicated."""
286
-
287
- def test_deduplication_by_object_identity(self):
288
- """Duplicate API instances (same object) should be deduplicated by id()."""
289
- # Create one API
290
- api1 = BoltAPI()
291
-
292
- @api1.get("/test")
293
- async def handler():
294
- return {"test": True}
295
-
296
- # Simulate autodiscovery finding the SAME api object twice
297
- apis = [
298
- ("testproject.api:api", api1),
299
- ("testproject.api:api", api1), # Same object reference
300
- ]
301
-
302
- # Deduplicate
303
- seen_ids = set()
304
- deduplicated = []
305
- for api_path, api in apis:
306
- api_id = id(api)
307
- if api_id not in seen_ids:
308
- seen_ids.add(api_id)
309
- deduplicated.append((api_path, api))
310
-
311
- # Should only have 1 entry
312
- assert len(deduplicated) == 1, \
313
- "Duplicate API instances must be deduplicated"
314
- assert deduplicated[0] == ("testproject.api:api", api1)
315
-
316
- def test_different_instances_are_not_deduplicated(self):
317
- """Different API instances should NOT be deduplicated."""
318
- # Create two different APIs
319
- api1 = BoltAPI()
320
-
321
- @api1.get("/api1")
322
- async def handler1():
323
- return {"api": 1}
324
-
325
- api2 = BoltAPI()
326
-
327
- @api2.get("/api2")
328
- async def handler2():
329
- return {"api": 2}
330
-
331
- # Both should be kept (different objects)
332
- apis = [
333
- ("app1.api:api", api1),
334
- ("app2.api:api", api2),
335
- ]
336
-
337
- # Deduplicate
338
- seen_ids = set()
339
- deduplicated = []
340
- for api_path, api in apis:
341
- api_id = id(api)
342
- if api_id not in seen_ids:
343
- seen_ids.add(api_id)
344
- deduplicated.append((api_path, api))
345
-
346
- # Should have both entries (different objects)
347
- assert len(deduplicated) == 2, \
348
- "Different API instances must not be deduplicated"
349
- assert id(deduplicated[0][1]) != id(deduplicated[1][1])
350
-
351
-
352
- class TestLoggingWithHandlerCalls:
353
- """Test that logging actually works when handlers are called."""
354
-
355
- def test_logging_middleware_logs_on_handler_call(self, caplog):
356
- """Logging middleware should log when handler is invoked."""
357
- config = LoggingConfig(logger_name="test.api", min_duration_ms=None)
358
- api = BoltAPI(logging_config=config)
359
-
360
- @api.get("/test")
361
- async def test_handler():
362
- return {"result": "success"}
363
-
364
- # Simulate calling the handler (simplified)
365
- request = {
366
- "method": "GET",
367
- "path": "/test",
368
- "query_params": {},
369
- "headers": {},
370
- }
371
-
372
- # Log the response manually (simulating what BoltAPI.call_handler does)
373
- with caplog.at_level(logging.INFO, logger="test.api"):
374
- api._logging_middleware.log_response(request, 200, 0.1)
375
-
376
- # Should have logged
377
- assert len(caplog.records) > 0, "Handler response should be logged"
378
- assert caplog.records[0].status_code == 200
379
-
380
- def test_logging_middleware_logs_exceptions(self, caplog):
381
- """Logging middleware should log exceptions."""
382
- config = LoggingConfig(logger_name="test.api")
383
- api = BoltAPI(logging_config=config)
384
-
385
- @api.get("/error")
386
- async def error_handler():
387
- raise ValueError("Test error")
388
-
389
- # Simulate exception logging
390
- request = {
391
- "method": "GET",
392
- "path": "/error",
393
- }
394
-
395
- exc = ValueError("Test error")
396
-
397
- with caplog.at_level(logging.ERROR, logger="test.api"):
398
- api._logging_middleware.log_exception(request, exc, exc_info=False)
399
-
400
- # Should have logged exception
401
- assert len(caplog.records) > 0, "Exception should be logged"
402
- assert "ValueError" in caplog.records[0].message
403
- assert "Test error" in caplog.records[0].message
404
-
405
- def test_disabled_logging_does_not_log(self, caplog):
406
- """API with logging disabled should not log."""
407
- api = BoltAPI(enable_logging=False)
408
-
409
- @api.get("/test")
410
- async def test_handler():
411
- return {"result": "success"}
412
-
413
- # Verify no logging middleware
414
- assert api._logging_middleware is None, \
415
- "API with enable_logging=False must not have logging middleware"
416
-
417
-
418
- if __name__ == "__main__":
419
- pytest.main([__file__, "-v", "-s"])