debugbundle-python 2.0.0__py3-none-any.whl → 2.1.0__py3-none-any.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.
Files changed (37) hide show
  1. debugbundle/__init__.py +16 -0
  2. debugbundle/acknowledgement.py +3 -1
  3. debugbundle/before_send.py +35 -191
  4. debugbundle/canonical_event.py +202 -0
  5. debugbundle/config.py +6 -0
  6. debugbundle/core.py +76 -316
  7. debugbundle/delivery_hooks.py +14 -0
  8. debugbundle/diagnostic_budget.py +191 -0
  9. debugbundle/event-envelope.schema.json +4904 -0
  10. debugbundle/event_support.py +1 -1
  11. debugbundle/evidence_delivery.py +115 -0
  12. debugbundle/evidence_http.py +45 -0
  13. debugbundle/evidence_json.py +68 -0
  14. debugbundle/evidence_wire.py +409 -0
  15. debugbundle/exception_capture.py +92 -0
  16. debugbundle/exception_evidence.py +199 -0
  17. debugbundle/exception_snapshot.py +1 -1
  18. debugbundle/flush_delivery.py +203 -0
  19. debugbundle/ingestion_wire.py +75 -0
  20. debugbundle/integrations/relay_django.py +4 -1
  21. debugbundle/integrations/relay_fastapi.py +10 -2
  22. debugbundle/integrations/relay_flask.py +4 -1
  23. debugbundle/operation_context.py +121 -0
  24. debugbundle/process_support.py +7 -0
  25. debugbundle/queue_admission.py +42 -2
  26. debugbundle/recovery_capture.py +43 -0
  27. debugbundle/relay.py +93 -36
  28. debugbundle/relay_delivery.py +108 -17
  29. debugbundle/remote_config_client.py +81 -0
  30. debugbundle/scope_ownership.py +99 -0
  31. debugbundle/transport.py +49 -20
  32. {debugbundle_python-2.0.0.dist-info → debugbundle_python-2.1.0.dist-info}/METADATA +122 -1
  33. debugbundle_python-2.1.0.dist-info/RECORD +51 -0
  34. {debugbundle_python-2.0.0.dist-info → debugbundle_python-2.1.0.dist-info}/WHEEL +1 -1
  35. debugbundle_python-2.0.0.dist-info/RECORD +0 -36
  36. {debugbundle_python-2.0.0.dist-info → debugbundle_python-2.1.0.dist-info}/licenses/LICENSE +0 -0
  37. {debugbundle_python-2.0.0.dist-info → debugbundle_python-2.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,81 @@
1
+ """Owned SDK boundary extracted without changing capture/delivery semantics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from .config import MINIMAL_CAPTURE_POLICY, parse_remote_config
8
+ from .event_support import sdk_config_endpoint, serialize_error
9
+ from .evidence_http import evidence_url
10
+
11
+ if TYPE_CHECKING:
12
+ from .core import DebugBundleSdk
13
+
14
+
15
+ def refresh_remote_config(sdk: DebugBundleSdk, initial: bool = False) -> None:
16
+ with sdk._lock:
17
+ if not sdk._enabled or sdk._fetch_impl is None:
18
+ return
19
+ request_headers: dict[str, str] = {"authorization": f"Bearer {sdk._project_token}"}
20
+ if sdk._remote_config_etag is not None:
21
+ request_headers["if-none-match"] = sdk._remote_config_etag
22
+ fetch = sdk._fetch_impl
23
+ endpoint = evidence_url(sdk_config_endpoint(sdk._endpoint))
24
+ poll_interval = sdk._configured_probes_poll_interval_ms
25
+ generation = sdk._generation
26
+
27
+ try:
28
+ response = fetch(endpoint, {"method": "GET", "headers": request_headers})
29
+ status_code = getattr(response, "status_code", None)
30
+ if status_code == 304:
31
+ snapshot = None
32
+ elif status_code == 200:
33
+ payload = response.json()
34
+ snapshot = parse_remote_config(
35
+ payload,
36
+ poll_interval,
37
+ int(sdk._time_provider() * 1000),
38
+ )
39
+ else:
40
+ raise RuntimeError(f"unexpected config status {status_code}")
41
+ headers = response.headers or {}
42
+ etag = headers.get("etag")
43
+ failure: BaseException | None = None
44
+ except BaseException as error:
45
+ status_code = None
46
+ snapshot = None
47
+ etag = None
48
+ failure = error
49
+
50
+ with sdk._lock:
51
+ if generation != sdk._generation or not sdk._enabled:
52
+ return
53
+ if failure is None and status_code == 304:
54
+ sdk._schedule_next_remote_config_refresh()
55
+ return
56
+ if failure is None:
57
+ sdk._evidence.observe(payload, True, snapshot is not None)
58
+ if snapshot is None:
59
+ sdk._emit_diagnostic(
60
+ "remote_probe_config_invalid",
61
+ "sdk-python received an invalid remote probe config payload",
62
+ )
63
+ if initial:
64
+ sdk._capture_policy = MINIMAL_CAPTURE_POLICY
65
+ sdk._schedule_next_remote_config_refresh(use_fallback=True)
66
+ return
67
+
68
+ sdk._remote_config_snapshot = snapshot
69
+ sdk._capture_policy = snapshot.capture_policy
70
+ if isinstance(etag, str) and len(etag) > 0:
71
+ sdk._remote_config_etag = etag
72
+ sdk._schedule_next_remote_config_refresh()
73
+ else:
74
+ sdk._emit_diagnostic(
75
+ "remote_probe_config_failed",
76
+ "sdk-python failed to refresh remote probe config",
77
+ metadata={"error": serialize_error(failure) if isinstance(failure, Exception) else "fetch_failed"},
78
+ )
79
+ if initial:
80
+ sdk._capture_policy = MINIMAL_CAPTURE_POLICY
81
+ sdk._schedule_next_remote_config_refresh(use_fallback=True)
@@ -0,0 +1,99 @@
1
+ """Revoke copied ContextVar values when their request/project owner ends."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextvars import Token
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, Any, cast
8
+
9
+ from .config import RemoteProbeDirective
10
+ from .operation_context import Operation
11
+
12
+ if TYPE_CHECKING:
13
+ from .core import DebugBundleSdk
14
+
15
+
16
+ @dataclass
17
+ class Lease:
18
+ generation: int
19
+ parent: Lease | None = None
20
+ active: bool = True
21
+ token_id: int = 0
22
+ operation: Token[Operation | None] | None = None
23
+
24
+
25
+ class ScopedContext(dict[str, object]):
26
+ lease: Lease
27
+
28
+
29
+ class ScopedDirectives(list[RemoteProbeDirective]):
30
+ lease: Lease
31
+
32
+
33
+ def alive(value: object, generation: int) -> bool:
34
+ if type(value) is not ScopedContext and type(value) is not ScopedDirectives:
35
+ return False
36
+ lease: Lease | None = value.lease
37
+ for _ in range(16):
38
+ if lease is None:
39
+ return True
40
+ if not lease.active or lease.generation != generation:
41
+ return False
42
+ lease = lease.parent
43
+ return False
44
+
45
+
46
+ def context_value(sdk: DebugBundleSdk) -> dict[str, object]:
47
+ value = sdk._scoped_context.get()
48
+ return value if alive(value, sdk._generation) and value is not None else {}
49
+
50
+
51
+ def bind_context(sdk: DebugBundleSdk, protected: dict[str, object]) -> Token[dict[str, object] | None]:
52
+ parent = sdk._scoped_context.get()
53
+ value = ScopedContext(protected)
54
+ value.lease = Lease(
55
+ sdk._generation, parent.lease if type(parent) is ScopedContext and alive(parent, sdk._generation) else None
56
+ )
57
+ token = sdk._scoped_context.set(value)
58
+ value.lease.token_id = id(token)
59
+ return token
60
+
61
+
62
+ def bind_directives(
63
+ sdk: DebugBundleSdk, directives: list[RemoteProbeDirective]
64
+ ) -> Token[list[RemoteProbeDirective] | None]:
65
+ parent = sdk._request_trigger_directives.get()
66
+ value = ScopedDirectives(directives)
67
+ value.lease = Lease(
68
+ sdk._generation,
69
+ parent.lease if type(parent) is ScopedDirectives and alive(parent, sdk._generation) else None,
70
+ operation=sdk._operations.begin({"kind": "request"}),
71
+ )
72
+ token = sdk._request_trigger_directives.set(value)
73
+ value.lease.token_id = id(token)
74
+ return token
75
+
76
+
77
+ def release(
78
+ sdk: DebugBundleSdk,
79
+ token: Token[dict[str, object] | None] | Token[list[RemoteProbeDirective] | None],
80
+ *,
81
+ directives: bool = False,
82
+ ) -> None:
83
+ variable = sdk._request_trigger_directives if directives else sdk._scoped_context
84
+ value = variable.get()
85
+ lease = value.lease if type(value) is ScopedContext or type(value) is ScopedDirectives else None
86
+ current_lease = lease
87
+ for _ in range(16):
88
+ if lease is None:
89
+ return
90
+ if lease.token_id == id(token):
91
+ if lease is current_lease:
92
+ # Preserve native Token context checks; old completion never clears
93
+ # the currently active newer owner.
94
+ cast(Any, variable).reset(token)
95
+ lease.active = False
96
+ if directives:
97
+ sdk._operations.end(lease.operation)
98
+ return
99
+ lease = lease.parent
debugbundle/transport.py CHANGED
@@ -1,11 +1,43 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import math
4
+ import time
3
5
  from collections.abc import Mapping
4
6
  from dataclasses import dataclass
7
+ from email.utils import parsedate_to_datetime
5
8
  from typing import Any, Protocol
6
9
 
7
10
  import httpx
8
11
 
12
+ from .evidence_http import bounded_response_json, evidence_url
13
+ from .ingestion_wire import MAX_BODY_BYTES, encode_body
14
+
15
+ MAX_RETRY_AFTER_MS = 300_000
16
+
17
+
18
+ def bounded_retry_after_ms(value: object, default: int = 1_000) -> int:
19
+ # Clamp before conversion so arbitrarily large integer hints cannot overflow a float deadline.
20
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
21
+ return default
22
+ if isinstance(value, float) and not math.isfinite(value):
23
+ return default
24
+ return int(min(MAX_RETRY_AFTER_MS, max(0, value)))
25
+
26
+
27
+ def _parse_retry_after(value: str | None) -> int | None:
28
+ if value is None:
29
+ return None
30
+ try:
31
+ seconds = float(value)
32
+ except ValueError:
33
+ try:
34
+ seconds = parsedate_to_datetime(value).timestamp() - time.time()
35
+ except (ValueError, TypeError, OverflowError):
36
+ return None
37
+ if not math.isfinite(seconds):
38
+ return None
39
+ return bounded_retry_after_ms(min(300, max(0, seconds)) * 1_000)
40
+
9
41
 
10
42
  @dataclass
11
43
  class TransportResponse:
@@ -15,8 +47,7 @@ class TransportResponse:
15
47
 
16
48
 
17
49
  class Transport(Protocol):
18
- def __call__(self, request: Mapping[str, object]) -> TransportResponse:
19
- ...
50
+ def __call__(self, request: Mapping[str, object]) -> TransportResponse: ...
20
51
 
21
52
 
22
53
  class HttpTransport:
@@ -25,28 +56,26 @@ class HttpTransport:
25
56
  self._client = httpx.Client(timeout=5.0)
26
57
 
27
58
  def __call__(self, request: Mapping[str, object]) -> TransportResponse:
59
+ return self.send_until(request, time.monotonic() + 5.0)
60
+
61
+ def send_until(self, request: Mapping[str, object], deadline: float) -> TransportResponse:
62
+ remaining = min(5.0, deadline - time.monotonic())
63
+ if remaining <= 0:
64
+ raise TimeoutError("ingestion deadline exhausted")
65
+ body_bytes = encode_body(request["events"])
66
+ if len(body_bytes) > MAX_BODY_BYTES:
67
+ raise ValueError("ingestion request exceeds byte limit")
28
68
  headers = {
29
69
  "authorization": f"Bearer {request['project_token']}",
30
70
  "content-type": "application/json",
71
+ "accept-encoding": "identity",
31
72
  }
32
- response = self._client.post(
33
- self._endpoint,
34
- json={"events": request["events"]},
35
- headers=headers,
36
- )
37
- retry_after_header = response.headers.get("retry-after")
38
- retry_after_ms = None
39
- if retry_after_header is not None:
40
- try:
41
- retry_after_ms = int(float(retry_after_header) * 1000)
42
- except ValueError:
43
- retry_after_ms = None
44
-
45
- try:
46
- body: object | None = response.json()
47
- except (ValueError, TypeError):
48
- body = None
49
- return TransportResponse(status_code=response.status_code, retry_after_ms=retry_after_ms, body=body)
73
+ with self._client.stream(
74
+ "POST", evidence_url(self._endpoint), content=body_bytes, headers=headers, timeout=remaining
75
+ ) as response:
76
+ retry_after_ms = _parse_retry_after(response.headers.get("retry-after"))
77
+ body = bounded_response_json(response, deadline)
78
+ return TransportResponse(status_code=response.status_code, retry_after_ms=retry_after_ms, body=body)
50
79
 
51
80
  def close(self) -> None:
52
81
  self._client.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: debugbundle-python
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: DebugBundle SDK for Python
5
5
  Author: DebugBundle
6
6
  License-Expression: Apache-2.0
@@ -43,6 +43,14 @@ Dynamic: license-file
43
43
 
44
44
  Python SDK for DebugBundle.
45
45
 
46
+ After `make install-docker`, `make evidence-performance` compares identical
47
+ exception/recovery calls with evidence disabled and enabled, including a full
48
+ warning queue, with networking disabled. It reports capture timing separately from
49
+ Python `tracemalloc` peak/live allocation measurements, verifies native causes and
50
+ original-error priority, and checks existing retention ceilings. Setup and cleanup
51
+ are excluded; traced allocations are Python-managed memory, not cumulative churn
52
+ or total process RSS.
53
+
46
54
  ![PyPI](https://img.shields.io/pypi/v/debugbundle-python?label=pypi)
47
55
  ![CI](https://img.shields.io/github/actions/workflow/status/debugbundle/debugbundle-python/ci.yml?branch=main&label=ci)
48
56
  ![License](https://img.shields.io/badge/license-Apache--2.0-blue)
@@ -51,6 +59,94 @@ Use this package to capture Python backend exceptions, request metadata, structu
51
59
 
52
60
  Requires Python 3.10 or newer.
53
61
 
62
+ ## Evidence candidate (unreleased)
63
+
64
+ The source candidate adds `capture_exception_evidence=True` and
65
+ `capture_recovery_observations=False` installation options. Collection requires
66
+ verified delivery/reader support, discovered on existing config/ACK traffic with
67
+ `debugbundle_evidence=1`. No capture call performs discovery or adds polling.
68
+ `evidence_capabilities` is an optional descriptor pin asserting the **entire**
69
+ path, including local readers, relays and wrappers; later discovery can revoke it.
70
+ Without proof, exception events retain their legacy shape. These additions are
71
+ not available in the installed 2.0.1 release.
72
+
73
+ Native cause, implicit context and Python 3.11+ exception-group evidence excludes
74
+ locals, source excerpts, arbitrary renderers and application properties. Python
75
+ 3.10 has no native exception groups. Graphs retain at most eight nodes, depth
76
+ four, five children and 32 frames per node; all optional exception additions fit
77
+ 32 KiB. Uncaught native capture bypasses `before_send` and uses two nodes, eight
78
+ frames and a 4-KiB total including execution/reference metadata. The original
79
+ sanitised stack remains separate.
80
+
81
+ `with sdk.operation_context({"kind": "job", "name": "invoice.calculate",
82
+ "limit_ms": 5000}):` supplies a fresh operation scope and monotonic elapsed time
83
+ to synchronous or asynchronous work inside the block. Kinds are `request`,
84
+ `command`, `job` and `unknown`. The limit is a known configured limit, not time
85
+ remaining. Framework requests use their existing begin/end lifecycle. Completed
86
+ owners revoke copied async context; reinitialisation/fork discard prior ownership.
87
+ Application results and exceptions pass through the context manager unchanged.
88
+
89
+ The existing background sender remains best-effort at immediate process exit.
90
+ The uncaught hook collects bounded native evidence without waiting for transport;
91
+ applications needing a delivery attempt use explicit `flush()` in a controlled
92
+ shutdown window. This candidate does not add default crash-file persistence or
93
+ synchronous capture transport.
94
+
95
+ Pass a fresh UUID v4 `failure_ref` in exception/log/request context. A valid
96
+ `evidence_scope` can specify a fresh page or operation UUID; an active operation
97
+ provides the default. `capture_recovery({"failure_ref": failure_ref,
98
+ "attempt_id": attempt_id, "state": "attempt_completed"})` records an explicit
99
+ application observation. Optional fields are `evidence_scope`, technical
100
+ `operation`, `status_code`, `duration_ms` and primitive `correlation` metadata.
101
+ Page scopes require a nonblank session ID, including the valid string `"0"`.
102
+ Only explicit `recovery_observed` claims semantic recovery; HTTP 200 and
103
+ `attempt_completed` leave it unknown. The other states are `attempt_started`,
104
+ `attempt_failed` and `failure_persisted`.
105
+
106
+ Standalone recovery requires its local opt-in, `recovery_v1` support and server
107
+ `capture_diagnostic_context`; minimal policy disables it. Privacy, sampling,
108
+ resulting hook-event policy and queue limits still apply. Fifty failure windows,
109
+ five attempts and twenty observations per failure share 64 KiB of tracker and
110
+ pending-payload ownership, retained until ACK/drop even offline. Canonical hooks
111
+ may change any allowed identity/type field; ownership follows the replacement.
112
+ The complete closed envelope is checked against the vendored accepting Draft 7
113
+ schema, followed by evidence semantics and mandatory privacy. Only native JSON
114
+ containers/scalars are copied; custom serializers and container subclasses are
115
+ never invoked by hook validation. Invalid or throwing hooks keep the protected
116
+ original. Exhausted validation or an unfit valid replacement is withheld, so an
117
+ application redaction cannot be undone by a budget fallback.
118
+
119
+ Native HTTP requests select an ordered prefix by the actual compact UTF-8 JSON
120
+ serializer and remain within the receiving 256-KiB limit. Unsent finalized tails
121
+ stay charged and continue through the existing sender without repeating their
122
+ hooks. Optional SDK evidence is reduced before withholding an unfit primary;
123
+ explicit custom/local transports retain their established budgets. Browser
124
+ relays measure after server identity/token expansion, split downstream requests
125
+ and keep incomplete spools. A legacy custom success remains accepted but cannot
126
+ mark a durable spool delivered; that requires complete canonical acknowledgements.
127
+ Relay chunks share a five-second monotonic forwarding deadline, and spool markers
128
+ are created exclusively with private permissions without following symlinks.
129
+
130
+ Native HTTP ACK reads stop at 256 KiB and close the response. Requests ask for
131
+ identity encoding; unexpected compressed ACKs are withheld. The five-second
132
+ deadline is checked between streamed reads; HTTPX's synchronous per-read/connect/
133
+ write timeouts remain advisory and an in-progress native read can overrun that
134
+ deadline. Explicit custom transports/config fetchers retain their own timeout,
135
+ allocation and cancellation responsibilities. No additional timer thread or
136
+ inline capture transport is introduced.
137
+
138
+ Browser relays and Django/Flask/FastAPI relay factories accept the same optional
139
+ `evidence_capabilities` pin. They preserve closed DTOs, protected application
140
+ context and server-owned credentials, and advertise only verified downstream
141
+ support. Opted-in replies keep string `errors` and add indexed `evidence_errors`;
142
+ partial batches return 202, while legacy partial responses retain 400. A durable
143
+ spool accepts locally but is marked delivered only after a valid complete ACK.
144
+ Malformed ACKs cannot grant proof. Only negotiated `invalid_event` rejection of
145
+ an original exception can retry its stripped legacy form once; standalone
146
+ context, policy and authentication rejections do not use this fallback.
147
+ Framework relay adapters read at most the configured body limit plus one byte
148
+ before parsing, and preserve the negotiation query/header through the handler.
149
+
54
150
  Version 2.0.0 protects capture callers with bounded, priority-aware background delivery. Review the [2.0 migration guide](MIGRATION-2.0.md) before upgrading from 1.x.
55
151
 
56
152
  ## Automatic capture and application filtering
@@ -357,3 +453,28 @@ CI validates Ruff, mypy, pytest, package build, event schema fixtures, and cover
357
453
  ## License
358
454
 
359
455
  Apache-2.0. See `LICENSE`.
456
+
457
+ ## Delivery acknowledgement and retry limits
458
+
459
+ The built-in HTTP transport requires the canonical ingestion acknowledgement (`accepted`, `rejected`, and `errors`). Empty responses, unrelated JSON, malformed counts, or invalid rejection indices retain the full batch with backoff. Valid acknowledgements remove accepted and terminally rejected events and retry only the indexed retryable rejections; an all-rejected batch does not advance `lastEventAt`.
460
+
461
+ File transports and explicitly supplied custom/legacy transports retain their documented bodyless success fallback. A custom transport that returns acknowledgement fields must return the complete canonical shape. Retry hints (delay seconds or HTTP dates) are bounded to five minutes and measured from response receipt, including partial/protocol acknowledgements and retryable server failures. Without a server hint, existing retry timing is preserved. Failures remain contained within the SDK's existing delivery path.
462
+
463
+ ## Probe checkpoint guidance
464
+
465
+ Application instrumentation supplies probe labels and bounded data. Deploy a new
466
+ checkpoint before activating it. Remote activation affects future matching calls
467
+ when policy permits; it does not inject code or recover unretained measurements.
468
+ Built-in runtime/error/device collectors keep their documented SDK settings.
469
+
470
+ Use fixed labels and allowlisted technical states: `media.preview.checkpoint`
471
+ (stage/input kind/attempt), `auth.session.checkpoint` (observed session presence
472
+ and result), `job.dependency.checkpoint` (named dependency/attempt/result), or
473
+ `cache.preview.checkpoint` (hit/miss/coarse age bucket). Omit customer identifiers,
474
+ tokens/cookies, cache keys, URLs and raw payloads. HTTP 200 or attempt completion
475
+ alone does not establish semantic recovery.
476
+
477
+ Empty probe data means no matching retained observation for the selected sample
478
+ window. It does not confirm activation delivery to every SDK or execution of a
479
+ label. Check the deployed SDK/wrapper version, instrumentation, scope, expiry,
480
+ policy, sampling and delivery receipts. See the [probe recipes](https://debugbundle.com/docs/probes#bounded-checkpoint-recipes).
@@ -0,0 +1,51 @@
1
+ debugbundle/__init__.py,sha256=OFqqIibB3YlNeuTSynunM_XZ0cuts3Xoh15yNYDVEFs,5685
2
+ debugbundle/acknowledgement.py,sha256=2miOWbuIfElrbkktJHEuL7a2-IqKYUZCpO1UjSaouYw,2461
3
+ debugbundle/before_send.py,sha256=VpE056j_GRtJhhF9er-aLvR665fagPkvUlyXIJxrgbE,2124
4
+ debugbundle/canonical_event.py,sha256=qMwKCMsF-rHnYMnNVB2obDe66ykTXlM5S6chHSJODBI,8054
5
+ debugbundle/capture_hooks.py,sha256=70Q6ske8JulyuYpeE9FohvMreytf4DUrj4QW9f2-o2Q,1940
6
+ debugbundle/config.py,sha256=3ZOYk35dEnZMqJOI_hvTGOzC17seVPUGxjnX05J15Do,9571
7
+ debugbundle/core.py,sha256=_oLk0FwW9vj1bxHDvW6cs47i0q6v4cjCsuA7k6N3Gr8,28285
8
+ debugbundle/delivery_hooks.py,sha256=hIXmFcKbHDw-3004Mc-pyz4iZGZNR-ZDiBw2xYKCTn4,3455
9
+ debugbundle/diagnostic_budget.py,sha256=dEfse2fqwYgR7IL6FASjqZ8cnq9lLlcwnLJ38utN0G0,7032
10
+ debugbundle/event-envelope.schema.json,sha256=QSAmE8Be_e91T_4_bDrBScdctEXABHpcsjMqyxZKeSI,148624
11
+ debugbundle/event_support.py,sha256=keOpma92Hxer2mOD9ELw-w5MRFYNOsj5A9C9goLdkDE,9046
12
+ debugbundle/evidence_delivery.py,sha256=cadrVo0lq5hNBLhl5woJof_HjAlrqrSgYjvBGfoUWVY,4781
13
+ debugbundle/evidence_http.py,sha256=k490I2Mh9L62A37Le1pnACb-9jVKIMFjsp9u0mYMhuA,1769
14
+ debugbundle/evidence_json.py,sha256=hAiENRzJBKUV-IujHDmK4qEEDtBkb8ZsBo9vyBmGwEo,2349
15
+ debugbundle/evidence_wire.py,sha256=nUY9SiiKoP9LhE31y6q_vyPE5_JieI-ZFDRYL_DX0dQ,16259
16
+ debugbundle/exception_capture.py,sha256=fHUuDxVuY5Uq7nLhtaGVd-vghIZAI1Tdbh5bDsNluwg,3797
17
+ debugbundle/exception_evidence.py,sha256=cclp6aIuuqCM6HHKl4nHezZeswsQQhFZQn2s9fNtSVc,7517
18
+ debugbundle/exception_snapshot.py,sha256=Q3yX-rPuwyiP5tiyMCHnUE2r5Y5lne7bLb8s8abXtDw,2863
19
+ debugbundle/flush_aggregates.py,sha256=ukHsIUykeGp4YJsesA_9NCUMarg_BAXVuzO4bqJKH3Y,1464
20
+ debugbundle/flush_delivery.py,sha256=0gtluLskyKFWaKMPQSfd16tV256_rCvQrfV5muQOXdw,9009
21
+ debugbundle/ingestion_wire.py,sha256=SGyzAw4uGr9YRsB5hClVXb7il_nn3z0rm0Ofziw51qo,2537
22
+ debugbundle/logger_integrations.py,sha256=T6LR10_DgDS8C-CUPxtC1kZz3qfX5j0B2YUV7LxDovk,3572
23
+ debugbundle/operation_context.py,sha256=oeY0W5su8VG1kWa4L8XrJZcNiAUZsGspjC5y-WGMXJQ,3783
24
+ debugbundle/probe_capture.py,sha256=c1a-ZKHyNeu6CMS5zgi8_wIfpLs5-pe12GEXNMpMTVM,3969
25
+ debugbundle/process_support.py,sha256=rKV_afnK-_tEcPO9Fzv2xvjN5Xt1sGwrZoF9yCsQ6_w,2273
26
+ debugbundle/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ debugbundle/queue_admission.py,sha256=NtXwpB8lZIu0sbtxJUNHXQp_AcgVNDnavEM80rz3raY,2970
28
+ debugbundle/recovery_capture.py,sha256=IOCarb9vmJ3ylwoYnD7GFTRwbra17dx7JCvuCA1isNY,1663
29
+ debugbundle/redaction.py,sha256=mxJgtILpVDUaj8LAhzswfaX1M4Ppyx87lfkhFqbMR8M,9663
30
+ debugbundle/relay.py,sha256=FVEvwrmMxdEDkTk3WDoNHFD0-_2w1OiYYHWAwQFVu9c,17664
31
+ debugbundle/relay_delivery.py,sha256=2EowBh_s5aF6ruPs1PjOXFAZ7IULRe5SBBg8v-bDf9o,9355
32
+ debugbundle/remote_config_client.py,sha256=WrAq-YzixEomJ98pTB05qo2ImfJKayc-HSYRYO1VJg0,3206
33
+ debugbundle/request_capture_policy.py,sha256=fl1EilWAnBQCdGTafRpY0tMlYUoSJK_7hZyJmPHLkeI,1583
34
+ debugbundle/scope_ownership.py,sha256=uB3dpG9blEwFcUbQNAu0jjHoxJlRoliRx7bx8F1YNVE,3141
35
+ debugbundle/structlog_integration.py,sha256=hDwCGXTQ8GAjnx2jwlRbpnvLE0y798kFFMsSB3iqOrM,6061
36
+ debugbundle/suppression.py,sha256=V_JvDvcV7tN3EHkfpEJGK1WB2G4nR3GhXvaKd400iFQ,4418
37
+ debugbundle/transport.py,sha256=-MkMFf2rjPrDEGLVe-LHewFtkvVlA40X7XDaHKxVeqE,3343
38
+ debugbundle/trigger_token.py,sha256=YUwIWnnxu1klAVaB8Ltgk_PwWW_PPjq9rzmEbWXeFeM,4455
39
+ debugbundle/integrations/__init__.py,sha256=Jr87ImWXzUXOuFP4WlGleK14VARajSSdGZN_nHt5emw,1161
40
+ debugbundle/integrations/common.py,sha256=iiwf5wDlpujFuWfbO-ziFTn4MtiHBXSIZNXtamhCavg,2014
41
+ debugbundle/integrations/django.py,sha256=vDQGc0ObcHCe4NQbC0ijcXUM-y9GzOVBjDtcHzxyrxw,1828
42
+ debugbundle/integrations/fastapi.py,sha256=-054Z6MogkZIqKR8DHR4jm2nNC5yyZ5vtanpw6g-voE,3414
43
+ debugbundle/integrations/flask.py,sha256=cgyHbsAH96gpNPf_5IGJYzp2va28JdDgXROBsF-Fbyo,2390
44
+ debugbundle/integrations/relay_django.py,sha256=KCo9-5eumVZZKEmzZemL4srCRN7inMB5aeOSUqeEEXE,2378
45
+ debugbundle/integrations/relay_fastapi.py,sha256=oVL5m8SFsbcbxzmDVefGsBk75Ha2DOSymJwDPmHOI-o,2551
46
+ debugbundle/integrations/relay_flask.py,sha256=GPiXNR3feH-KU5GJ8tguOvt0Rr9b776GY8O2SjiLwQs,2169
47
+ debugbundle_python-2.1.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
48
+ debugbundle_python-2.1.0.dist-info/METADATA,sha256=-l6UHUrrbNukdvKQ3jPLvpkvJNtKPBZBktmpQizU99s,23754
49
+ debugbundle_python-2.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
50
+ debugbundle_python-2.1.0.dist-info/top_level.txt,sha256=RCB9STTFnl1OKdojxz-xhaks2zkRFs1meZXsKnm18LM,12
51
+ debugbundle_python-2.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (84.0.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,36 +0,0 @@
1
- debugbundle/__init__.py,sha256=QH5RY2Cdp3Ry5PNSAhiV9akOMoS2pERRqowFnZ94roc,5131
2
- debugbundle/acknowledgement.py,sha256=tyI8XLRG9N3-Weo6uiGrAlf_O4tjptVe1x1cG7RjrMc,2314
3
- debugbundle/before_send.py,sha256=_tmV6ovHUBIxfQPMQ8a0C6r91IAnd4DVs8UcjsKhqX0,7792
4
- debugbundle/capture_hooks.py,sha256=70Q6ske8JulyuYpeE9FohvMreytf4DUrj4QW9f2-o2Q,1940
5
- debugbundle/config.py,sha256=ENRSR5jUI7xYdBqUakWws3GIjBk00GWFmwXVE_iQp2Y,9296
6
- debugbundle/core.py,sha256=XgRx92jSzrP6PjvHHRI0NlwdD53cRmBQ6qN4UtnQqIA,39310
7
- debugbundle/delivery_hooks.py,sha256=BUqtHuEv1MDOSLv0sUP6D582c06JofsbXhi7I7Y5IGc,2691
8
- debugbundle/event_support.py,sha256=MtPg2CO7B91U7-zE9XGro0-fynaEnJZmQ6Ms9arfRlg,9046
9
- debugbundle/exception_snapshot.py,sha256=_2p55F572sL7_ogBL6Raa137HRyUqvSaHVMwpKiC8fs,2831
10
- debugbundle/flush_aggregates.py,sha256=ukHsIUykeGp4YJsesA_9NCUMarg_BAXVuzO4bqJKH3Y,1464
11
- debugbundle/logger_integrations.py,sha256=T6LR10_DgDS8C-CUPxtC1kZz3qfX5j0B2YUV7LxDovk,3572
12
- debugbundle/probe_capture.py,sha256=c1a-ZKHyNeu6CMS5zgi8_wIfpLs5-pe12GEXNMpMTVM,3969
13
- debugbundle/process_support.py,sha256=D1aFhaP1wOGfMdzkdtEhO54R3XWa8dLCZlfWHWN3qA4,1962
14
- debugbundle/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- debugbundle/queue_admission.py,sha256=QtFengGZ4KEVbu1UqSp3u8eLucQeS9dGIwrzq62A5cY,1433
16
- debugbundle/redaction.py,sha256=mxJgtILpVDUaj8LAhzswfaX1M4Ppyx87lfkhFqbMR8M,9663
17
- debugbundle/relay.py,sha256=9HkEEVZpqrebzH6A-sSuFVuFjyLg07JuTMpMG8pu7SY,14726
18
- debugbundle/relay_delivery.py,sha256=VL-nIgJR6mYXqKy-EbA0USWeY_iS_uAr1KBzrnwBnnk,4676
19
- debugbundle/request_capture_policy.py,sha256=fl1EilWAnBQCdGTafRpY0tMlYUoSJK_7hZyJmPHLkeI,1583
20
- debugbundle/structlog_integration.py,sha256=hDwCGXTQ8GAjnx2jwlRbpnvLE0y798kFFMsSB3iqOrM,6061
21
- debugbundle/suppression.py,sha256=V_JvDvcV7tN3EHkfpEJGK1WB2G4nR3GhXvaKd400iFQ,4418
22
- debugbundle/transport.py,sha256=V289FwFWL6KV5gyzOsesluEMbpRobm_FHu3x8BpOYKU,2014
23
- debugbundle/trigger_token.py,sha256=YUwIWnnxu1klAVaB8Ltgk_PwWW_PPjq9rzmEbWXeFeM,4455
24
- debugbundle/integrations/__init__.py,sha256=Jr87ImWXzUXOuFP4WlGleK14VARajSSdGZN_nHt5emw,1161
25
- debugbundle/integrations/common.py,sha256=iiwf5wDlpujFuWfbO-ziFTn4MtiHBXSIZNXtamhCavg,2014
26
- debugbundle/integrations/django.py,sha256=vDQGc0ObcHCe4NQbC0ijcXUM-y9GzOVBjDtcHzxyrxw,1828
27
- debugbundle/integrations/fastapi.py,sha256=-054Z6MogkZIqKR8DHR4jm2nNC5yyZ5vtanpw6g-voE,3414
28
- debugbundle/integrations/flask.py,sha256=cgyHbsAH96gpNPf_5IGJYzp2va28JdDgXROBsF-Fbyo,2390
29
- debugbundle/integrations/relay_django.py,sha256=Wj8BE9D5otU2KmtjgqdKIJ-BvXVcagCKgpLYvnPZ6WE,2281
30
- debugbundle/integrations/relay_fastapi.py,sha256=-Zl6bvhYMLii__mP6-UvSdwxS5VKsb9OZvk-yy-3hEw,2227
31
- debugbundle/integrations/relay_flask.py,sha256=VFHkDTJy4LkZzL_Ry_Ba-e_gW6UTG4PBwnLcvB_oXuQ,2011
32
- debugbundle_python-2.0.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
33
- debugbundle_python-2.0.0.dist-info/METADATA,sha256=oX9_gQfmoPs8PCSNucy0tTany71DEiuHsu71IPmYBmg,15231
34
- debugbundle_python-2.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
35
- debugbundle_python-2.0.0.dist-info/top_level.txt,sha256=RCB9STTFnl1OKdojxz-xhaks2zkRFs1meZXsKnm18LM,12
36
- debugbundle_python-2.0.0.dist-info/RECORD,,