auditry 0.2.0__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.0/src/auditry.egg-info → auditry-0.2.2}/PKG-INFO +34 -4
  2. {auditry-0.2.0 → auditry-0.2.2}/README.md +33 -3
  3. {auditry-0.2.0 → auditry-0.2.2}/pyproject.toml +1 -3
  4. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/__init__.py +1 -1
  5. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/core/logger.py +8 -4
  6. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/fastapi/adapters.py +9 -2
  7. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/models.py +9 -1
  8. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/quart/adapters.py +30 -3
  9. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/quart/middleware.py +35 -0
  10. {auditry-0.2.0 → auditry-0.2.2/src/auditry.egg-info}/PKG-INFO +34 -4
  11. {auditry-0.2.0 → auditry-0.2.2}/src/auditry.egg-info/SOURCES.txt +1 -0
  12. auditry-0.2.2/tests/test_body_logging.py +166 -0
  13. {auditry-0.2.0 → auditry-0.2.2}/LICENSE +0 -0
  14. {auditry-0.2.0 → auditry-0.2.2}/MANIFEST.in +0 -0
  15. {auditry-0.2.0 → auditry-0.2.2}/setup.cfg +0 -0
  16. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/core/__init__.py +0 -0
  17. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/core/base.py +0 -0
  18. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/correlation.py +0 -0
  19. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/fastapi/__init__.py +0 -0
  20. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/fastapi/middleware.py +0 -0
  21. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/logging_config.py +0 -0
  22. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/py.typed +0 -0
  23. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/quart/__init__.py +0 -0
  24. {auditry-0.2.0 → auditry-0.2.2}/src/auditry/redaction.py +0 -0
  25. {auditry-0.2.0 → auditry-0.2.2}/src/auditry.egg-info/dependency_links.txt +0 -0
  26. {auditry-0.2.0 → auditry-0.2.2}/src/auditry.egg-info/requires.txt +0 -0
  27. {auditry-0.2.0 → auditry-0.2.2}/src/auditry.egg-info/top_level.txt +0 -0
  28. {auditry-0.2.0 → auditry-0.2.2}/tests/test_correlation.py +0 -0
  29. {auditry-0.2.0 → auditry-0.2.2}/tests/test_fastapi_middleware.py +0 -0
  30. {auditry-0.2.0 → auditry-0.2.2}/tests/test_quart_middleware.py +0 -0
  31. {auditry-0.2.0 → 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.0
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
@@ -133,11 +133,13 @@ config = ObservabilityConfig(
133
133
  )
134
134
  ```
135
135
 
136
+ **Note:** The `service_name` is required and must be provided. This ensures all services have meaningful names in logs rather than generic defaults.
137
+
136
138
  ### Full Configuration Options
137
139
 
138
140
  ```python
139
141
  config = ObservabilityConfig(
140
- # REQUIRED: Service name for log filtering
142
+ # REQUIRED: Service name for log filtering (no default)
141
143
  service_name="my-service-name",
142
144
 
143
145
  # Correlation ID header name (default: X-Correlation-ID)
@@ -158,6 +160,14 @@ config = ObservabilityConfig(
158
160
 
159
161
  # Whether to log query parameters (default: True)
160
162
  log_query_params=True,
163
+
164
+ # Whether to log request bodies for all endpoints (default: True)
165
+ # Set to False for applications handling sensitive data
166
+ log_request_body=True,
167
+
168
+ # Whether to log response bodies for all endpoints (default: True)
169
+ # Set to False for applications returning sensitive data
170
+ log_response_body=True,
161
171
  )
162
172
 
163
173
  # For FastAPI:
@@ -355,9 +365,11 @@ All logs are structured JSON, ready for log aggregators:
355
365
  }
356
366
  ```
357
367
 
358
- ## Sensitive Data Redaction
368
+ ## Sensitive Data Handling
369
+
370
+ ### Automatic Redaction
359
371
 
360
- Automatically redacts these sensitive field patterns:
372
+ Automatically redacts these sensitive field patterns in all logged requests/responses:
361
373
 
362
374
  - `password`
363
375
  - `token`
@@ -376,6 +388,24 @@ config = ObservabilityConfig(
376
388
  additional_redaction_patterns=["internal_token", "employee_id"],
377
389
  )
378
390
  ```
391
+
392
+ ### Disabling Body Logging (Application-Wide)
393
+
394
+ For applications handling sensitive data, you can disable logging of request and/or response bodies across the entire application:
395
+
396
+ ```python
397
+ config = ObservabilityConfig(
398
+ service_name="my-service-name",
399
+
400
+ # Disable request body logging for all endpoints
401
+ log_request_body=False,
402
+
403
+ # Disable response body logging for all endpoints
404
+ log_response_body=False,
405
+ )
406
+ ```
407
+
408
+ When body logging is disabled, logs will show `[BODY_LOGGING_DISABLED]` instead of the actual content, while still logging metadata like headers, status codes, and timing information.
379
409
  ## Best Practices
380
410
 
381
411
  ### 1. Configure Logging Early
@@ -90,11 +90,13 @@ config = ObservabilityConfig(
90
90
  )
91
91
  ```
92
92
 
93
+ **Note:** The `service_name` is required and must be provided. This ensures all services have meaningful names in logs rather than generic defaults.
94
+
93
95
  ### Full Configuration Options
94
96
 
95
97
  ```python
96
98
  config = ObservabilityConfig(
97
- # REQUIRED: Service name for log filtering
99
+ # REQUIRED: Service name for log filtering (no default)
98
100
  service_name="my-service-name",
99
101
 
100
102
  # Correlation ID header name (default: X-Correlation-ID)
@@ -115,6 +117,14 @@ config = ObservabilityConfig(
115
117
 
116
118
  # Whether to log query parameters (default: True)
117
119
  log_query_params=True,
120
+
121
+ # Whether to log request bodies for all endpoints (default: True)
122
+ # Set to False for applications handling sensitive data
123
+ log_request_body=True,
124
+
125
+ # Whether to log response bodies for all endpoints (default: True)
126
+ # Set to False for applications returning sensitive data
127
+ log_response_body=True,
118
128
  )
119
129
 
120
130
  # For FastAPI:
@@ -312,9 +322,11 @@ All logs are structured JSON, ready for log aggregators:
312
322
  }
313
323
  ```
314
324
 
315
- ## Sensitive Data Redaction
325
+ ## Sensitive Data Handling
326
+
327
+ ### Automatic Redaction
316
328
 
317
- Automatically redacts these sensitive field patterns:
329
+ Automatically redacts these sensitive field patterns in all logged requests/responses:
318
330
 
319
331
  - `password`
320
332
  - `token`
@@ -333,6 +345,24 @@ config = ObservabilityConfig(
333
345
  additional_redaction_patterns=["internal_token", "employee_id"],
334
346
  )
335
347
  ```
348
+
349
+ ### Disabling Body Logging (Application-Wide)
350
+
351
+ For applications handling sensitive data, you can disable logging of request and/or response bodies across the entire application:
352
+
353
+ ```python
354
+ config = ObservabilityConfig(
355
+ service_name="my-service-name",
356
+
357
+ # Disable request body logging for all endpoints
358
+ log_request_body=False,
359
+
360
+ # Disable response body logging for all endpoints
361
+ log_response_body=False,
362
+ )
363
+ ```
364
+
365
+ When body logging is disabled, logs will show `[BODY_LOGGING_DISABLED]` instead of the actual content, while still logging metadata like headers, status codes, and timing information.
336
366
  ## Best Practices
337
367
 
338
368
  ### 1. Configure Logging Early
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "auditry"
3
- version = "0.2.0"
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"}
@@ -23,14 +23,12 @@ classifiers = [
23
23
  "Topic :: System :: Logging",
24
24
  ]
25
25
 
26
- # Core dependencies needed for all functionality
27
26
  dependencies = [
28
27
  "pydantic>=2.0.0",
29
28
  "structlog>=23.0.0",
30
29
  "asgi-correlation-id>=4.0.0",
31
30
  ]
32
31
 
33
- # Optional dependencies for specific frameworks
34
32
  [project.optional-dependencies]
35
33
  fastapi = [
36
34
  "fastapi>=0.100.0",
@@ -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
@@ -81,9 +81,11 @@ class RequestResponseLogger:
81
81
  if raw_data.get("path_params"):
82
82
  prepared["path_params"] = raw_data["path_params"]
83
83
 
84
- # Parse and redact body
85
- if raw_data.get("body"):
84
+ # Parse and redact body (only if configured to log request body)
85
+ if self.config.log_request_body and raw_data.get("body"):
86
86
  prepared["body"] = self._parse_and_redact_body(raw_data["body"])
87
+ elif raw_data.get("body") and not self.config.log_request_body:
88
+ prepared["body"] = "[BODY_LOGGING_DISABLED]"
87
89
 
88
90
  return prepared
89
91
 
@@ -110,9 +112,11 @@ class RequestResponseLogger:
110
112
  if self.config.log_response_headers and raw_data.get("headers"):
111
113
  prepared["headers"] = redact_headers(raw_data["headers"])
112
114
 
113
- # Parse and redact body
114
- if raw_data.get("body"):
115
+ # Parse and redact body (only if configured to log response body)
116
+ if self.config.log_response_body and raw_data.get("body"):
115
117
  prepared["body"] = self._parse_and_redact_body(raw_data["body"])
118
+ elif raw_data.get("body") and not self.config.log_response_body:
119
+ prepared["body"] = "[BODY_LOGGING_DISABLED]"
116
120
 
117
121
  return prepared
118
122
 
@@ -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
@@ -68,4 +68,12 @@ class ObservabilityConfig(BaseModel):
68
68
  log_response_headers: bool = Field(
69
69
  default=False, description="Whether to log response headers (redacted)"
70
70
  )
71
- log_query_params: bool = Field(default=True, description="Whether to log query parameters")
71
+ log_query_params: bool = Field(default=True, description="Whether to log query parameters")
72
+ log_request_body: bool = Field(
73
+ default=True,
74
+ description="Whether to log request bodies for the application"
75
+ )
76
+ log_response_body: bool = Field(
77
+ default=True,
78
+ description="Whether to log response bodies for the application"
79
+ )
@@ -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.0
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
@@ -133,11 +133,13 @@ config = ObservabilityConfig(
133
133
  )
134
134
  ```
135
135
 
136
+ **Note:** The `service_name` is required and must be provided. This ensures all services have meaningful names in logs rather than generic defaults.
137
+
136
138
  ### Full Configuration Options
137
139
 
138
140
  ```python
139
141
  config = ObservabilityConfig(
140
- # REQUIRED: Service name for log filtering
142
+ # REQUIRED: Service name for log filtering (no default)
141
143
  service_name="my-service-name",
142
144
 
143
145
  # Correlation ID header name (default: X-Correlation-ID)
@@ -158,6 +160,14 @@ config = ObservabilityConfig(
158
160
 
159
161
  # Whether to log query parameters (default: True)
160
162
  log_query_params=True,
163
+
164
+ # Whether to log request bodies for all endpoints (default: True)
165
+ # Set to False for applications handling sensitive data
166
+ log_request_body=True,
167
+
168
+ # Whether to log response bodies for all endpoints (default: True)
169
+ # Set to False for applications returning sensitive data
170
+ log_response_body=True,
161
171
  )
162
172
 
163
173
  # For FastAPI:
@@ -355,9 +365,11 @@ All logs are structured JSON, ready for log aggregators:
355
365
  }
356
366
  ```
357
367
 
358
- ## Sensitive Data Redaction
368
+ ## Sensitive Data Handling
369
+
370
+ ### Automatic Redaction
359
371
 
360
- Automatically redacts these sensitive field patterns:
372
+ Automatically redacts these sensitive field patterns in all logged requests/responses:
361
373
 
362
374
  - `password`
363
375
  - `token`
@@ -376,6 +388,24 @@ config = ObservabilityConfig(
376
388
  additional_redaction_patterns=["internal_token", "employee_id"],
377
389
  )
378
390
  ```
391
+
392
+ ### Disabling Body Logging (Application-Wide)
393
+
394
+ For applications handling sensitive data, you can disable logging of request and/or response bodies across the entire application:
395
+
396
+ ```python
397
+ config = ObservabilityConfig(
398
+ service_name="my-service-name",
399
+
400
+ # Disable request body logging for all endpoints
401
+ log_request_body=False,
402
+
403
+ # Disable response body logging for all endpoints
404
+ log_response_body=False,
405
+ )
406
+ ```
407
+
408
+ When body logging is disabled, logs will show `[BODY_LOGGING_DISABLED]` instead of the actual content, while still logging metadata like headers, status codes, and timing information.
379
409
  ## Best Practices
380
410
 
381
411
  ### 1. Configure Logging Early
@@ -22,6 +22,7 @@ src/auditry/fastapi/middleware.py
22
22
  src/auditry/quart/__init__.py
23
23
  src/auditry/quart/adapters.py
24
24
  src/auditry/quart/middleware.py
25
+ tests/test_body_logging.py
25
26
  tests/test_correlation.py
26
27
  tests/test_fastapi_middleware.py
27
28
  tests/test_quart_middleware.py
@@ -0,0 +1,166 @@
1
+ """Tests for request/response body logging configuration."""
2
+ import pytest
3
+ from fastapi import FastAPI, Request
4
+ from fastapi.testclient import TestClient
5
+ from quart import Quart
6
+
7
+ from auditry import ObservabilityConfig, configure_logging
8
+ from auditry.fastapi import create_middleware
9
+ from auditry.quart import create_middleware as create_quart_middleware
10
+
11
+
12
+ configure_logging(level="INFO")
13
+
14
+
15
+ def test_fastapi_disable_request_body_logging():
16
+ """Test that request body logging can be disabled in FastAPI."""
17
+ app = FastAPI()
18
+ app = create_middleware(
19
+ app,
20
+ config=ObservabilityConfig(
21
+ service_name="test-service",
22
+ log_request_body=False, # Disable request body logging
23
+ log_response_body=True,
24
+ )
25
+ )
26
+
27
+ @app.post("/users")
28
+ def create_user():
29
+ return {"id": "123", "status": "created"}
30
+
31
+ client = TestClient(app)
32
+ response = client.post(
33
+ "/users",
34
+ json={"name": "Test User", "password": "secret123", "ssn": "123-45-6789"}
35
+ )
36
+ assert response.status_code == 200
37
+ # Body should not be logged, but response should be
38
+
39
+
40
+ def test_fastapi_disable_response_body_logging():
41
+ """Test that response body logging can be disabled in FastAPI."""
42
+ app = FastAPI()
43
+ app = create_middleware(
44
+ app,
45
+ config=ObservabilityConfig(
46
+ service_name="test-service",
47
+ log_request_body=True,
48
+ log_response_body=False, # Disable response body logging
49
+ )
50
+ )
51
+
52
+ @app.post("/users")
53
+ def create_user():
54
+ return {
55
+ "id": "123",
56
+ "name": "Test User",
57
+ "ssn": "123-45-6789", # Sensitive data in response
58
+ "api_key": "secret-key"
59
+ }
60
+
61
+ client = TestClient(app)
62
+ response = client.post(
63
+ "/users",
64
+ json={"name": "Test User"}
65
+ )
66
+ assert response.status_code == 200
67
+ # Request body should be logged, but response body should not be
68
+
69
+
70
+ def test_fastapi_disable_both_body_logging():
71
+ """Test that both request and response body logging can be disabled."""
72
+ app = FastAPI()
73
+ app = create_middleware(
74
+ app,
75
+ config=ObservabilityConfig(
76
+ service_name="test-service",
77
+ log_request_body=False,
78
+ log_response_body=False,
79
+ )
80
+ )
81
+
82
+ @app.post("/sensitive")
83
+ def sensitive_endpoint():
84
+ return {"secret": "data"}
85
+
86
+ client = TestClient(app)
87
+ response = client.post(
88
+ "/sensitive",
89
+ json={"password": "secret", "credit_card": "1234-5678-9012-3456"}
90
+ )
91
+ assert response.status_code == 200
92
+ # Neither request nor response bodies should be logged
93
+
94
+
95
+ @pytest.mark.asyncio
96
+ async def test_quart_disable_request_body_logging():
97
+ """Test that request body logging can be disabled in Quart."""
98
+ app = Quart(__name__)
99
+ app = create_quart_middleware(
100
+ app,
101
+ config=ObservabilityConfig(
102
+ service_name="test-quart-service",
103
+ log_request_body=False, # Disable request body logging
104
+ log_response_body=True,
105
+ )
106
+ )
107
+
108
+ @app.route("/users", methods=["POST"])
109
+ async def create_user():
110
+ return {"id": "123", "status": "created"}
111
+
112
+ client = app.test_client()
113
+ response = await client.post(
114
+ "/users",
115
+ json={"name": "Test User", "password": "secret123", "ssn": "123-45-6789"}
116
+ )
117
+ assert response.status_code == 200
118
+
119
+
120
+ @pytest.mark.asyncio
121
+ async def test_quart_disable_response_body_logging():
122
+ """Test that response body logging can be disabled in Quart."""
123
+ app = Quart(__name__)
124
+ app = create_quart_middleware(
125
+ app,
126
+ config=ObservabilityConfig(
127
+ service_name="test-quart-service",
128
+ log_request_body=True,
129
+ log_response_body=False, # Disable response body logging
130
+ )
131
+ )
132
+
133
+ @app.route("/users", methods=["POST"])
134
+ async def create_user():
135
+ return {
136
+ "id": "123",
137
+ "name": "Test User",
138
+ "ssn": "123-45-6789", # Sensitive data in response
139
+ "api_key": "secret-key"
140
+ }
141
+
142
+ client = app.test_client()
143
+ response = await client.post(
144
+ "/users",
145
+ json={"name": "Test User"}
146
+ )
147
+ assert response.status_code == 200
148
+
149
+
150
+ def test_service_name_required():
151
+ """Test that service_name is required and cannot be empty."""
152
+ # Should raise validation error without service_name
153
+ with pytest.raises(Exception): # Pydantic ValidationError
154
+ ObservabilityConfig()
155
+
156
+ # Should raise validation error with empty service_name
157
+ with pytest.raises(Exception): # Pydantic ValidationError
158
+ ObservabilityConfig(service_name="")
159
+
160
+ # Should raise validation error with whitespace-only service_name
161
+ with pytest.raises(Exception): # Pydantic ValidationError
162
+ ObservabilityConfig(service_name=" ")
163
+
164
+ # Should work with valid service_name
165
+ config = ObservabilityConfig(service_name="valid-service")
166
+ assert config.service_name == "valid-service"
File without changes
File without changes
File without changes
File without changes
File without changes