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/types.py ADDED
@@ -0,0 +1,75 @@
1
+ """
2
+ Type definitions for Dory SDK.
3
+
4
+ This module contains all type hints and type aliases used across the SDK.
5
+ No application-specific types should be defined here.
6
+ """
7
+
8
+ from typing import TypeVar, Callable, Awaitable, Any, Protocol
9
+ from enum import Enum, auto
10
+
11
+
12
+ class LifecycleState(Enum):
13
+ """States in the processor lifecycle state machine."""
14
+
15
+ CREATED = auto() # Instance created, not yet started
16
+ STARTING = auto() # startup() in progress
17
+ RUNNING = auto() # run() in progress
18
+ SHUTTING_DOWN = auto() # shutdown() in progress
19
+ STOPPED = auto() # Gracefully stopped
20
+ FAILED = auto() # Error occurred
21
+
22
+
23
+ class StateBackend(Enum):
24
+ """Supported state storage backends."""
25
+
26
+ CONFIGMAP = "configmap" # Kubernetes ConfigMap (default, <1MB)
27
+ PVC = "pvc" # Persistent Volume Claim
28
+ S3 = "s3" # AWS S3 (for multi-cluster)
29
+ LOCAL = "local" # Local file (for testing)
30
+
31
+
32
+ class LogFormat(Enum):
33
+ """Log output formats."""
34
+
35
+ JSON = "json" # Structured JSON (default, for production)
36
+ TEXT = "text" # Human-readable text (for local dev)
37
+
38
+
39
+ class RecoveryStrategy(Enum):
40
+ """Recovery strategies after fault detection."""
41
+
42
+ RESTORE_STATE = "restore_state" # Try to restore from checkpoint
43
+ GOLDEN_IMAGE = "golden_image" # Start fresh, discard state
44
+ GOLDEN_WITH_BACKOFF = "golden_backoff" # Start fresh with delay
45
+
46
+
47
+ class FaultType(Enum):
48
+ """Types of faults detected by SDK."""
49
+
50
+ CLEAN_EXIT = "clean_exit" # Normal termination
51
+ UNEXPECTED_CRASH = "crash" # Exit code non-zero
52
+ HUNG_PROCESS = "hung" # Liveness probe timeout
53
+ STATE_CORRUPTION = "state_corrupt" # State validation failed
54
+ STARTUP_FAILURE = "startup_fail" # Startup threw exception
55
+ HEALTH_CHECK_FAILURE = "health_fail" # Health endpoint returned error
56
+
57
+
58
+ # Type aliases
59
+ StateDict = dict[str, Any]
60
+ ConfigDict = dict[str, Any]
61
+ MetadataDict = dict[str, str]
62
+
63
+ # Callback types
64
+ ShutdownCallback = Callable[[], Awaitable[None]]
65
+ StateCallback = Callable[[], StateDict]
66
+
67
+
68
+ class ProcessorProtocol(Protocol):
69
+ """Protocol defining the processor interface."""
70
+
71
+ async def startup(self) -> None: ...
72
+ async def run(self) -> None: ...
73
+ async def shutdown(self) -> None: ...
74
+ def get_state(self) -> StateDict: ...
75
+ async def restore_state(self, state: StateDict) -> None: ...
dory/utils/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """Utility modules for Dory SDK."""
2
+
3
+ from dory.utils.errors import (
4
+ DoryError,
5
+ DoryStartupError,
6
+ DoryShutdownError,
7
+ DoryStateError,
8
+ DoryConfigError,
9
+ DoryK8sError,
10
+ )
11
+ from dory.utils.retry import retry_async, RetryConfig
12
+ from dory.utils.timeout import timeout_async, TimeoutConfig
13
+
14
+ __all__ = [
15
+ "DoryError",
16
+ "DoryStartupError",
17
+ "DoryShutdownError",
18
+ "DoryStateError",
19
+ "DoryConfigError",
20
+ "DoryK8sError",
21
+ "retry_async",
22
+ "RetryConfig",
23
+ "timeout_async",
24
+ "TimeoutConfig",
25
+ ]
dory/utils/errors.py ADDED
@@ -0,0 +1,59 @@
1
+ """
2
+ Dory SDK Exception Classes.
3
+
4
+ All SDK-specific exceptions inherit from DoryError for easy catching.
5
+ """
6
+
7
+
8
+ class DoryError(Exception):
9
+ """Base exception for all Dory SDK errors."""
10
+
11
+ def __init__(self, message: str, cause: Exception | None = None):
12
+ super().__init__(message)
13
+ self.message = message
14
+ self.cause = cause
15
+
16
+ def __str__(self) -> str:
17
+ if self.cause:
18
+ return f"{self.message}: {self.cause}"
19
+ return self.message
20
+
21
+
22
+ class DoryStartupError(DoryError):
23
+ """Raised when processor startup fails."""
24
+ pass
25
+
26
+
27
+ class DoryShutdownError(DoryError):
28
+ """Raised when processor shutdown fails or times out."""
29
+ pass
30
+
31
+
32
+ class DoryStateError(DoryError):
33
+ """Raised when state operations fail (snapshot, restore, validation)."""
34
+ pass
35
+
36
+
37
+ class DoryConfigError(DoryError):
38
+ """Raised when configuration is invalid or missing."""
39
+ pass
40
+
41
+
42
+ class DoryK8sError(DoryError):
43
+ """Raised when Kubernetes API operations fail."""
44
+ pass
45
+
46
+
47
+ class DoryHealthError(DoryError):
48
+ """Raised when health check operations fail."""
49
+ pass
50
+
51
+
52
+ class DoryTimeoutError(DoryError):
53
+ """Raised when an operation times out."""
54
+ pass
55
+
56
+
57
+ class DoryValidationError(DoryError):
58
+ """Raised when validation fails (state schema, config, etc.)."""
59
+ pass
dory/utils/retry.py ADDED
@@ -0,0 +1,115 @@
1
+ """
2
+ Retry utilities with exponential backoff.
3
+ """
4
+
5
+ import asyncio
6
+ import functools
7
+ import random
8
+ from dataclasses import dataclass, field
9
+ from typing import Callable, TypeVar, ParamSpec, Awaitable
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ P = ParamSpec("P")
15
+ T = TypeVar("T")
16
+
17
+
18
+ @dataclass
19
+ class RetryConfig:
20
+ """Configuration for retry behavior."""
21
+
22
+ max_attempts: int = 3
23
+ base_delay: float = 1.0 # seconds
24
+ max_delay: float = 60.0 # seconds
25
+ exponential_base: float = 2.0
26
+ jitter: bool = True
27
+ retryable_exceptions: tuple[type[Exception], ...] = field(
28
+ default_factory=lambda: (Exception,)
29
+ )
30
+
31
+
32
+ def calculate_delay(
33
+ attempt: int,
34
+ config: RetryConfig,
35
+ ) -> float:
36
+ """Calculate delay for next retry with exponential backoff."""
37
+ delay = config.base_delay * (config.exponential_base ** (attempt - 1))
38
+ delay = min(delay, config.max_delay)
39
+
40
+ if config.jitter:
41
+ # Add random jitter (0-25% of delay)
42
+ delay = delay * (1 + random.uniform(0, 0.25))
43
+
44
+ return delay
45
+
46
+
47
+ async def retry_async(
48
+ func: Callable[P, Awaitable[T]],
49
+ *args: P.args,
50
+ config: RetryConfig | None = None,
51
+ **kwargs: P.kwargs,
52
+ ) -> T:
53
+ """
54
+ Execute an async function with retry logic.
55
+
56
+ Args:
57
+ func: Async function to execute
58
+ *args: Positional arguments for func
59
+ config: Retry configuration (uses defaults if None)
60
+ **kwargs: Keyword arguments for func
61
+
62
+ Returns:
63
+ Result of func
64
+
65
+ Raises:
66
+ Last exception if all retries fail
67
+ """
68
+ if config is None:
69
+ config = RetryConfig()
70
+
71
+ last_exception: Exception | None = None
72
+
73
+ for attempt in range(1, config.max_attempts + 1):
74
+ try:
75
+ return await func(*args, **kwargs)
76
+ except config.retryable_exceptions as e:
77
+ last_exception = e
78
+
79
+ if attempt == config.max_attempts:
80
+ logger.warning(
81
+ f"Retry exhausted after {attempt} attempts: {e}"
82
+ )
83
+ break
84
+
85
+ delay = calculate_delay(attempt, config)
86
+ logger.debug(
87
+ f"Retry attempt {attempt}/{config.max_attempts} failed: {e}. "
88
+ f"Retrying in {delay:.2f}s"
89
+ )
90
+ await asyncio.sleep(delay)
91
+
92
+ if last_exception:
93
+ raise last_exception
94
+
95
+ # Should never reach here, but satisfy type checker
96
+ raise RuntimeError("Retry logic error")
97
+
98
+
99
+ def with_retry(
100
+ config: RetryConfig | None = None,
101
+ ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
102
+ """
103
+ Decorator to add retry logic to async functions.
104
+
105
+ Usage:
106
+ @with_retry(RetryConfig(max_attempts=5))
107
+ async def my_flaky_function():
108
+ ...
109
+ """
110
+ def decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
111
+ @functools.wraps(func)
112
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
113
+ return await retry_async(func, *args, config=config, **kwargs)
114
+ return wrapper
115
+ return decorator
dory/utils/timeout.py ADDED
@@ -0,0 +1,80 @@
1
+ """
2
+ Timeout utilities for async operations.
3
+ """
4
+
5
+ import asyncio
6
+ import functools
7
+ from dataclasses import dataclass
8
+ from typing import Callable, TypeVar, ParamSpec, Awaitable
9
+
10
+ from dory.utils.errors import DoryTimeoutError
11
+
12
+ P = ParamSpec("P")
13
+ T = TypeVar("T")
14
+
15
+
16
+ @dataclass
17
+ class TimeoutConfig:
18
+ """Configuration for timeout behavior."""
19
+
20
+ timeout_seconds: float
21
+ error_message: str | None = None
22
+
23
+
24
+ async def timeout_async(
25
+ func: Callable[P, Awaitable[T]],
26
+ *args: P.args,
27
+ timeout_seconds: float,
28
+ error_message: str | None = None,
29
+ **kwargs: P.kwargs,
30
+ ) -> T:
31
+ """
32
+ Execute an async function with a timeout.
33
+
34
+ Args:
35
+ func: Async function to execute
36
+ *args: Positional arguments for func
37
+ timeout_seconds: Maximum time to wait
38
+ error_message: Custom error message on timeout
39
+ **kwargs: Keyword arguments for func
40
+
41
+ Returns:
42
+ Result of func
43
+
44
+ Raises:
45
+ DoryTimeoutError: If function doesn't complete within timeout
46
+ """
47
+ try:
48
+ return await asyncio.wait_for(
49
+ func(*args, **kwargs),
50
+ timeout=timeout_seconds,
51
+ )
52
+ except asyncio.TimeoutError:
53
+ msg = error_message or f"Operation timed out after {timeout_seconds}s"
54
+ raise DoryTimeoutError(msg)
55
+
56
+
57
+ def with_timeout(
58
+ timeout_seconds: float,
59
+ error_message: str | None = None,
60
+ ) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
61
+ """
62
+ Decorator to add timeout to async functions.
63
+
64
+ Usage:
65
+ @with_timeout(30, "Startup took too long")
66
+ async def startup():
67
+ ...
68
+ """
69
+ def decorator(func: Callable[P, Awaitable[T]]) -> Callable[P, Awaitable[T]]:
70
+ @functools.wraps(func)
71
+ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
72
+ return await timeout_async(
73
+ func,
74
+ *args,
75
+ timeout_seconds=timeout_seconds,
76
+ error_message=error_message,
77
+ **kwargs,
78
+ )
79
+ return wrapper
80
+ return decorator