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.
Files changed (31) hide show
  1. {auditry-0.2.1/src/auditry.egg-info → auditry-0.2.2}/PKG-INFO +1 -1
  2. {auditry-0.2.1 → auditry-0.2.2}/pyproject.toml +1 -1
  3. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/__init__.py +1 -1
  4. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/fastapi/adapters.py +9 -2
  5. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/quart/adapters.py +30 -3
  6. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/quart/middleware.py +35 -0
  7. {auditry-0.2.1 → auditry-0.2.2/src/auditry.egg-info}/PKG-INFO +1 -1
  8. {auditry-0.2.1 → auditry-0.2.2}/LICENSE +0 -0
  9. {auditry-0.2.1 → auditry-0.2.2}/MANIFEST.in +0 -0
  10. {auditry-0.2.1 → auditry-0.2.2}/README.md +0 -0
  11. {auditry-0.2.1 → auditry-0.2.2}/setup.cfg +0 -0
  12. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/core/__init__.py +0 -0
  13. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/core/base.py +0 -0
  14. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/core/logger.py +0 -0
  15. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/correlation.py +0 -0
  16. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/fastapi/__init__.py +0 -0
  17. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/fastapi/middleware.py +0 -0
  18. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/logging_config.py +0 -0
  19. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/models.py +0 -0
  20. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/py.typed +0 -0
  21. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/quart/__init__.py +0 -0
  22. {auditry-0.2.1 → auditry-0.2.2}/src/auditry/redaction.py +0 -0
  23. {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/SOURCES.txt +0 -0
  24. {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/dependency_links.txt +0 -0
  25. {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/requires.txt +0 -0
  26. {auditry-0.2.1 → auditry-0.2.2}/src/auditry.egg-info/top_level.txt +0 -0
  27. {auditry-0.2.1 → auditry-0.2.2}/tests/test_body_logging.py +0 -0
  28. {auditry-0.2.1 → auditry-0.2.2}/tests/test_correlation.py +0 -0
  29. {auditry-0.2.1 → auditry-0.2.2}/tests/test_fastapi_middleware.py +0 -0
  30. {auditry-0.2.1 → auditry-0.2.2}/tests/test_quart_middleware.py +0 -0
  31. {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.1
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.1"
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"}
@@ -31,7 +31,7 @@ from .logging_config import configure_logging, get_logger
31
31
  from .correlation import get_correlation_id
32
32
  from .models import ObservabilityConfig, BusinessEventConfig
33
33
 
34
- __version__ = "0.2.0"
34
+ __version__ = "0.2.2"
35
35
 
36
36
  __all__ = [
37
37
  # Configuration
@@ -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
- from typing import Any, Dict, Optional
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
- from typing import Any, Dict, Optional
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
- return await response.get_data()
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.1
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