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.
Files changed (69) hide show
  1. dory/__init__.py +70 -0
  2. dory/auto_instrument.py +142 -0
  3. dory/cli/__init__.py +5 -0
  4. dory/cli/main.py +290 -0
  5. dory/cli/templates.py +333 -0
  6. dory/config/__init__.py +23 -0
  7. dory/config/defaults.py +50 -0
  8. dory/config/loader.py +361 -0
  9. dory/config/presets.py +325 -0
  10. dory/config/schema.py +152 -0
  11. dory/core/__init__.py +27 -0
  12. dory/core/app.py +404 -0
  13. dory/core/context.py +209 -0
  14. dory/core/lifecycle.py +214 -0
  15. dory/core/meta.py +121 -0
  16. dory/core/modes.py +479 -0
  17. dory/core/processor.py +654 -0
  18. dory/core/signals.py +122 -0
  19. dory/decorators.py +142 -0
  20. dory/errors/__init__.py +117 -0
  21. dory/errors/classification.py +362 -0
  22. dory/errors/codes.py +495 -0
  23. dory/health/__init__.py +10 -0
  24. dory/health/probes.py +210 -0
  25. dory/health/server.py +306 -0
  26. dory/k8s/__init__.py +11 -0
  27. dory/k8s/annotation_watcher.py +184 -0
  28. dory/k8s/client.py +251 -0
  29. dory/k8s/pod_metadata.py +182 -0
  30. dory/logging/__init__.py +9 -0
  31. dory/logging/logger.py +175 -0
  32. dory/metrics/__init__.py +7 -0
  33. dory/metrics/collector.py +301 -0
  34. dory/middleware/__init__.py +36 -0
  35. dory/middleware/connection_tracker.py +608 -0
  36. dory/middleware/request_id.py +321 -0
  37. dory/middleware/request_tracker.py +501 -0
  38. dory/migration/__init__.py +11 -0
  39. dory/migration/configmap.py +260 -0
  40. dory/migration/serialization.py +167 -0
  41. dory/migration/state_manager.py +301 -0
  42. dory/monitoring/__init__.py +23 -0
  43. dory/monitoring/opentelemetry.py +462 -0
  44. dory/py.typed +2 -0
  45. dory/recovery/__init__.py +60 -0
  46. dory/recovery/golden_image.py +480 -0
  47. dory/recovery/golden_snapshot.py +561 -0
  48. dory/recovery/golden_validator.py +518 -0
  49. dory/recovery/partial_recovery.py +479 -0
  50. dory/recovery/recovery_decision.py +242 -0
  51. dory/recovery/restart_detector.py +142 -0
  52. dory/recovery/state_validator.py +187 -0
  53. dory/resilience/__init__.py +45 -0
  54. dory/resilience/circuit_breaker.py +454 -0
  55. dory/resilience/retry.py +389 -0
  56. dory/sidecar/__init__.py +6 -0
  57. dory/sidecar/main.py +75 -0
  58. dory/sidecar/server.py +329 -0
  59. dory/simple.py +342 -0
  60. dory/types.py +75 -0
  61. dory/utils/__init__.py +25 -0
  62. dory/utils/errors.py +59 -0
  63. dory/utils/retry.py +115 -0
  64. dory/utils/timeout.py +80 -0
  65. dory_sdk-2.1.0.dist-info/METADATA +663 -0
  66. dory_sdk-2.1.0.dist-info/RECORD +69 -0
  67. dory_sdk-2.1.0.dist-info/WHEEL +5 -0
  68. dory_sdk-2.1.0.dist-info/entry_points.txt +3 -0
  69. 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")