debugbundle-python 0.1.2__tar.gz → 0.1.3__tar.gz

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 (38) hide show
  1. {debugbundle_python-0.1.2/src/debugbundle_python.egg-info → debugbundle_python-0.1.3}/PKG-INFO +1 -1
  2. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/pyproject.toml +1 -1
  3. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/core.py +29 -2
  4. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3/src/debugbundle_python.egg-info}/PKG-INFO +1 -1
  5. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_remote_config.py +85 -1
  6. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/LICENSE +0 -0
  7. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/README.md +0 -0
  8. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/setup.cfg +0 -0
  9. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/__init__.py +0 -0
  10. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/config.py +0 -0
  11. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/__init__.py +0 -0
  12. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/common.py +0 -0
  13. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/django.py +0 -0
  14. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/fastapi.py +0 -0
  15. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/flask.py +0 -0
  16. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_django.py +0 -0
  17. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_fastapi.py +0 -0
  18. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/integrations/relay_flask.py +0 -0
  19. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/logger_integrations.py +0 -0
  20. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/py.typed +0 -0
  21. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/redaction.py +0 -0
  22. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/relay.py +0 -0
  23. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/suppression.py +0 -0
  24. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/transport.py +0 -0
  25. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle/trigger_token.py +0 -0
  26. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/SOURCES.txt +0 -0
  27. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/dependency_links.txt +0 -0
  28. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/requires.txt +0 -0
  29. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/src/debugbundle_python.egg-info/top_level.txt +0 -0
  30. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_contracts.py +0 -0
  31. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_framework_integrations.py +0 -0
  32. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_hooks.py +0 -0
  33. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_http_integration.py +0 -0
  34. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_relay.py +0 -0
  35. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_repository_metadata.py +0 -0
  36. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_schema_validation.py +0 -0
  37. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_sdk.py +0 -0
  38. {debugbundle_python-0.1.2 → debugbundle_python-0.1.3}/tests/test_trigger_token.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debugbundle-python
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: DebugBundle SDK for Python
5
5
  Author: DebugBundle
6
6
  License-Expression: AGPL-3.0-only
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "debugbundle-python"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "DebugBundle SDK for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -53,6 +53,11 @@ LEVEL_RANKS = {
53
53
  "error": 40,
54
54
  "critical": 50,
55
55
  }
56
+ BALANCED_IMMEDIATE_REQUEST_STATUSES = {408, 423, 424, 425, 429}
57
+ INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES = BALANCED_IMMEDIATE_REQUEST_STATUSES | {409}
58
+ BALANCED_STANDARD_ANOMALY_STATUSES = {401, 403, 404, 409, 422}
59
+ BALANCED_HIGH_VOLUME_ANOMALY_STATUSES = {400, 410}
60
+ INVESTIGATIVE_ANOMALY_STATUSES = BALANCED_STANDARD_ANOMALY_STATUSES | BALANCED_HIGH_VOLUME_ANOMALY_STATUSES
56
61
 
57
62
 
58
63
  @dataclass
@@ -604,7 +609,7 @@ class DebugBundleSdk:
604
609
  candidate = response.get("status_code") or response.get("response_status")
605
610
  if isinstance(candidate, int):
606
611
  status_code = candidate
607
- if status_code is not None and status_code >= 500:
612
+ if _is_immediate_request_incident_status(status_code, self._capture_policy.preset):
608
613
  return True
609
614
  if policy == "off":
610
615
  return False
@@ -615,7 +620,7 @@ class DebugBundleSdk:
615
620
  if status_code is None:
616
621
  return policy == "filtered"
617
622
  if policy == "failures_only":
618
- return False
623
+ return _is_request_anomaly_candidate_status(status_code, self._capture_policy.preset)
619
624
  if policy == "filtered":
620
625
  return False
621
626
  return True
@@ -846,6 +851,28 @@ def _iso_now(time_provider: Callable[[], float]) -> str:
846
851
  return datetime.fromtimestamp(time_provider(), tz=timezone.utc).isoformat().replace("+00:00", "Z")
847
852
 
848
853
 
