insider-python 0.1.7__tar.gz → 0.1.8__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.
- {insider_python-0.1.7 → insider_python-0.1.8}/PKG-INFO +1 -1
- {insider_python-0.1.7 → insider_python-0.1.8}/pyproject.toml +1 -1
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/_version.py +1 -1
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/asgi.py +12 -5
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/request.py +45 -8
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider_python.egg-info/PKG-INFO +1 -1
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_django_integration.py +14 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/README.md +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/setup.cfg +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/__init__.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/_envelope.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/_footprint.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/client.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/contrib/__init__.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/contrib/django/__init__.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/contrib/django/apps.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/contrib/django/middleware.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/dsn.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/__init__.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/__init__.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/capture.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/drf.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/handler.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/perf.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/signals.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/django/wsgi.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/logging/__init__.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/logging/handler.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/integrations/logging/levels.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/py.typed +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/safety.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/scope.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/scrubbing.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/stacktrace.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider/transport.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider_python.egg-info/SOURCES.txt +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider_python.egg-info/dependency_links.txt +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider_python.egg-info/requires.txt +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/src/insider_python.egg-info/top_level.txt +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_asgi_integration.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_capture.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_capture_log.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_capture_perf.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_django.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_drf_integration.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_dsn.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_envelope.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_logging_integration.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_never_crash.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_safety.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_scrubbing.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_stacktrace.py +0 -0
- {insider_python-0.1.7 → insider_python-0.1.8}/tests/test_transport.py +0 -0
|
@@ -19,7 +19,7 @@ from ...client import _client
|
|
|
19
19
|
from ...safety import debug, safe
|
|
20
20
|
from ...stacktrace import exception_payload
|
|
21
21
|
from .perf import emit_http_footprint
|
|
22
|
-
from .request import build_request_ctx_from_scope
|
|
22
|
+
from .request import build_request_ctx_from_scope, parse_response_body_text
|
|
23
23
|
|
|
24
24
|
ASGIApp = Callable[..., Any]
|
|
25
25
|
|
|
@@ -95,14 +95,19 @@ class _InsiderAsgiHttpWrapper:
|
|
|
95
95
|
|
|
96
96
|
start = time.perf_counter()
|
|
97
97
|
status_code: Optional[int] = None
|
|
98
|
+
response_content_type = ""
|
|
98
99
|
response_chunks: list[bytes] = []
|
|
99
100
|
response_bytes = 0
|
|
100
101
|
max_response_bytes = 8192
|
|
101
102
|
|
|
102
103
|
async def send_wrapper(message: Dict[str, Any]) -> None:
|
|
103
|
-
nonlocal status_code, response_bytes
|
|
104
|
+
nonlocal status_code, response_bytes, response_content_type
|
|
104
105
|
if message.get("type") == "http.response.start":
|
|
105
106
|
status_code = int(message.get("status", 500))
|
|
107
|
+
for key, value in message.get("headers") or []:
|
|
108
|
+
if key.lower() == b"content-type":
|
|
109
|
+
response_content_type = value.decode("latin-1")
|
|
110
|
+
break
|
|
106
111
|
elif (
|
|
107
112
|
client.send_default_pii
|
|
108
113
|
and message.get("type") == "http.response.body"
|
|
@@ -125,11 +130,13 @@ class _InsiderAsgiHttpWrapper:
|
|
|
125
130
|
raise
|
|
126
131
|
finally:
|
|
127
132
|
if client.send_default_pii and response_chunks:
|
|
128
|
-
|
|
133
|
+
body_text = b"".join(response_chunks).decode("utf-8", errors="replace")
|
|
129
134
|
if response_bytes >= max_response_bytes:
|
|
130
|
-
|
|
135
|
+
body_text += "...[truncated]"
|
|
131
136
|
ctx = dict(client.scope.current_request() or {})
|
|
132
|
-
ctx["response_body"] =
|
|
137
|
+
ctx["response_body"] = parse_response_body_text(
|
|
138
|
+
body_text, response_content_type
|
|
139
|
+
)
|
|
133
140
|
client.scope.set_request(ctx)
|
|
134
141
|
path = str(scope.get("path") or "/")
|
|
135
142
|
method = str(scope.get("method") or "GET")
|
|
@@ -184,8 +184,44 @@ def _read_body(request: Any) -> Optional[str]:
|
|
|
184
184
|
_RESPONSE_BODY_MAX_BYTES = 8192
|
|
185
185
|
|
|
186
186
|
|
|
187
|
-
def
|
|
188
|
-
"""
|
|
187
|
+
def parse_response_body_text(text: str, content_type: str = "") -> Any:
|
|
188
|
+
"""Decode captured body text; parse JSON API responses into objects."""
|
|
189
|
+
if not text:
|
|
190
|
+
return text
|
|
191
|
+
ct = content_type.lower()
|
|
192
|
+
if "application/json" in ct or ct.endswith("+json"):
|
|
193
|
+
try:
|
|
194
|
+
import json
|
|
195
|
+
|
|
196
|
+
return json.loads(text)
|
|
197
|
+
except json.JSONDecodeError:
|
|
198
|
+
pass
|
|
199
|
+
return text
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _response_content_type(response: Any) -> str:
|
|
203
|
+
try:
|
|
204
|
+
if hasattr(response, "get"):
|
|
205
|
+
value = response.get("Content-Type")
|
|
206
|
+
if value:
|
|
207
|
+
return str(value)
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
try:
|
|
211
|
+
headers = getattr(response, "headers", None)
|
|
212
|
+
if headers is not None:
|
|
213
|
+
value = headers.get("Content-Type")
|
|
214
|
+
if value:
|
|
215
|
+
return str(value)
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
return ""
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def read_response_body(
|
|
222
|
+
response: Any, *, max_bytes: int = _RESPONSE_BODY_MAX_BYTES
|
|
223
|
+
) -> Any:
|
|
224
|
+
"""Return response content; JSON responses are parsed to dict/list."""
|
|
189
225
|
if response is None:
|
|
190
226
|
return None
|
|
191
227
|
try:
|
|
@@ -200,14 +236,15 @@ def read_response_body(response: Any, *, max_bytes: int = _RESPONSE_BODY_MAX_BYT
|
|
|
200
236
|
if content is None:
|
|
201
237
|
return None
|
|
202
238
|
if not isinstance(content, (bytes, bytearray)):
|
|
203
|
-
return str(content)
|
|
239
|
+
return parse_response_body_text(str(content), _response_content_type(response))
|
|
204
240
|
raw = bytes(content)
|
|
205
|
-
|
|
241
|
+
truncated = len(raw) > max_bytes
|
|
242
|
+
if truncated:
|
|
206
243
|
raw = raw[:max_bytes]
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return
|
|
244
|
+
text = raw.decode("utf-8", errors="replace")
|
|
245
|
+
if truncated:
|
|
246
|
+
text += "...[truncated]"
|
|
247
|
+
return parse_response_body_text(text, _response_content_type(response))
|
|
211
248
|
except Exception:
|
|
212
249
|
return None
|
|
213
250
|
|
|
@@ -54,6 +54,20 @@ def test_integration_captures_response_body_when_pii_enabled(
|
|
|
54
54
|
assert fake_transport.envelopes[0].get("response_body") == "ok"
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
@pytest.mark.django_db
|
|
58
|
+
def test_integration_captures_json_response_body_as_object(
|
|
59
|
+
sdk_client, client, fake_transport
|
|
60
|
+
):
|
|
61
|
+
sdk_client.send_default_pii = True
|
|
62
|
+
response = client.get("/json/")
|
|
63
|
+
assert response.status_code == 200
|
|
64
|
+
assert len(fake_transport.envelopes) == 1
|
|
65
|
+
assert fake_transport.envelopes[0].get("response_body") == {
|
|
66
|
+
"ok": True,
|
|
67
|
+
"count": 2,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
57
71
|
@pytest.mark.django_db
|
|
58
72
|
def test_integration_clean_request_emits_one_footprint(client, fake_transport):
|
|
59
73
|
response = client.get("/ok/")
|
|
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
|
|
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
|
|
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
|
{insider_python-0.1.7 → insider_python-0.1.8}/src/insider_python.egg-info/dependency_links.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|