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
dory/core/signals.py ADDED
@@ -0,0 +1,122 @@
1
+ """
2
+ SignalHandler - Handles OS signals for graceful shutdown.
3
+
4
+ Captures SIGTERM, SIGINT, and SIGUSR1 and triggers appropriate
5
+ actions in the SDK.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import signal
11
+ import sys
12
+ from typing import Callable, Awaitable
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class SignalHandler:
18
+ """
19
+ Handles OS signals for graceful shutdown.
20
+
21
+ Signals handled:
22
+ SIGTERM: Graceful shutdown (from Kubelet)
23
+ SIGINT: Graceful shutdown (Ctrl+C for local testing)
24
+ SIGUSR1: Trigger state snapshot (for debugging)
25
+ """
26
+
27
+ def __init__(self):
28
+ self._shutdown_callback: Callable[[], Awaitable[None]] | None = None
29
+ self._snapshot_callback: Callable[[], Awaitable[None]] | None = None
30
+ self._loop: asyncio.AbstractEventLoop | None = None
31
+ self._shutdown_triggered = False
32
+
33
+ def setup(
34
+ self,
35
+ shutdown_callback: Callable[[], Awaitable[None]],
36
+ snapshot_callback: Callable[[], Awaitable[None]] | None = None,
37
+ ) -> None:
38
+ """
39
+ Setup signal handlers.
40
+
41
+ Args:
42
+ shutdown_callback: Async callback for graceful shutdown
43
+ snapshot_callback: Optional async callback for state snapshot
44
+ """
45
+ self._shutdown_callback = shutdown_callback
46
+ self._snapshot_callback = snapshot_callback
47
+ self._loop = asyncio.get_event_loop()
48
+
49
+ # Register signal handlers
50
+ if sys.platform != "win32":
51
+ # Unix signals
52
+ self._loop.add_signal_handler(
53
+ signal.SIGTERM,
54
+ self._handle_shutdown_signal,
55
+ "SIGTERM",
56
+ )
57
+ self._loop.add_signal_handler(
58
+ signal.SIGINT,
59
+ self._handle_shutdown_signal,
60
+ "SIGINT",
61
+ )
62
+ self._loop.add_signal_handler(
63
+ signal.SIGUSR1,
64
+ self._handle_snapshot_signal,
65
+ )
66
+ logger.debug("Signal handlers registered (Unix)")
67
+ else:
68
+ # Windows - limited signal support
69
+ signal.signal(signal.SIGTERM, self._handle_shutdown_signal_sync)
70
+ signal.signal(signal.SIGINT, self._handle_shutdown_signal_sync)
71
+ logger.debug("Signal handlers registered (Windows)")
72
+
73
+ def _handle_shutdown_signal(self, sig_name: str) -> None:
74
+ """Handle SIGTERM/SIGINT asynchronously."""
75
+ if self._shutdown_triggered:
76
+ logger.warning(f"Received {sig_name} but shutdown already in progress")
77
+ return
78
+
79
+ self._shutdown_triggered = True
80
+ logger.info(f"Received {sig_name}, initiating graceful shutdown")
81
+
82
+ if self._shutdown_callback and self._loop:
83
+ asyncio.ensure_future(
84
+ self._shutdown_callback(),
85
+ loop=self._loop,
86
+ )
87
+
88
+ def _handle_shutdown_signal_sync(self, signum: int, frame) -> None:
89
+ """Handle signal synchronously (Windows compatibility)."""
90
+ sig_name = signal.Signals(signum).name
91
+ self._handle_shutdown_signal(sig_name)
92
+
93
+ def _handle_snapshot_signal(self) -> None:
94
+ """Handle SIGUSR1 for debug state snapshot."""
95
+ logger.info("Received SIGUSR1, triggering state snapshot")
96
+
97
+ if self._snapshot_callback and self._loop:
98
+ asyncio.ensure_future(
99
+ self._snapshot_callback(),
100
+ loop=self._loop,
101
+ )
102
+
103
+ def remove_handlers(self) -> None:
104
+ """Remove signal handlers during shutdown."""
105
+ if self._loop and sys.platform != "win32":
106
+ try:
107
+ self._loop.remove_signal_handler(signal.SIGTERM)
108
+ self._loop.remove_signal_handler(signal.SIGINT)
109
+ self._loop.remove_signal_handler(signal.SIGUSR1)
110
+ logger.debug("Signal handlers removed")
111
+ except (ValueError, RuntimeError):
112
+ # Handler not registered or loop closed
113
+ pass
114
+
115
+ @property
116
+ def shutdown_triggered(self) -> bool:
117
+ """Check if shutdown has been triggered."""
118
+ return self._shutdown_triggered
119
+
120
+ def reset(self) -> None:
121
+ """Reset shutdown state (for testing)."""
122
+ self._shutdown_triggered = False
dory/decorators.py ADDED
@@ -0,0 +1,142 @@
1
+ """
2
+ Decorators for simplified Dory SDK integration.
3
+
4
+ Provides @stateful decorator for automatic state management,
5
+ eliminating the need to manually implement get_state() and restore_state().
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, TypeVar, Generic, Callable
10
+
11
+ T = TypeVar('T')
12
+
13
+
14
+ @dataclass
15
+ class StatefulVar(Generic[T]):
16
+ """
17
+ A stateful variable that automatically participates in state save/restore.
18
+
19
+ Usage:
20
+ from dory import stateful
21
+
22
+ class MyProcessor(BaseProcessor):
23
+ counter = stateful(0)
24
+ sessions = stateful(dict)
25
+
26
+ async def run(self):
27
+ self.counter += 1 # Automatically saved/restored
28
+ """
29
+ _default: T | Callable[[], T]
30
+ _name: str = ""
31
+ _value: T = field(default=None, repr=False)
32
+ _initialized: bool = field(default=False, repr=False)
33
+
34
+ def __set_name__(self, owner: type, name: str) -> None:
35
+ """Called when descriptor is assigned to a class attribute."""
36
+ self._name = name
37
+ # Register this stateful var with the class
38
+ if not hasattr(owner, '_stateful_vars'):
39
+ owner._stateful_vars = {}
40
+ owner._stateful_vars[name] = self
41
+
42
+ def __get__(self, obj: Any, objtype: type | None = None) -> T:
43
+ """Get the current value."""
44
+ if obj is None:
45
+ return self
46
+
47
+ # Initialize on first access
48
+ if not hasattr(obj, f'_stateful_{self._name}_value'):
49
+ default = self._default() if callable(self._default) else self._default
50
+ setattr(obj, f'_stateful_{self._name}_value', default)
51
+
52
+ return getattr(obj, f'_stateful_{self._name}_value')
53
+
54
+ def __set__(self, obj: Any, value: T) -> None:
55
+ """Set the value."""
56
+ setattr(obj, f'_stateful_{self._name}_value', value)
57
+
58
+
59
+ def stateful(default: T | Callable[[], T] = None) -> StatefulVar[T]:
60
+ """
61
+ Mark a class attribute as stateful for automatic state management.
62
+
63
+ The SDK will automatically include this attribute in get_state() output
64
+ and restore it in restore_state().
65
+
66
+ Args:
67
+ default: Default value or factory function (use factory for mutable defaults)
68
+
69
+ Usage:
70
+ class MyProcessor(BaseProcessor):
71
+ # Simple values
72
+ counter = stateful(0)
73
+ name = stateful("default")
74
+
75
+ # Mutable defaults (use factory to avoid sharing)
76
+ sessions = stateful(dict) # Same as stateful(lambda: {})
77
+ items = stateful(list) # Same as stateful(lambda: [])
78
+
79
+ Example:
80
+ from dory import DoryApp, BaseProcessor, stateful
81
+
82
+ class MyProcessor(BaseProcessor):
83
+ counter = stateful(0)
84
+ data = stateful(dict)
85
+
86
+ async def run(self):
87
+ while not self.context.is_shutdown_requested():
88
+ self.counter += 1
89
+ await asyncio.sleep(1)
90
+
91
+ # No need to implement get_state() or restore_state()!
92
+ # SDK handles it automatically.
93
+ """
94
+ return StatefulVar(_default=default)
95
+
96
+
97
+ def get_stateful_vars(obj: Any) -> dict[str, Any]:
98
+ """
99
+ Get all stateful variables from an object.
100
+
101
+ Used internally by SDK to auto-generate get_state().
102
+ """
103
+ cls = type(obj)
104
+ if not hasattr(cls, '_stateful_vars'):
105
+ return {}
106
+
107
+ result = {}
108
+ for name in cls._stateful_vars:
109
+ result[name] = getattr(obj, name)
110
+ return result
111
+
112
+
113
+ def set_stateful_vars(obj: Any, state: dict[str, Any]) -> None:
114
+ """
115
+ Set stateful variables on an object from state dict.
116
+
117
+ Used internally by SDK to auto-generate restore_state().
118
+ """
119
+ cls = type(obj)
120
+ if not hasattr(cls, '_stateful_vars'):
121
+ return
122
+
123
+ for name in cls._stateful_vars:
124
+ if name in state:
125
+ setattr(obj, name, state[name])
126
+
127
+
128
+ class StatefulMixin:
129
+ """
130
+ Mixin that provides automatic get_state() and restore_state() for @stateful vars.
131
+
132
+ If a class has @stateful decorated attributes but doesn't override
133
+ get_state()/restore_state(), this mixin provides default implementations.
134
+ """
135
+
136
+ def _get_stateful_state(self) -> dict:
137
+ """Get state from @stateful decorated attributes."""
138
+ return get_stateful_vars(self)
139
+
140
+ def _set_stateful_state(self, state: dict) -> None:
141
+ """Set state to @stateful decorated attributes."""
142
+ set_stateful_vars(self, state)
@@ -0,0 +1,117 @@
1
+ """
2
+ Error classification and handling for Dory SDK.
3
+
4
+ Provides intelligent error classification to determine appropriate recovery strategies.
5
+
6
+ Error types:
7
+ - TRANSIENT: Temporary failures (network, timeout) - retry
8
+ - PERMANENT: Logic errors (validation, not found) - don't retry
9
+ - RESOURCE: Resource exhaustion (memory, disk) - backoff + scale
10
+ - EXTERNAL: External dependency failures - circuit breaker
11
+ - LOGIC: Application logic errors - fix code
12
+
13
+ Usage:
14
+ from dory.errors import ErrorClassifier, ErrorType
15
+
16
+ classifier = ErrorClassifier()
17
+ error_type = classifier.classify(exception)
18
+
19
+ if error_type == ErrorType.TRANSIENT:
20
+ # Retry the operation
21
+ await retry_operation()
22
+ elif error_type == ErrorType.EXTERNAL:
23
+ # Use circuit breaker
24
+ await circuit_breaker.call(operation)
25
+ """
26
+
27
+ from .classification import (
28
+ ErrorType,
29
+ ErrorClassifier,
30
+ ClassificationResult,
31
+ RecoveryAction,
32
+ register_error_type,
33
+ )
34
+ from .codes import (
35
+ ErrorCode,
36
+ ErrorDomain,
37
+ ErrorCodeRegistry,
38
+ DoryError,
39
+ # Retry errors
40
+ E_RET_001,
41
+ E_RET_002,
42
+ E_RET_003,
43
+ # Circuit breaker errors
44
+ E_CBR_001,
45
+ E_CBR_002,
46
+ E_CBR_003,
47
+ # Error classification errors
48
+ E_ECL_001,
49
+ E_ECL_002,
50
+ # Golden image errors
51
+ E_GLD_001,
52
+ E_GLD_002,
53
+ E_GLD_003,
54
+ E_GLD_004,
55
+ E_GLD_005,
56
+ # Validation errors
57
+ E_VAL_001,
58
+ E_VAL_002,
59
+ E_VAL_003,
60
+ # Processing mode errors
61
+ E_MOD_001,
62
+ E_MOD_002,
63
+ E_MOD_003,
64
+ # Request tracking errors
65
+ E_REQ_001,
66
+ E_REQ_002,
67
+ # Connection errors
68
+ E_CON_001,
69
+ E_CON_002,
70
+ E_CON_003,
71
+ # State management errors
72
+ E_STA_001,
73
+ E_STA_002,
74
+ E_STA_003,
75
+ )
76
+
77
+ __all__ = [
78
+ # Classification
79
+ "ErrorType",
80
+ "ErrorClassifier",
81
+ "ClassificationResult",
82
+ "RecoveryAction",
83
+ "register_error_type",
84
+ # Error codes
85
+ "ErrorCode",
86
+ "ErrorDomain",
87
+ "ErrorCodeRegistry",
88
+ "DoryError",
89
+ # Specific error codes
90
+ "E_RET_001",
91
+ "E_RET_002",
92
+ "E_RET_003",
93
+ "E_CBR_001",
94
+ "E_CBR_002",
95
+ "E_CBR_003",
96
+ "E_ECL_001",
97
+ "E_ECL_002",
98
+ "E_GLD_001",
99
+ "E_GLD_002",
100
+ "E_GLD_003",
101
+ "E_GLD_004",
102
+ "E_GLD_005",
103
+ "E_VAL_001",
104
+ "E_VAL_002",
105
+ "E_VAL_003",
106
+ "E_MOD_001",
107
+ "E_MOD_002",
108
+ "E_MOD_003",
109
+ "E_REQ_001",
110
+ "E_REQ_002",
111
+ "E_CON_001",
112
+ "E_CON_002",
113
+ "E_CON_003",
114
+ "E_STA_001",
115
+ "E_STA_002",
116
+ "E_STA_003",
117
+ ]