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,462 @@
1
+ """
2
+ OpenTelemetry Integration
3
+
4
+ Provides full OpenTelemetry support for distributed tracing and metrics.
5
+
6
+ Features:
7
+ - Automatic span creation
8
+ - Trace propagation
9
+ - Metrics collection
10
+ - Context management
11
+ - Integration with request IDs
12
+
13
+ Note: Requires opentelemetry packages:
14
+ pip install opentelemetry-api opentelemetry-sdk
15
+ pip install opentelemetry-exporter-otlp
16
+ """
17
+
18
+ import logging
19
+ from contextlib import contextmanager
20
+ from functools import wraps
21
+ from typing import Any, Dict, Optional, Callable
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Try to import OpenTelemetry (optional dependency)
26
+ try:
27
+ from opentelemetry import trace
28
+ from opentelemetry.sdk.trace import TracerProvider
29
+ from opentelemetry.sdk.trace.export import (
30
+ BatchSpanProcessor,
31
+ ConsoleSpanExporter,
32
+ )
33
+ from opentelemetry.sdk.resources import Resource, SERVICE_NAME
34
+ from opentelemetry.trace import Status, StatusCode, SpanKind
35
+ from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
36
+
37
+ OTEL_AVAILABLE = True
38
+ except ImportError:
39
+ OTEL_AVAILABLE = False
40
+ logger.warning(
41
+ "OpenTelemetry not available. Install with: "
42
+ "pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp"
43
+ )
44
+
45
+
46
+ class OpenTelemetryManager:
47
+ """
48
+ Manages OpenTelemetry tracing and metrics.
49
+
50
+ Features:
51
+ - Automatic tracer setup
52
+ - Span creation and management
53
+ - Context propagation
54
+ - Metrics collection
55
+ - Export to OTLP endpoints
56
+
57
+ Usage:
58
+ # Initialize
59
+ otel = OpenTelemetryManager(service_name="my-service")
60
+ otel.initialize()
61
+
62
+ # Use decorator
63
+ @otel.trace("process_item")
64
+ async def process_item(item):
65
+ # Automatically traced
66
+ pass
67
+
68
+ # Or context manager
69
+ with otel.create_span("operation"):
70
+ # Operations here are traced
71
+ pass
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ service_name: str = "dory-processor",
77
+ service_version: str = "1.0.0",
78
+ environment: str = "production",
79
+ otlp_endpoint: Optional[str] = None,
80
+ console_export: bool = False,
81
+ enable_metrics: bool = True,
82
+ ):
83
+ """
84
+ Initialize OpenTelemetry manager.
85
+
86
+ Args:
87
+ service_name: Name of the service
88
+ service_version: Version of the service
89
+ environment: Environment (production, staging, dev)
90
+ otlp_endpoint: Optional OTLP endpoint URL
91
+ console_export: Export spans to console (for debugging)
92
+ enable_metrics: Enable metrics collection
93
+ """
94
+ if not OTEL_AVAILABLE:
95
+ raise ImportError(
96
+ "OpenTelemetry not available. Install with: "
97
+ "pip install opentelemetry-api opentelemetry-sdk"
98
+ )
99
+
100
+ self.service_name = service_name
101
+ self.service_version = service_version
102
+ self.environment = environment
103
+ self.otlp_endpoint = otlp_endpoint
104
+ self.console_export = console_export
105
+ self.enable_metrics = enable_metrics
106
+
107
+ self._tracer_provider: Optional[TracerProvider] = None
108
+ self._tracer: Optional[trace.Tracer] = None
109
+ self._initialized = False
110
+
111
+ logger.info(
112
+ f"OpenTelemetryManager created: service={service_name}, "
113
+ f"version={service_version}, env={environment}"
114
+ )
115
+
116
+ def initialize(self) -> None:
117
+ """
118
+ Initialize OpenTelemetry.
119
+
120
+ Sets up tracer provider, exporters, and processors.
121
+ """
122
+ if self._initialized:
123
+ logger.warning("OpenTelemetry already initialized")
124
+ return
125
+
126
+ # Create resource
127
+ resource = Resource.create({
128
+ SERVICE_NAME: self.service_name,
129
+ "service.version": self.service_version,
130
+ "deployment.environment": self.environment,
131
+ })
132
+
133
+ # Create tracer provider
134
+ self._tracer_provider = TracerProvider(resource=resource)
135
+
136
+ # Add console exporter if enabled
137
+ if self.console_export:
138
+ console_exporter = ConsoleSpanExporter()
139
+ console_processor = BatchSpanProcessor(console_exporter)
140
+ self._tracer_provider.add_span_processor(console_processor)
141
+ logger.info("Console span exporter added")
142
+
143
+ # Add OTLP exporter if endpoint provided
144
+ if self.otlp_endpoint:
145
+ try:
146
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
147
+
148
+ otlp_exporter = OTLPSpanExporter(endpoint=self.otlp_endpoint)
149
+ otlp_processor = BatchSpanProcessor(otlp_exporter)
150
+ self._tracer_provider.add_span_processor(otlp_processor)
151
+ logger.info(f"OTLP span exporter added: {self.otlp_endpoint}")
152
+ except ImportError:
153
+ logger.warning(
154
+ "OTLP exporter not available. Install with: "
155
+ "pip install opentelemetry-exporter-otlp"
156
+ )
157
+
158
+ # Set global tracer provider
159
+ trace.set_tracer_provider(self._tracer_provider)
160
+
161
+ # Get tracer
162
+ self._tracer = trace.get_tracer(
163
+ instrumenting_module_name=__name__,
164
+ instrumenting_library_version=self.service_version,
165
+ )
166
+
167
+ self._initialized = True
168
+ logger.info("OpenTelemetry initialized successfully")
169
+
170
+ def get_tracer(self) -> trace.Tracer:
171
+ """
172
+ Get the tracer instance.
173
+
174
+ Returns:
175
+ OpenTelemetry tracer
176
+ """
177
+ if not self._initialized:
178
+ self.initialize()
179
+ return self._tracer
180
+
181
+ @contextmanager
182
+ def create_span(
183
+ self,
184
+ name: str,
185
+ attributes: Optional[Dict[str, Any]] = None,
186
+ kind: SpanKind = SpanKind.INTERNAL,
187
+ ):
188
+ """
189
+ Create a span context manager.
190
+
191
+ Args:
192
+ name: Span name
193
+ attributes: Optional span attributes
194
+ kind: Span kind (INTERNAL, SERVER, CLIENT, etc.)
195
+
196
+ Example:
197
+ with otel.create_span("database_query", {"query": "SELECT ...")):
198
+ result = await db.execute(query)
199
+ """
200
+ if not self._initialized:
201
+ # If not initialized, just yield without tracing
202
+ yield None
203
+ return
204
+
205
+ tracer = self.get_tracer()
206
+
207
+ with tracer.start_as_current_span(name, kind=kind) as span:
208
+ # Add attributes
209
+ if attributes:
210
+ for key, value in attributes.items():
211
+ span.set_attribute(key, value)
212
+
213
+ try:
214
+ yield span
215
+ except Exception as e:
216
+ # Record exception
217
+ span.record_exception(e)
218
+ span.set_status(Status(StatusCode.ERROR, str(e)))
219
+ raise
220
+ else:
221
+ span.set_status(Status(StatusCode.OK))
222
+
223
+ def trace(
224
+ self,
225
+ name: Optional[str] = None,
226
+ attributes: Optional[Dict[str, Any]] = None,
227
+ kind: SpanKind = SpanKind.INTERNAL,
228
+ ):
229
+ """
230
+ Decorator to automatically trace a function.
231
+
232
+ Args:
233
+ name: Span name (uses function name if None)
234
+ attributes: Optional span attributes
235
+ kind: Span kind
236
+
237
+ Example:
238
+ @otel.trace("process_item")
239
+ async def process_item(item):
240
+ # Automatically traced
241
+ pass
242
+ """
243
+ def decorator(func):
244
+ span_name = name or func.__name__
245
+
246
+ @wraps(func)
247
+ async def async_wrapper(*args, **kwargs):
248
+ with self.create_span(span_name, attributes, kind):
249
+ return await func(*args, **kwargs)
250
+
251
+ @wraps(func)
252
+ def sync_wrapper(*args, **kwargs):
253
+ with self.create_span(span_name, attributes, kind):
254
+ return func(*args, **kwargs)
255
+
256
+ # Return appropriate wrapper
257
+ import asyncio
258
+ if asyncio.iscoroutinefunction(func):
259
+ return async_wrapper
260
+ else:
261
+ return sync_wrapper
262
+
263
+ return decorator
264
+
265
+ def add_span_attributes(self, attributes: Dict[str, Any]) -> None:
266
+ """
267
+ Add attributes to current span.
268
+
269
+ Args:
270
+ attributes: Attributes to add
271
+ """
272
+ if not self._initialized:
273
+ return
274
+
275
+ span = trace.get_current_span()
276
+ if span:
277
+ for key, value in attributes.items():
278
+ span.set_attribute(key, value)
279
+
280
+ def record_exception(self, exception: Exception) -> None:
281
+ """
282
+ Record an exception in current span.
283
+
284
+ Args:
285
+ exception: Exception to record
286
+ """
287
+ if not self._initialized:
288
+ return
289
+
290
+ span = trace.get_current_span()
291
+ if span:
292
+ span.record_exception(exception)
293
+ span.set_status(Status(StatusCode.ERROR, str(exception)))
294
+
295
+ def inject_context(self, headers: Dict[str, str]) -> Dict[str, str]:
296
+ """
297
+ Inject trace context into headers for propagation.
298
+
299
+ Args:
300
+ headers: HTTP headers or similar dict
301
+
302
+ Returns:
303
+ Headers with trace context injected
304
+ """
305
+ if not self._initialized:
306
+ return headers
307
+
308
+ propagator = TraceContextTextMapPropagator()
309
+ headers = dict(headers)
310
+ propagator.inject(headers)
311
+ return headers
312
+
313
+ def extract_context(self, headers: Dict[str, str]) -> None:
314
+ """
315
+ Extract trace context from headers.
316
+
317
+ Args:
318
+ headers: HTTP headers or similar dict
319
+ """
320
+ if not self._initialized:
321
+ return
322
+
323
+ propagator = TraceContextTextMapPropagator()
324
+ context = propagator.extract(headers)
325
+ # Context is automatically set as current
326
+
327
+ def shutdown(self) -> None:
328
+ """Shutdown OpenTelemetry and flush spans."""
329
+ if self._tracer_provider:
330
+ self._tracer_provider.shutdown()
331
+ logger.info("OpenTelemetry shut down")
332
+
333
+
334
+ # Global instance
335
+ _global_otel: Optional[OpenTelemetryManager] = None
336
+
337
+
338
+ def get_global_otel() -> Optional[OpenTelemetryManager]:
339
+ """Get global OpenTelemetry manager."""
340
+ return _global_otel
341
+
342
+
343
+ def initialize_otel(
344
+ service_name: str = "dory-processor",
345
+ **kwargs
346
+ ) -> OpenTelemetryManager:
347
+ """
348
+ Initialize global OpenTelemetry manager.
349
+
350
+ Args:
351
+ service_name: Service name
352
+ **kwargs: Additional arguments for OpenTelemetryManager
353
+
354
+ Returns:
355
+ Initialized OpenTelemetryManager
356
+ """
357
+ global _global_otel
358
+
359
+ if _global_otel:
360
+ logger.warning("Global OpenTelemetry already initialized")
361
+ return _global_otel
362
+
363
+ _global_otel = OpenTelemetryManager(service_name=service_name, **kwargs)
364
+ _global_otel.initialize()
365
+
366
+ logger.info(f"Global OpenTelemetry initialized for service: {service_name}")
367
+ return _global_otel
368
+
369
+
370
+ # Convenience functions using global instance
371
+
372
+ def get_tracer() -> Optional[trace.Tracer]:
373
+ """Get tracer from global instance."""
374
+ otel = get_global_otel()
375
+ return otel.get_tracer() if otel else None
376
+
377
+
378
+ def create_span(
379
+ name: str,
380
+ attributes: Optional[Dict[str, Any]] = None,
381
+ kind: SpanKind = SpanKind.INTERNAL,
382
+ ):
383
+ """Create span using global instance."""
384
+ otel = get_global_otel()
385
+ if otel:
386
+ return otel.create_span(name, attributes, kind)
387
+ else:
388
+ # Return no-op context manager
389
+ @contextmanager
390
+ def noop():
391
+ yield None
392
+ return noop()
393
+
394
+
395
+ def trace_function(
396
+ name: Optional[str] = None,
397
+ attributes: Optional[Dict[str, Any]] = None,
398
+ kind: SpanKind = SpanKind.INTERNAL,
399
+ ):
400
+ """Trace function using global instance."""
401
+ otel = get_global_otel()
402
+ if otel:
403
+ return otel.trace(name, attributes, kind)
404
+ else:
405
+ # Return no-op decorator
406
+ def decorator(func):
407
+ return func
408
+ return decorator
409
+
410
+
411
+ def add_span_attributes(attributes: Dict[str, Any]) -> None:
412
+ """Add attributes to current span using global instance."""
413
+ otel = get_global_otel()
414
+ if otel:
415
+ otel.add_span_attributes(attributes)
416
+
417
+
418
+ def record_exception(exception: Exception) -> None:
419
+ """Record exception in current span using global instance."""
420
+ otel = get_global_otel()
421
+ if otel:
422
+ otel.record_exception(exception)
423
+
424
+
425
+ # Integration with request IDs
426
+
427
+ def integrate_with_request_id():
428
+ """
429
+ Integrate OpenTelemetry with request ID middleware.
430
+
431
+ Automatically adds request IDs as span attributes.
432
+ """
433
+ try:
434
+ from dory.middleware.request_id import get_current_request_id
435
+
436
+ otel = get_global_otel()
437
+ if not otel:
438
+ logger.warning("OpenTelemetry not initialized")
439
+ return
440
+
441
+ # Monkey patch create_span to add request ID
442
+ original_create_span = otel.create_span
443
+
444
+ @contextmanager
445
+ def create_span_with_request_id(name, attributes=None, kind=SpanKind.INTERNAL):
446
+ # Get current request ID
447
+ request_id = get_current_request_id()
448
+
449
+ # Add to attributes
450
+ attrs = attributes or {}
451
+ if request_id:
452
+ attrs["request.id"] = request_id
453
+
454
+ with original_create_span(name, attrs, kind) as span:
455
+ yield span
456
+
457
+ otel.create_span = create_span_with_request_id
458
+
459
+ logger.info("OpenTelemetry integrated with request ID middleware")
460
+
461
+ except ImportError:
462
+ logger.warning("Request ID middleware not available for integration")
dory/py.typed ADDED
@@ -0,0 +1,2 @@
1
+ # Marker file for PEP 561
2
+ # This file indicates that the dory package supports type checking
@@ -0,0 +1,60 @@
1
+ """Recovery and fault handling modules."""
2
+
3
+ from dory.recovery.restart_detector import RestartDetector, RestartInfo
4
+ from dory.recovery.state_validator import StateValidator
5
+ from dory.recovery.golden_image import GoldenImageManager, ResetLevel, ResetResult, CacheResetManager
6
+ from dory.recovery.recovery_decision import RecoveryDecisionMaker, RecoveryDecision
7
+ from dory.recovery.golden_snapshot import (
8
+ GoldenSnapshotManager,
9
+ Snapshot,
10
+ SnapshotMetadata,
11
+ SnapshotStorageError,
12
+ SnapshotValidationError,
13
+ SnapshotFormat,
14
+ )
15
+ from dory.recovery.golden_validator import (
16
+ GoldenValidator,
17
+ ValidationResult,
18
+ ValidationIssue,
19
+ ValidationSeverity,
20
+ )
21
+ from dory.recovery.partial_recovery import (
22
+ PartialRecoveryManager,
23
+ RecoveryResult as PartialRecoveryResult,
24
+ FieldRecovery,
25
+ FieldStatus,
26
+ numeric_recovery_strategy,
27
+ string_recovery_strategy,
28
+ list_recovery_strategy,
29
+ dict_recovery_strategy,
30
+ )
31
+
32
+ __all__ = [
33
+ "RestartDetector",
34
+ "RestartInfo",
35
+ "StateValidator",
36
+ "GoldenImageManager",
37
+ "ResetLevel",
38
+ "ResetResult",
39
+ "CacheResetManager",
40
+ "RecoveryDecisionMaker",
41
+ "RecoveryDecision",
42
+ "GoldenSnapshotManager",
43
+ "Snapshot",
44
+ "SnapshotMetadata",
45
+ "SnapshotStorageError",
46
+ "SnapshotValidationError",
47
+ "SnapshotFormat",
48
+ "GoldenValidator",
49
+ "ValidationResult",
50
+ "ValidationIssue",
51
+ "ValidationSeverity",
52
+ "PartialRecoveryManager",
53
+ "PartialRecoveryResult",
54
+ "FieldRecovery",
55
+ "FieldStatus",
56
+ "numeric_recovery_strategy",
57
+ "string_recovery_strategy",
58
+ "list_recovery_strategy",
59
+ "dict_recovery_strategy",
60
+ ]