auditry 0.2.1__tar.gz → 0.2.2__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.
- {auditry-0.2.1/src/auditry.egg-info → auditry-0.2.2}/PKG-INFO +1 -1
- {auditry-0.2.1 → auditry-0.2.2}/pyproject.toml +1 -1
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/__init__.py +1 -1
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/fastapi/adapters.py +9 -2
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/quart/adapters.py +30 -3
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/quart/middleware.py +35 -0
- {auditry-0.2.1 → auditry-0.2.2/src/auditry.egg-info}/PKG-INFO +1 -1
- {auditry-0.2.1 → auditry-0.2.2}/LICENSE +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/MANIFEST.in +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/README.md +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/setup.cfg +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/core/__init__.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/core/base.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/core/logger.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/correlation.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/fastapi/__init__.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/fastapi/middleware.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/logging_config.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/models.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/py.typed +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/quart/__init__.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry/redaction.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/SOURCES.txt +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/dependency_links.txt +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/requires.txt +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/top_level.txt +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/tests/test_body_logging.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/tests/test_correlation.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/tests/test_fastapi_middleware.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/tests/test_quart_middleware.py +0 -0
- {auditry-0.2.1 → auditry-0.2.2}/tests/test_redaction.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: auditry
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Observability middleware for FastAPI and Quart with request/response logging, correlation IDs, and sensitive data redaction
|
|
5
5
|
Author-email: Liv Stark <livstark.work@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "auditry"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Observability middleware for FastAPI and Quart with request/response logging, correlation IDs, and sensitive data redaction"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Liv Stark", email = "livstark.work@gmail.com"}
|
|
@@ -5,7 +5,8 @@ These adapters implement the abstract base classes to extract data from
|
|
|
5
5
|
FastAPI/Starlette request and response objects.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import inspect
|
|
9
|
+
from typing import Dict, Optional
|
|
9
10
|
|
|
10
11
|
from fastapi import Request, Response
|
|
11
12
|
from starlette.responses import StreamingResponse
|
|
@@ -104,8 +105,14 @@ class FastAPIResponseAdapter(BaseResponseAdapter):
|
|
|
104
105
|
if isinstance(response, StreamingResponse):
|
|
105
106
|
return None
|
|
106
107
|
|
|
108
|
+
# Check if response has a body_iterator (generator/async generator)
|
|
109
|
+
if hasattr(response, "body_iterator"):
|
|
110
|
+
# Check if it's a generator or async generator
|
|
111
|
+
if inspect.isasyncgen(response.body_iterator) or inspect.isgenerator(response.body_iterator):
|
|
112
|
+
return None
|
|
113
|
+
|
|
107
114
|
# Get body if available
|
|
108
115
|
if hasattr(response, "body"):
|
|
109
116
|
return response.body
|
|
110
117
|
|
|
111
|
-
return None
|
|
118
|
+
return None
|
|
@@ -5,7 +5,7 @@ These adapters implement the abstract base classes to extract data from
|
|
|
5
5
|
Quart request and response objects.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import inspect
|
|
9
9
|
import json
|
|
10
10
|
|
|
11
11
|
from quart import Request, Response, g
|
|
@@ -133,17 +133,44 @@ class QuartResponseAdapter(BaseResponseAdapter):
|
|
|
133
133
|
"""
|
|
134
134
|
Extract body from Quart response.
|
|
135
135
|
|
|
136
|
+
Returns None for streaming responses to avoid consuming the stream.
|
|
136
137
|
Handles both Response objects and tuple returns.
|
|
137
138
|
"""
|
|
138
139
|
try:
|
|
139
140
|
# For Response objects
|
|
141
|
+
if hasattr(response, "response"):
|
|
142
|
+
# Check if the response is an async generator (streaming)
|
|
143
|
+
if inspect.isasyncgen(response.response):
|
|
144
|
+
# This is a streaming response, return None to avoid consuming it
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
# Check if it's a regular generator (also streaming)
|
|
148
|
+
if inspect.isgenerator(response.response):
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
# For Response objects with get_data method (non-streaming)
|
|
140
152
|
if hasattr(response, "get_data"):
|
|
141
|
-
|
|
153
|
+
# Only call get_data if we're sure it's not streaming
|
|
154
|
+
# Double-check for streaming response attribute
|
|
155
|
+
if hasattr(response, "response"):
|
|
156
|
+
resp_attr = response.response
|
|
157
|
+
if inspect.isasyncgen(resp_attr) or inspect.isgenerator(resp_attr):
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
# Safe to get data for non-streaming responses
|
|
161
|
+
data = await response.get_data()
|
|
162
|
+
if isinstance(data, str):
|
|
163
|
+
return data.encode("utf-8")
|
|
164
|
+
return data
|
|
142
165
|
|
|
143
166
|
# For tuple responses (body, status_code, headers)
|
|
144
167
|
if isinstance(response, tuple) and len(response) >= 1:
|
|
145
168
|
body = response[0]
|
|
146
169
|
|
|
170
|
+
# Check if body is a generator (streaming)
|
|
171
|
+
if inspect.isasyncgen(body) or inspect.isgenerator(body):
|
|
172
|
+
return None
|
|
173
|
+
|
|
147
174
|
# Convert various body types to bytes
|
|
148
175
|
if isinstance(body, bytes):
|
|
149
176
|
return body
|
|
@@ -154,4 +181,4 @@ class QuartResponseAdapter(BaseResponseAdapter):
|
|
|
154
181
|
|
|
155
182
|
return None
|
|
156
183
|
except Exception:
|
|
157
|
-
return None
|
|
184
|
+
return None
|
|
@@ -89,6 +89,41 @@ class QuartMiddleware(BaseMiddleware):
|
|
|
89
89
|
if not hasattr(request, "observability_start_time"):
|
|
90
90
|
return response
|
|
91
91
|
|
|
92
|
+
# Check if this is a streaming response - if so, handle it separately
|
|
93
|
+
if hasattr(response, "response"):
|
|
94
|
+
if inspect.isasyncgen(response.response) or inspect.isgenerator(response.response):
|
|
95
|
+
# For streaming responses, log without consuming the stream
|
|
96
|
+
duration_ms = (time.time() - request.observability_start_time) * 1000
|
|
97
|
+
|
|
98
|
+
# Get stored correlation ID from before_request
|
|
99
|
+
correlation_id = getattr(request, "observability_correlation_id", None)
|
|
100
|
+
request_data = getattr(request, "observability_request_data", {})
|
|
101
|
+
|
|
102
|
+
# Re-extract user_id (might have been set during request processing)
|
|
103
|
+
user_id = await self.request_adapter.extract_user_id(request)
|
|
104
|
+
|
|
105
|
+
# Log with minimal response data (no body for streaming)
|
|
106
|
+
self.logger.log_success(
|
|
107
|
+
request_data=request_data,
|
|
108
|
+
response_data={
|
|
109
|
+
"status_code": response.status_code if hasattr(response, "status_code") else 200,
|
|
110
|
+
"headers": dict(response.headers) if hasattr(response, "headers") else {},
|
|
111
|
+
"body": None # Don't try to extract body for streaming
|
|
112
|
+
},
|
|
113
|
+
duration_ms=duration_ms,
|
|
114
|
+
correlation_id=correlation_id,
|
|
115
|
+
user_id=user_id,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Add correlation ID to response headers (same as non-streaming)
|
|
119
|
+
if correlation_id:
|
|
120
|
+
response.headers[self.config.correlation_id_header] = correlation_id
|
|
121
|
+
|
|
122
|
+
# Clear cache and return immediately
|
|
123
|
+
self.request_adapter.clear_cache(request)
|
|
124
|
+
return response
|
|
125
|
+
|
|
126
|
+
# Non-streaming response handling
|
|
92
127
|
# Calculate duration
|
|
93
128
|
duration_ms = (time.time() - request.observability_start_time) * 1000
|
|
94
129
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: auditry
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Observability middleware for FastAPI and Quart with request/response logging, correlation IDs, and sensitive data redaction
|
|
5
5
|
Author-email: Liv Stark <livstark.work@gmail.com>
|
|
6
6
|
License: MIT
|
|
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
|