debugbundle-python 0.1.0__tar.gz → 0.1.1__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.0/src/debugbundle_python.egg-info → debugbundle_python-0.1.1}/PKG-INFO +1 -1
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/pyproject.toml +1 -1
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/config.py +5 -5
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/core.py +10 -4
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/relay.py +1 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1/src/debugbundle_python.egg-info}/PKG-INFO +1 -1
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_framework_integrations.py +6 -6
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_relay.py +25 -1
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_remote_config.py +8 -5
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_repository_metadata.py +9 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/LICENSE +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/README.md +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/setup.cfg +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/__init__.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/__init__.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/common.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/django.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/fastapi.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/flask.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/relay_django.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/relay_fastapi.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/relay_flask.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/logger_integrations.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/py.typed +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/redaction.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/suppression.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/transport.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/trigger_token.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle_python.egg-info/SOURCES.txt +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle_python.egg-info/dependency_links.txt +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle_python.egg-info/requires.txt +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle_python.egg-info/top_level.txt +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_contracts.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_hooks.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_http_integration.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_schema_validation.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_sdk.py +0 -0
- {debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/tests/test_trigger_token.py +0 -0
|
@@ -37,15 +37,15 @@ class RemoteConfigSnapshot:
|
|
|
37
37
|
BALANCED_CAPTURE_POLICY = CapturePolicy(
|
|
38
38
|
preset="balanced",
|
|
39
39
|
capture_logs="warning",
|
|
40
|
-
capture_request_events="
|
|
41
|
-
capture_breadcrumbs="
|
|
42
|
-
capture_probe_events="
|
|
40
|
+
capture_request_events="failures_only",
|
|
41
|
+
capture_breadcrumbs="exception_only",
|
|
42
|
+
capture_probe_events="buffer_only",
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
MINIMAL_CAPTURE_POLICY = CapturePolicy(
|
|
46
46
|
preset="minimal",
|
|
47
|
-
capture_logs="
|
|
48
|
-
capture_request_events="
|
|
47
|
+
capture_logs="error",
|
|
48
|
+
capture_request_events="failures_only",
|
|
49
49
|
capture_breadcrumbs="local_only",
|
|
50
50
|
capture_probe_events="buffer_only",
|
|
51
51
|
)
|
|
@@ -590,19 +590,25 @@ class DebugBundleSdk:
|
|
|
590
590
|
|
|
591
591
|
def _should_capture_request_event(self, response: Mapping[str, object] | None) -> bool:
|
|
592
592
|
policy = self._capture_policy.capture_request_events
|
|
593
|
+
status_code = None
|
|
594
|
+
if response is not None:
|
|
595
|
+
candidate = response.get("status_code") or response.get("response_status")
|
|
596
|
+
if isinstance(candidate, int):
|
|
597
|
+
status_code = candidate
|
|
598
|
+
if status_code is not None and status_code >= 500:
|
|
599
|
+
return True
|
|
593
600
|
if policy == "off":
|
|
594
601
|
return False
|
|
595
602
|
if policy == "all":
|
|
596
603
|
return True
|
|
597
604
|
if response is None:
|
|
598
605
|
return policy == "filtered"
|
|
599
|
-
|
|
600
|
-
if not isinstance(status_code, int):
|
|
606
|
+
if status_code is None:
|
|
601
607
|
return policy == "filtered"
|
|
602
608
|
if policy == "failures_only":
|
|
603
|
-
return
|
|
609
|
+
return False
|
|
604
610
|
if policy == "filtered":
|
|
605
|
-
return
|
|
611
|
+
return False
|
|
606
612
|
return True
|
|
607
613
|
|
|
608
614
|
def _emit_probe_events(self, label: str, data: dict[str, object], directives: list[RemoteProbeDirective]) -> None:
|
|
@@ -178,14 +178,14 @@ def test_django_middleware_captures_requests_logs_and_errors() -> None:
|
|
|
178
178
|
|
|
179
179
|
factory = RequestFactory()
|
|
180
180
|
|
|
181
|
-
def
|
|
182
|
-
logging.getLogger("django-test").warning("django
|
|
183
|
-
return HttpResponse("
|
|
181
|
+
def server_error_response(request: object) -> HttpResponse:
|
|
182
|
+
logging.getLogger("django-test").warning("django server warning")
|
|
183
|
+
return HttpResponse("failed", status=503)
|
|
184
184
|
|
|
185
|
-
middleware = DebugBundleDjangoMiddleware(
|
|
186
|
-
request = factory.get("/
|
|
185
|
+
middleware = DebugBundleDjangoMiddleware(server_error_response, sdk=sdk)
|
|
186
|
+
request = factory.get("/failed", HTTP_X_DEBUGBUNDLE_TRACE_ID="trace-django")
|
|
187
187
|
response = middleware(request)
|
|
188
|
-
assert response.status_code ==
|
|
188
|
+
assert response.status_code == 503
|
|
189
189
|
|
|
190
190
|
raising_middleware = DebugBundleDjangoMiddleware(
|
|
191
191
|
lambda request: (_ for _ in ()).throw(RuntimeError("django failure")),
|
|
@@ -166,7 +166,7 @@ def test_preserves_correlation_trace_id() -> None:
|
|
|
166
166
|
|
|
167
167
|
def test_accepts_all_supported_event_types() -> None:
|
|
168
168
|
handler = BrowserRelayHandler(allowed_origins=["https://example.com"])
|
|
169
|
-
for event_type in ("frontend_exception", "error_suppressed", "frontend_breadcrumb", "probe_event"):
|
|
169
|
+
for event_type in ("frontend_exception", "error_suppressed", "frontend_breadcrumb", "request_event", "probe_event"):
|
|
170
170
|
evt = _valid_event(event_type)
|
|
171
171
|
response = handler.handle(
|
|
172
172
|
_make_request(body={"batch": [evt]}, ip_address=f"10.0.0.{hash(event_type) % 254 + 1}")
|
|
@@ -174,6 +174,30 @@ def test_accepts_all_supported_event_types() -> None:
|
|
|
174
174
|
assert response.status == 202, f"Failed for event type: {event_type}"
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
def test_accepts_browser_request_event_payloads() -> None:
|
|
178
|
+
accepted: list[BrowserRelayAcceptedBatch] = []
|
|
179
|
+
handler = BrowserRelayHandler(
|
|
180
|
+
allowed_origins=["https://example.com"],
|
|
181
|
+
on_accept=lambda batch: accepted.append(batch),
|
|
182
|
+
)
|
|
183
|
+
event = _valid_event("request_event")
|
|
184
|
+
event["payload"] = {
|
|
185
|
+
"method": "POST",
|
|
186
|
+
"path": "/v1/billing/checkout",
|
|
187
|
+
"query": {"plan": "team"},
|
|
188
|
+
"headers": {},
|
|
189
|
+
"response_status": 503,
|
|
190
|
+
"duration_ms": 84,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
response = handler.handle(_make_request(body={"batch": [event]}))
|
|
194
|
+
|
|
195
|
+
assert response.status == 202
|
|
196
|
+
assert len(accepted) == 1
|
|
197
|
+
assert accepted[0].events[0]["event_type"] == "request_event"
|
|
198
|
+
assert accepted[0].events[0]["payload"]["response_status"] == 503
|
|
199
|
+
|
|
200
|
+
|
|
177
201
|
def test_rejects_event_with_missing_required_fields() -> None:
|
|
178
202
|
handler = BrowserRelayHandler(allowed_origins=["https://example.com"])
|
|
179
203
|
bad_event = {"event_type": "frontend_exception"}
|
|
@@ -111,8 +111,8 @@ def test_remote_config_uses_etag_and_activates_heavy_probes_only_while_directive
|
|
|
111
111
|
"capture_policy": {
|
|
112
112
|
"preset": "balanced",
|
|
113
113
|
"capture_logs": "warning",
|
|
114
|
-
"capture_request_events": "
|
|
115
|
-
"capture_breadcrumbs": "
|
|
114
|
+
"capture_request_events": "failures_only",
|
|
115
|
+
"capture_breadcrumbs": "exception_only",
|
|
116
116
|
"capture_probe_events": "standalone_when_activated",
|
|
117
117
|
},
|
|
118
118
|
},
|
|
@@ -173,14 +173,17 @@ def test_failed_init_config_fetch_falls_back_to_minimal_policy() -> None:
|
|
|
173
173
|
probes_poll_interval=25000,
|
|
174
174
|
)
|
|
175
175
|
|
|
176
|
-
sdk.capture_message("warning
|
|
176
|
+
sdk.capture_message("warning blocked", level="warning")
|
|
177
|
+
sdk.capture_message("error still allowed", level="error")
|
|
177
178
|
sdk.capture_message("info blocked", level="info")
|
|
178
179
|
sdk.capture_request({"method": "GET", "path": "/ok", "headers": {}}, {"status_code": 200})
|
|
180
|
+
sdk.capture_request({"method": "GET", "path": "/boom", "headers": {}}, {"status_code": 503})
|
|
179
181
|
sdk.flush()
|
|
180
182
|
|
|
181
183
|
events = _events(transport)
|
|
182
|
-
assert [event["event_type"] for event in events] == ["log_event"]
|
|
183
|
-
assert events[0]["payload"]["message"] == "
|
|
184
|
+
assert [event["event_type"] for event in events] == ["log_event", "request_event"]
|
|
185
|
+
assert events[0]["payload"]["message"] == "error still allowed"
|
|
186
|
+
assert events[1]["payload"]["response_status"] == 503
|
|
184
187
|
|
|
185
188
|
|
|
186
189
|
def test_capture_policy_filters_logs_and_request_events_from_remote_config() -> None:
|
|
@@ -25,6 +25,15 @@ def test_pyproject_points_to_standalone_repository_urls() -> None:
|
|
|
25
25
|
assert 'Issues = "https://github.com/debugbundle/debugbundle-python/issues"' in pyproject
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def test_standalone_changelog_and_security_policy_are_launch_ready() -> None:
|
|
29
|
+
changelog = (REPO_ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
|
|
30
|
+
security = (REPO_ROOT / "SECURITY.md").read_text(encoding="utf-8")
|
|
31
|
+
|
|
32
|
+
assert "## [Unreleased]" in changelog
|
|
33
|
+
assert "## [0.1.0] - 2026-05-07" in changelog
|
|
34
|
+
assert "https://github.com/debugbundle/debugbundle-python/security/advisories/new" in security
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
def test_standalone_ci_workflow_covers_python_sdk_checks() -> None:
|
|
29
38
|
workflow = (REPO_ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
|
|
30
39
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/__init__.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/common.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/django.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/fastapi.py
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/relay_django.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/relay_fastapi.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/integrations/relay_flask.py
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle/logger_integrations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/src/debugbundle_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{debugbundle_python-0.1.0 → debugbundle_python-0.1.1}/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
|