854
+ def _is_immediate_request_incident_status(status_code: int | None, preset: str) -> bool:
855
+ if status_code is None:
856
+ return False
857
+ if status_code >= 500:
858
+ return True
859
+ if preset == "investigative":
860
+ return status_code in INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES
861
+ if preset == "balanced":
862
+ return status_code in BALANCED_IMMEDIATE_REQUEST_STATUSES
863
+ return False
864
+
865
+
866
+ def _is_request_anomaly_candidate_status(status_code: int | None, preset: str) -> bool:
867
+ if status_code is None or status_code < 400 or status_code >= 500:
868
+ return False
869
+ if preset == "investigative":
870
+ return status_code in INVESTIGATIVE_ANOMALY_STATUSES
871
+ if preset == "balanced":
872
+ return status_code in BALANCED_STANDARD_ANOMALY_STATUSES or status_code in BALANCED_HIGH_VOLUME_ANOMALY_STATUSES
873
+ return False
874
+
875
+
849
876
  def _time_now() -> float:
850
877
  return datetime.now(tz=timezone.utc).timestamp()
851
878
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debugbundle-python
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: DebugBundle SDK for Python
5
5
  Author: DebugBundle
6
6
  License-Expression: AGPL-3.0-only
@@ -226,4 +226,88 @@ def test_capture_policy_filters_logs_and_request_events_from_remote_config() ->
226
226
  events = _events(transport)
227
227
  assert [event["event_type"] for event in events] == ["log_event", "request_event"]
228
228
  assert events[0]["payload"]["message"] == "error kept"
229
- assert events[1]["payload"]["response_status"] == 503
229
+ assert events[1]["payload"]["response_status"] == 503
230
+
231
+
232
+ def test_balanced_capture_policy_keeps_request_failure_anomaly_candidates() -> None:
233
+ clock = ManualClock()
234
+ fetch = FakeFetch(
235
+ responses=[
236
+ FakeConfigResponse(
237
+ 200,
238
+ {
239
+ "probes_enabled": True,
240
+ "remote_probes_enabled": True,
241
+ "active_probes": [],
242
+ "poll_interval_ms": 15000,
243
+ "capture_policy": {
244
+ "preset": "balanced",
245
+ "capture_logs": "warning",
246
+ "capture_request_events": "failures_only",
247
+ "capture_breadcrumbs": "exception_only",
248
+ "capture_probe_events": "buffer_only",
249
+ },
250
+ },
251
+ )
252
+ ]
253
+ )
254
+ transport = FakeTransport()
255
+ sdk = DebugBundleSdk(transport=transport, time_provider=clock.time)
256
+ sdk.init(
257
+ project_token="dbundle_proj_test",
258
+ service="checkout-api",
259
+ environment="production",
260
+ fetch_impl=fetch,
261
+ )
262
+
263
+ sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 429})
264
+ sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 404})
265
+ sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 409})
266
+ sdk.flush()
267
+
268
+ events = _events(transport)
269
+ request_statuses = [
270
+ event["payload"]["response_status"]
271
+ for event in events
272
+ if event["event_type"] == "request_event"
273
+ ]
274
+ assert request_statuses == [429, 404, 409]
275
+
276
+
277
+ def test_investigative_capture_policy_promotes_409_even_when_request_capture_is_off() -> None:
278
+ clock = ManualClock()
279
+ fetch = FakeFetch(
280
+ responses=[
281
+ FakeConfigResponse(
282
+ 200,
283
+ {
284
+ "probes_enabled": True,
285
+ "remote_probes_enabled": True,
286
+ "active_probes": [],
287
+ "poll_interval_ms": 15000,
288
+ "capture_policy": {
289
+ "preset": "investigative",
290
+ "capture_logs": "info",
291
+ "capture_request_events": "off",
292
+ "capture_breadcrumbs": "standalone",
293
+ "capture_probe_events": "standalone_when_activated",
294
+ },
295
+ },
296
+ )
297
+ ]
298
+ )
299
+ transport = FakeTransport()
300
+ sdk = DebugBundleSdk(transport=transport, time_provider=clock.time)
301
+ sdk.init(
302
+ project_token="dbundle_proj_test",
303
+ service="checkout-api",
304
+ environment="production",
305
+ fetch_impl=fetch,
306
+ )
307
+
308
+ sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 409})
309
+ sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 404})
310
+ sdk.flush()
311
+
312
+ events = _events(transport)
313
+ assert [event["payload"]["response_status"] for event in events if event["event_type"] == "request_event"] == [409]