dory-sdk 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.
- dory/__init__.py +70 -0
- dory/auto_instrument.py +142 -0
- dory/cli/__init__.py +5 -0
- dory/cli/main.py +290 -0
- dory/cli/templates.py +333 -0
- dory/config/__init__.py +23 -0
- dory/config/defaults.py +50 -0
- dory/config/loader.py +361 -0
- dory/config/presets.py +325 -0
- dory/config/schema.py +152 -0
- dory/core/__init__.py +27 -0
- dory/core/app.py +404 -0
- dory/core/context.py +209 -0
- dory/core/lifecycle.py +214 -0
- dory/core/meta.py +121 -0
- dory/core/modes.py +479 -0
- dory/core/processor.py +654 -0
- dory/core/signals.py +122 -0
- dory/decorators.py +142 -0
- dory/errors/__init__.py +117 -0
- dory/errors/classification.py +362 -0
- dory/errors/codes.py +495 -0
- dory/health/__init__.py +10 -0
- dory/health/probes.py +210 -0
- dory/health/server.py +306 -0
- dory/k8s/__init__.py +11 -0
- dory/k8s/annotation_watcher.py +184 -0
- dory/k8s/client.py +251 -0
- dory/k8s/pod_metadata.py +182 -0
- dory/logging/__init__.py +9 -0
- dory/logging/logger.py +175 -0
- dory/metrics/__init__.py +7 -0
- dory/metrics/collector.py +301 -0
- dory/middleware/__init__.py +36 -0
- dory/middleware/connection_tracker.py +608 -0
- dory/middleware/request_id.py +321 -0
- dory/middleware/request_tracker.py +501 -0
- dory/migration/__init__.py +11 -0
- dory/migration/configmap.py +260 -0
- dory/migration/serialization.py +167 -0
- dory/migration/state_manager.py +301 -0
- dory/monitoring/__init__.py +23 -0
- dory/monitoring/opentelemetry.py +462 -0
- dory/py.typed +2 -0
- dory/recovery/__init__.py +60 -0
- dory/recovery/golden_image.py +480 -0
- dory/recovery/golden_snapshot.py +561 -0
- dory/recovery/golden_validator.py +518 -0
- dory/recovery/partial_recovery.py +479 -0
- dory/recovery/recovery_decision.py +242 -0
- dory/recovery/restart_detector.py +142 -0
- dory/recovery/state_validator.py +187 -0
- dory/resilience/__init__.py +45 -0
- dory/resilience/circuit_breaker.py +454 -0
- dory/resilience/retry.py +389 -0
- dory/sidecar/__init__.py +6 -0
- dory/sidecar/main.py +75 -0
- dory/sidecar/server.py +329 -0
- dory/simple.py +342 -0
- dory/types.py +75 -0
- dory/utils/__init__.py +25 -0
- dory/utils/errors.py +59 -0
- dory/utils/retry.py +115 -0
- dory/utils/timeout.py +80 -0
- dory_sdk-2.1.0.dist-info/METADATA +663 -0
- dory_sdk-2.1.0.dist-info/RECORD +69 -0
- dory_sdk-2.1.0.dist-info/WHEEL +5 -0
- dory_sdk-2.1.0.dist-info/entry_points.txt +3 -0
- dory_sdk-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request ID Middleware
|
|
3
|
+
|
|
4
|
+
Automatically generates and propagates request IDs for tracing and correlation.
|
|
5
|
+
Eliminates manual request ID management.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Automatic request ID generation
|
|
9
|
+
- Context propagation
|
|
10
|
+
- Log integration
|
|
11
|
+
- Correlation across services
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import logging
|
|
16
|
+
import uuid
|
|
17
|
+
from contextvars import ContextVar
|
|
18
|
+
from functools import wraps
|
|
19
|
+
from typing import Optional, Callable, Any
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# Context variable for current request ID
|
|
24
|
+
_request_id_context: ContextVar[Optional[str]] = ContextVar("request_id", default=None)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def generate_request_id() -> str:
|
|
28
|
+
"""
|
|
29
|
+
Generate a unique request ID.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
UUID-based request ID
|
|
33
|
+
"""
|
|
34
|
+
return str(uuid.uuid4())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_current_request_id() -> Optional[str]:
|
|
38
|
+
"""
|
|
39
|
+
Get the current request ID from context.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Current request ID or None if not set
|
|
43
|
+
"""
|
|
44
|
+
return _request_id_context.get()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def set_request_id(request_id: str) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Set the request ID in context.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
request_id: Request ID to set
|
|
53
|
+
"""
|
|
54
|
+
_request_id_context.set(request_id)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RequestIdMiddleware:
|
|
58
|
+
"""
|
|
59
|
+
Middleware for automatic request ID management.
|
|
60
|
+
|
|
61
|
+
Features:
|
|
62
|
+
- Generates request IDs
|
|
63
|
+
- Propagates through context
|
|
64
|
+
- Integrates with logging
|
|
65
|
+
- Supports custom ID generation
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
middleware = RequestIdMiddleware()
|
|
69
|
+
|
|
70
|
+
# Use context manager
|
|
71
|
+
async with middleware.with_request_id():
|
|
72
|
+
# All operations have access to request ID
|
|
73
|
+
request_id = get_current_request_id()
|
|
74
|
+
logger.info(f"Processing with request_id={request_id}")
|
|
75
|
+
|
|
76
|
+
# Or use decorator
|
|
77
|
+
@middleware.with_request_id_decorator()
|
|
78
|
+
async def process_item(item):
|
|
79
|
+
request_id = get_current_request_id()
|
|
80
|
+
# Process with request ID
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
id_generator: Optional[Callable[[], str]] = None,
|
|
86
|
+
header_name: str = "X-Request-ID",
|
|
87
|
+
log_request_id: bool = True,
|
|
88
|
+
):
|
|
89
|
+
"""
|
|
90
|
+
Initialize request ID middleware.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
id_generator: Optional custom ID generator function
|
|
94
|
+
header_name: Header name for request ID
|
|
95
|
+
log_request_id: Whether to add request ID to logs
|
|
96
|
+
"""
|
|
97
|
+
self.id_generator = id_generator or generate_request_id
|
|
98
|
+
self.header_name = header_name
|
|
99
|
+
self.log_request_id = log_request_id
|
|
100
|
+
|
|
101
|
+
logger.info(
|
|
102
|
+
f"RequestIdMiddleware initialized: header={header_name}, "
|
|
103
|
+
f"log_enabled={log_request_id}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
async def with_request_id(
|
|
107
|
+
self,
|
|
108
|
+
request_id: Optional[str] = None,
|
|
109
|
+
):
|
|
110
|
+
"""
|
|
111
|
+
Context manager that sets request ID for the scope.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
request_id: Optional existing request ID (generates new if None)
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
async with middleware.with_request_id():
|
|
118
|
+
# Operations here have access to request ID
|
|
119
|
+
request_id = get_current_request_id()
|
|
120
|
+
"""
|
|
121
|
+
# Generate or use provided request ID
|
|
122
|
+
if request_id is None:
|
|
123
|
+
request_id = self.id_generator()
|
|
124
|
+
|
|
125
|
+
# Set in context
|
|
126
|
+
token = _request_id_context.set(request_id)
|
|
127
|
+
|
|
128
|
+
# Add to logs if enabled
|
|
129
|
+
if self.log_request_id:
|
|
130
|
+
log_filter = RequestIdLogFilter(request_id)
|
|
131
|
+
logging.getLogger().addFilter(log_filter)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
yield request_id
|
|
135
|
+
finally:
|
|
136
|
+
# Reset context
|
|
137
|
+
_request_id_context.reset(token)
|
|
138
|
+
|
|
139
|
+
# Remove log filter
|
|
140
|
+
if self.log_request_id:
|
|
141
|
+
logging.getLogger().removeFilter(log_filter)
|
|
142
|
+
|
|
143
|
+
def with_request_id_decorator(
|
|
144
|
+
self,
|
|
145
|
+
request_id_arg: Optional[str] = None,
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
Decorator that automatically manages request ID.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
request_id_arg: Optional argument name containing request ID
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
@middleware.with_request_id_decorator()
|
|
155
|
+
async def process_item(item):
|
|
156
|
+
request_id = get_current_request_id()
|
|
157
|
+
logger.info(f"Processing {item}")
|
|
158
|
+
# Log will include request_id
|
|
159
|
+
|
|
160
|
+
# Or extract from argument
|
|
161
|
+
@middleware.with_request_id_decorator(request_id_arg="headers")
|
|
162
|
+
async def handle_request(headers):
|
|
163
|
+
# Uses headers.get("X-Request-ID") if present
|
|
164
|
+
pass
|
|
165
|
+
"""
|
|
166
|
+
def decorator(func):
|
|
167
|
+
@wraps(func)
|
|
168
|
+
async def wrapper(*args, **kwargs):
|
|
169
|
+
# Try to extract request ID from arguments if specified
|
|
170
|
+
extracted_id = None
|
|
171
|
+
if request_id_arg:
|
|
172
|
+
# Check kwargs
|
|
173
|
+
if request_id_arg in kwargs:
|
|
174
|
+
arg_value = kwargs[request_id_arg]
|
|
175
|
+
if isinstance(arg_value, dict):
|
|
176
|
+
extracted_id = arg_value.get(self.header_name)
|
|
177
|
+
else:
|
|
178
|
+
extracted_id = arg_value
|
|
179
|
+
|
|
180
|
+
# Use extracted or generate new
|
|
181
|
+
request_id = extracted_id or self.id_generator()
|
|
182
|
+
|
|
183
|
+
async with self.with_request_id(request_id):
|
|
184
|
+
return await func(*args, **kwargs)
|
|
185
|
+
|
|
186
|
+
return wrapper
|
|
187
|
+
|
|
188
|
+
return decorator
|
|
189
|
+
|
|
190
|
+
def extract_request_id(self, headers: dict) -> Optional[str]:
|
|
191
|
+
"""
|
|
192
|
+
Extract request ID from headers.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
headers: HTTP headers or similar dict
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Request ID if present, None otherwise
|
|
199
|
+
"""
|
|
200
|
+
return headers.get(self.header_name)
|
|
201
|
+
|
|
202
|
+
def inject_request_id(self, headers: dict, request_id: Optional[str] = None) -> dict:
|
|
203
|
+
"""
|
|
204
|
+
Inject request ID into headers.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
headers: HTTP headers or similar dict
|
|
208
|
+
request_id: Optional request ID (uses current context if None)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Headers with request ID injected
|
|
212
|
+
"""
|
|
213
|
+
if request_id is None:
|
|
214
|
+
request_id = get_current_request_id()
|
|
215
|
+
|
|
216
|
+
if request_id:
|
|
217
|
+
headers = dict(headers)
|
|
218
|
+
headers[self.header_name] = request_id
|
|
219
|
+
|
|
220
|
+
return headers
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class RequestIdLogFilter(logging.Filter):
|
|
224
|
+
"""
|
|
225
|
+
Log filter that adds request ID to log records.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
def __init__(self, request_id: str):
|
|
229
|
+
"""
|
|
230
|
+
Initialize log filter.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
request_id: Request ID to add to logs
|
|
234
|
+
"""
|
|
235
|
+
super().__init__()
|
|
236
|
+
self.request_id = request_id
|
|
237
|
+
|
|
238
|
+
def filter(self, record):
|
|
239
|
+
"""Add request ID to log record."""
|
|
240
|
+
record.request_id = self.request_id
|
|
241
|
+
return True
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# Convenience decorator using global middleware instance
|
|
245
|
+
_global_middleware: Optional[RequestIdMiddleware] = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_global_middleware() -> RequestIdMiddleware:
|
|
249
|
+
"""Get or create global middleware instance."""
|
|
250
|
+
global _global_middleware
|
|
251
|
+
if _global_middleware is None:
|
|
252
|
+
_global_middleware = RequestIdMiddleware()
|
|
253
|
+
return _global_middleware
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def with_request_id(func: Optional[Callable] = None, request_id: Optional[str] = None):
|
|
257
|
+
"""
|
|
258
|
+
Convenience decorator using global middleware.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
func: Function to decorate
|
|
262
|
+
request_id: Optional request ID
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
@with_request_id
|
|
266
|
+
async def process_item(item):
|
|
267
|
+
request_id = get_current_request_id()
|
|
268
|
+
logger.info(f"Processing {item}") # Log includes request_id
|
|
269
|
+
"""
|
|
270
|
+
middleware = get_global_middleware()
|
|
271
|
+
|
|
272
|
+
if func is None:
|
|
273
|
+
# Used as @with_request_id()
|
|
274
|
+
return middleware.with_request_id_decorator()
|
|
275
|
+
|
|
276
|
+
# Used as @with_request_id
|
|
277
|
+
return middleware.with_request_id_decorator()(func)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# Example log format with request ID
|
|
281
|
+
RECOMMENDED_LOG_FORMAT = "%(asctime)s [%(levelname)s] [%(request_id)s] %(name)s: %(message)s"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def configure_logging_with_request_id(
|
|
285
|
+
level: int = logging.INFO,
|
|
286
|
+
format_string: str = RECOMMENDED_LOG_FORMAT,
|
|
287
|
+
) -> None:
|
|
288
|
+
"""
|
|
289
|
+
Configure logging to include request IDs.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
level: Logging level
|
|
293
|
+
format_string: Format string (should include %(request_id)s)
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
configure_logging_with_request_id()
|
|
297
|
+
|
|
298
|
+
@with_request_id
|
|
299
|
+
async def my_function():
|
|
300
|
+
logger.info("Hello") # [INFO] [<request-id>] module: Hello
|
|
301
|
+
"""
|
|
302
|
+
# Add default value for request_id if not in context
|
|
303
|
+
old_factory = logging.getLogRecordFactory()
|
|
304
|
+
|
|
305
|
+
def record_factory(*args, **kwargs):
|
|
306
|
+
record = old_factory(*args, **kwargs)
|
|
307
|
+
if not hasattr(record, "request_id"):
|
|
308
|
+
request_id = get_current_request_id()
|
|
309
|
+
record.request_id = request_id or "no-request-id"
|
|
310
|
+
return record
|
|
311
|
+
|
|
312
|
+
logging.setLogRecordFactory(record_factory)
|
|
313
|
+
|
|
314
|
+
# Configure basic logging
|
|
315
|
+
logging.basicConfig(
|
|
316
|
+
level=level,
|
|
317
|
+
format=format_string,
|
|
318
|
+
force=True,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
logger.info("Logging configured with request ID support")
|