debugbundle-python 0.1.3__tar.gz → 0.1.5__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.
- {debugbundle_python-0.1.3/src/debugbundle_python.egg-info → debugbundle_python-0.1.5}/PKG-INFO +1 -1
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/pyproject.toml +1 -1
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/config.py +24 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/core.py +12 -2
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5/src/debugbundle_python.egg-info}/PKG-INFO +1 -1
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_remote_config.py +47 -1
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/LICENSE +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/README.md +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/setup.cfg +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/__init__.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/__init__.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/common.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/django.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/fastapi.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/flask.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/relay_django.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/relay_fastapi.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/relay_flask.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/logger_integrations.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/py.typed +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/redaction.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/relay.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/suppression.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/transport.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/trigger_token.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/SOURCES.txt +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/dependency_links.txt +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/requires.txt +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/top_level.txt +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_contracts.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_framework_integrations.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_hooks.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_http_integration.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_relay.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_repository_metadata.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_schema_validation.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_sdk.py +0 -0
- {debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/tests/test_trigger_token.py +0 -0
|
@@ -13,6 +13,7 @@ class CapturePolicy:
|
|
|
13
13
|
capture_request_events: str
|
|
14
14
|
capture_breadcrumbs: str
|
|
15
15
|
capture_probe_events: str
|
|
16
|
+
immediate_client_error_statuses: tuple[int, ...]
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
@dataclass(frozen=True)
|
|
@@ -40,6 +41,7 @@ BALANCED_CAPTURE_POLICY = CapturePolicy(
|
|
|
40
41
|
capture_request_events="failures_only",
|
|
41
42
|
capture_breadcrumbs="exception_only",
|
|
42
43
|
capture_probe_events="buffer_only",
|
|
44
|
+
immediate_client_error_statuses=(),
|
|
43
45
|
)
|
|
44
46
|
|
|
45
47
|
MINIMAL_CAPTURE_POLICY = CapturePolicy(
|
|
@@ -48,6 +50,7 @@ MINIMAL_CAPTURE_POLICY = CapturePolicy(
|
|
|
48
50
|
capture_request_events="failures_only",
|
|
49
51
|
capture_breadcrumbs="local_only",
|
|
50
52
|
capture_probe_events="buffer_only",
|
|
53
|
+
immediate_client_error_statuses=(),
|
|
51
54
|
)
|
|
52
55
|
|
|
53
56
|
|
|
@@ -117,6 +120,9 @@ def _parse_capture_policy(payload: object) -> CapturePolicy | None:
|
|
|
117
120
|
capture_request_events = _as_non_empty_string(payload.get("capture_request_events"))
|
|
118
121
|
capture_breadcrumbs = _as_non_empty_string(payload.get("capture_breadcrumbs"))
|
|
119
122
|
capture_probe_events = _as_non_empty_string(payload.get("capture_probe_events"))
|
|
123
|
+
immediate_client_error_statuses = _parse_immediate_client_error_statuses(
|
|
124
|
+
payload.get("immediate_client_error_statuses")
|
|
125
|
+
)
|
|
120
126
|
|
|
121
127
|
if capture_logs not in {"off", "error", "warning", "info"}:
|
|
122
128
|
return None
|
|
@@ -126,6 +132,8 @@ def _parse_capture_policy(payload: object) -> CapturePolicy | None:
|
|
|
126
132
|
return None
|
|
127
133
|
if capture_probe_events not in {"buffer_only", "standalone_when_activated"}:
|
|
128
134
|
return None
|
|
135
|
+
if immediate_client_error_statuses is None:
|
|
136
|
+
return None
|
|
129
137
|
|
|
130
138
|
return CapturePolicy(
|
|
131
139
|
preset=preset,
|
|
@@ -133,9 +141,25 @@ def _parse_capture_policy(payload: object) -> CapturePolicy | None:
|
|
|
133
141
|
capture_request_events=capture_request_events,
|
|
134
142
|
capture_breadcrumbs=capture_breadcrumbs,
|
|
135
143
|
capture_probe_events=capture_probe_events,
|
|
144
|
+
immediate_client_error_statuses=immediate_client_error_statuses,
|
|
136
145
|
)
|
|
137
146
|
|
|
138
147
|
|
|
148
|
+
def _parse_immediate_client_error_statuses(value: object) -> tuple[int, ...] | None:
|
|
149
|
+
if value is None:
|
|
150
|
+
return ()
|
|
151
|
+
if not isinstance(value, list) or len(value) > 12:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
statuses: list[int] = []
|
|
155
|
+
for item in value:
|
|
156
|
+
if not isinstance(item, int) or isinstance(item, bool) or item < 400 or item > 499:
|
|
157
|
+
return None
|
|
158
|
+
statuses.append(item)
|
|
159
|
+
|
|
160
|
+
return tuple(sorted(set(statuses)))
|
|
161
|
+
|
|
162
|
+
|
|
139
163
|
def _parse_directive(payload: object) -> RemoteProbeDirective | None:
|
|
140
164
|
if not isinstance(payload, dict):
|
|
141
165
|
return None
|
|
@@ -609,7 +609,11 @@ class DebugBundleSdk:
|
|
|
609
609
|
candidate = response.get("status_code") or response.get("response_status")
|
|
610
610
|
if isinstance(candidate, int):
|
|
611
611
|
status_code = candidate
|
|
612
|
-
if _is_immediate_request_incident_status(
|
|
612
|
+
if _is_immediate_request_incident_status(
|
|
613
|
+
status_code,
|
|
614
|
+
self._capture_policy.preset,
|
|
615
|
+
self._capture_policy.immediate_client_error_statuses,
|
|
616
|
+
):
|
|
613
617
|
return True
|
|
614
618
|
if policy == "off":
|
|
615
619
|
return False
|
|
@@ -851,11 +855,17 @@ def _iso_now(time_provider: Callable[[], float]) -> str:
|
|
|
851
855
|
return datetime.fromtimestamp(time_provider(), tz=timezone.utc).isoformat().replace("+00:00", "Z")
|
|
852
856
|
|
|
853
857
|
|
|
854
|
-
def _is_immediate_request_incident_status(
|
|
858
|
+
def _is_immediate_request_incident_status(
|
|
859
|
+
status_code: int | None,
|
|
860
|
+
preset: str,
|
|
861
|
+
immediate_client_error_statuses: tuple[int, ...],
|
|
862
|
+
) -> bool:
|
|
855
863
|
if status_code is None:
|
|
856
864
|
return False
|
|
857
865
|
if status_code >= 500:
|
|
858
866
|
return True
|
|
867
|
+
if status_code in immediate_client_error_statuses:
|
|
868
|
+
return True
|
|
859
869
|
if preset == "investigative":
|
|
860
870
|
return status_code in INVESTIGATIVE_IMMEDIATE_REQUEST_STATUSES
|
|
861
871
|
if preset == "balanced":
|
|
@@ -310,4 +310,50 @@ def test_investigative_capture_policy_promotes_409_even_when_request_capture_is_
|
|
|
310
310
|
sdk.flush()
|
|
311
311
|
|
|
312
312
|
events = _events(transport)
|
|
313
|
-
assert [event["payload"]["response_status"] for event in events if event["event_type"] == "request_event"] == [409]
|
|
313
|
+
assert [event["payload"]["response_status"] for event in events if event["event_type"] == "request_event"] == [409]
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_capture_policy_promotes_configured_client_error_statuses_when_request_capture_is_off() -> None:
|
|
317
|
+
clock = ManualClock()
|
|
318
|
+
fetch = FakeFetch(
|
|
319
|
+
responses=[
|
|
320
|
+
FakeConfigResponse(
|
|
321
|
+
200,
|
|
322
|
+
{
|
|
323
|
+
"probes_enabled": True,
|
|
324
|
+
"remote_probes_enabled": True,
|
|
325
|
+
"active_probes": [],
|
|
326
|
+
"poll_interval_ms": 15000,
|
|
327
|
+
"capture_policy": {
|
|
328
|
+
"preset": "minimal",
|
|
329
|
+
"capture_logs": "error",
|
|
330
|
+
"capture_request_events": "off",
|
|
331
|
+
"capture_breadcrumbs": "local_only",
|
|
332
|
+
"capture_probe_events": "buffer_only",
|
|
333
|
+
"immediate_client_error_statuses": [422, 403, 403],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
)
|
|
337
|
+
]
|
|
338
|
+
)
|
|
339
|
+
transport = FakeTransport()
|
|
340
|
+
sdk = DebugBundleSdk(transport=transport, time_provider=clock.time)
|
|
341
|
+
sdk.init(
|
|
342
|
+
project_token="dbundle_proj_test",
|
|
343
|
+
service="checkout-api",
|
|
344
|
+
environment="production",
|
|
345
|
+
fetch_impl=fetch,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 403})
|
|
349
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 404})
|
|
350
|
+
sdk.capture_request({"method": "POST", "path": "/checkout", "headers": {}}, {"status_code": 422})
|
|
351
|
+
sdk.flush()
|
|
352
|
+
|
|
353
|
+
events = _events(transport)
|
|
354
|
+
request_statuses = [
|
|
355
|
+
event["payload"]["response_status"]
|
|
356
|
+
for event in events
|
|
357
|
+
if event["event_type"] == "request_event"
|
|
358
|
+
]
|
|
359
|
+
assert request_statuses == [403, 422]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/__init__.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/common.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/django.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/fastapi.py
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/relay_django.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/relay_fastapi.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/integrations/relay_flask.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle/logger_integrations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{debugbundle_python-0.1.3 → debugbundle_python-0.1.5}/src/debugbundle_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|