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,301 @@
1
+ """
2
+ StateManager - High-level state management for migrations.
3
+
4
+ Provides unified interface for state operations across different
5
+ storage backends (ConfigMap, PVC, S3, local file).
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Any, TYPE_CHECKING
13
+
14
+ from dory.types import StateBackend
15
+ from dory.migration.serialization import StateSerializer
16
+ from dory.migration.configmap import ConfigMapStore
17
+ from dory.utils.errors import DoryStateError
18
+
19
+ if TYPE_CHECKING:
20
+ from dory.config.schema import DoryConfig
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class StateManager:
26
+ """
27
+ High-level state management for processor migrations.
28
+
29
+ Supports multiple backends:
30
+ - ConfigMap: Kubernetes ConfigMap (default, <1MB)
31
+ - PVC: Persistent Volume Claim
32
+ - S3: AWS S3 (for multi-cluster)
33
+ - Local: Local file (for testing)
34
+ """
35
+
36
+ LOCAL_STATE_PATH = "/data/dory-state.json"
37
+ LOCAL_STATE_PATH_FALLBACK = "/tmp/dory-state.json"
38
+
39
+ def __init__(
40
+ self,
41
+ backend: str | StateBackend = StateBackend.CONFIGMAP,
42
+ config: "DoryConfig | None" = None,
43
+ ):
44
+ """
45
+ Initialize state manager.
46
+
47
+ Args:
48
+ backend: Storage backend to use
49
+ config: SDK configuration
50
+ """
51
+ if isinstance(backend, str):
52
+ backend = StateBackend(backend)
53
+
54
+ self._backend = backend
55
+ self._config = config
56
+ self._serializer = StateSerializer()
57
+ self._configmap_store: ConfigMapStore | None = None
58
+
59
+ # Get namespace from environment
60
+ self._namespace = os.environ.get("POD_NAMESPACE", "default")
61
+ self._pod_name = os.environ.get("POD_NAME", "unknown")
62
+
63
+ async def save_state(
64
+ self,
65
+ processor_id: str,
66
+ state: dict[str, Any],
67
+ restart_count: int = 0,
68
+ ) -> None:
69
+ """
70
+ Save processor state.
71
+
72
+ Args:
73
+ processor_id: Processor ID
74
+ state: State dictionary to save
75
+ restart_count: Current restart count for metadata
76
+
77
+ Raises:
78
+ DoryStateError: If save fails
79
+ """
80
+ logger.debug(f"Saving state for processor {processor_id}")
81
+
82
+ # Serialize state
83
+ state_json = self._serializer.serialize(
84
+ state=state,
85
+ processor_id=processor_id,
86
+ pod_name=self._pod_name,
87
+ restart_count=restart_count,
88
+ )
89
+
90
+ # Save to backend
91
+ if self._backend == StateBackend.CONFIGMAP:
92
+ await self._save_to_configmap(processor_id, state_json)
93
+ elif self._backend == StateBackend.LOCAL:
94
+ await self._save_to_local(processor_id, state_json)
95
+ elif self._backend == StateBackend.PVC:
96
+ await self._save_to_pvc(processor_id, state_json)
97
+ elif self._backend == StateBackend.S3:
98
+ await self._save_to_s3(processor_id, state_json)
99
+ else:
100
+ raise DoryStateError(f"Unsupported state backend: {self._backend}")
101
+
102
+ logger.info(f"State saved for processor {processor_id}")
103
+
104
+ async def load_state(self, processor_id: str) -> dict[str, Any] | None:
105
+ """
106
+ Load processor state.
107
+
108
+ Args:
109
+ processor_id: Processor ID
110
+
111
+ Returns:
112
+ State dictionary, or None if no state found
113
+
114
+ Raises:
115
+ DoryStateError: If load fails
116
+ """
117
+ logger.debug(f"Loading state for processor {processor_id}")
118
+
119
+ # Load from backend
120
+ state_json: str | None = None
121
+
122
+ if self._backend == StateBackend.CONFIGMAP:
123
+ state_json = await self._load_from_configmap(processor_id)
124
+ elif self._backend == StateBackend.LOCAL:
125
+ state_json = await self._load_from_local(processor_id)
126
+ elif self._backend == StateBackend.PVC:
127
+ state_json = await self._load_from_pvc(processor_id)
128
+ elif self._backend == StateBackend.S3:
129
+ state_json = await self._load_from_s3(processor_id)
130
+ else:
131
+ raise DoryStateError(f"Unsupported state backend: {self._backend}")
132
+
133
+ if state_json is None:
134
+ logger.debug(f"No state found for processor {processor_id}")
135
+ return None
136
+
137
+ # Deserialize state
138
+ state = self._serializer.deserialize(state_json)
139
+ logger.info(f"State loaded for processor {processor_id}")
140
+ return state
141
+
142
+ async def delete_state(self, processor_id: str) -> bool:
143
+ """
144
+ Delete processor state (golden image reset).
145
+
146
+ Args:
147
+ processor_id: Processor ID
148
+
149
+ Returns:
150
+ True if state was deleted
151
+
152
+ Raises:
153
+ DoryStateError: If delete fails
154
+ """
155
+ logger.debug(f"Deleting state for processor {processor_id}")
156
+
157
+ if self._backend == StateBackend.CONFIGMAP:
158
+ return await self._delete_from_configmap(processor_id)
159
+ elif self._backend == StateBackend.LOCAL:
160
+ return await self._delete_from_local(processor_id)
161
+ elif self._backend == StateBackend.PVC:
162
+ return await self._delete_from_pvc(processor_id)
163
+ elif self._backend == StateBackend.S3:
164
+ return await self._delete_from_s3(processor_id)
165
+ else:
166
+ raise DoryStateError(f"Unsupported state backend: {self._backend}")
167
+
168
+ # =========================================================================
169
+ # ConfigMap Backend
170
+ # =========================================================================
171
+
172
+ async def _save_to_configmap(self, processor_id: str, state_json: str) -> None:
173
+ """Save state to Kubernetes ConfigMap."""
174
+ if self._configmap_store is None:
175
+ self._configmap_store = ConfigMapStore(namespace=self._namespace)
176
+
177
+ await self._configmap_store.save(processor_id, state_json)
178
+
179
+ async def _load_from_configmap(self, processor_id: str) -> str | None:
180
+ """Load state from Kubernetes ConfigMap."""
181
+ if self._configmap_store is None:
182
+ self._configmap_store = ConfigMapStore(namespace=self._namespace)
183
+
184
+ return await self._configmap_store.load(processor_id)
185
+
186
+ async def _delete_from_configmap(self, processor_id: str) -> bool:
187
+ """Delete state ConfigMap."""
188
+ if self._configmap_store is None:
189
+ self._configmap_store = ConfigMapStore(namespace=self._namespace)
190
+
191
+ return await self._configmap_store.delete(processor_id)
192
+
193
+ # =========================================================================
194
+ # Local File Backend
195
+ # =========================================================================
196
+
197
+ def _get_local_path(self, processor_id: str) -> Path:
198
+ """Get local file path for state."""
199
+ # Try /data first, fall back to /tmp
200
+ base_path = Path(self.LOCAL_STATE_PATH).parent
201
+ if not base_path.exists():
202
+ base_path = Path(self.LOCAL_STATE_PATH_FALLBACK).parent
203
+
204
+ return base_path / f"dory-state-{processor_id}.json"
205
+
206
+ async def _save_to_local(self, processor_id: str, state_json: str) -> None:
207
+ """Save state to local file."""
208
+ path = self._get_local_path(processor_id)
209
+ path.parent.mkdir(parents=True, exist_ok=True)
210
+
211
+ try:
212
+ path.write_text(state_json)
213
+ logger.debug(f"State saved to local file: {path}")
214
+ except Exception as e:
215
+ raise DoryStateError(f"Failed to save state to {path}: {e}", cause=e)
216
+
217
+ async def _load_from_local(self, processor_id: str) -> str | None:
218
+ """Load state from local file."""
219
+ path = self._get_local_path(processor_id)
220
+
221
+ if not path.exists():
222
+ return None
223
+
224
+ try:
225
+ return path.read_text()
226
+ except Exception as e:
227
+ raise DoryStateError(f"Failed to load state from {path}: {e}", cause=e)
228
+
229
+ async def _delete_from_local(self, processor_id: str) -> bool:
230
+ """Delete local state file."""
231
+ path = self._get_local_path(processor_id)
232
+
233
+ if not path.exists():
234
+ return False
235
+
236
+ try:
237
+ path.unlink()
238
+ logger.debug(f"State file deleted: {path}")
239
+ return True
240
+ except Exception as e:
241
+ raise DoryStateError(f"Failed to delete state file {path}: {e}", cause=e)
242
+
243
+ # =========================================================================
244
+ # PVC Backend
245
+ # =========================================================================
246
+
247
+ async def _save_to_pvc(self, processor_id: str, state_json: str) -> None:
248
+ """Save state to PVC mount."""
249
+ # PVC uses local file at mounted path
250
+ mount_path = self._config.state_pvc_mount if self._config else "/data"
251
+ path = Path(mount_path) / f"dory-state-{processor_id}.json"
252
+
253
+ try:
254
+ path.parent.mkdir(parents=True, exist_ok=True)
255
+ path.write_text(state_json)
256
+ logger.debug(f"State saved to PVC: {path}")
257
+ except Exception as e:
258
+ raise DoryStateError(f"Failed to save state to PVC {path}: {e}", cause=e)
259
+
260
+ async def _load_from_pvc(self, processor_id: str) -> str | None:
261
+ """Load state from PVC mount."""
262
+ mount_path = self._config.state_pvc_mount if self._config else "/data"
263
+ path = Path(mount_path) / f"dory-state-{processor_id}.json"
264
+
265
+ if not path.exists():
266
+ return None
267
+
268
+ try:
269
+ return path.read_text()
270
+ except Exception as e:
271
+ raise DoryStateError(f"Failed to load state from PVC {path}: {e}", cause=e)
272
+
273
+ async def _delete_from_pvc(self, processor_id: str) -> bool:
274
+ """Delete state from PVC."""
275
+ mount_path = self._config.state_pvc_mount if self._config else "/data"
276
+ path = Path(mount_path) / f"dory-state-{processor_id}.json"
277
+
278
+ if not path.exists():
279
+ return False
280
+
281
+ try:
282
+ path.unlink()
283
+ return True
284
+ except Exception as e:
285
+ raise DoryStateError(f"Failed to delete state from PVC {path}: {e}", cause=e)
286
+
287
+ # =========================================================================
288
+ # S3 Backend (placeholder - would need boto3)
289
+ # =========================================================================
290
+
291
+ async def _save_to_s3(self, processor_id: str, state_json: str) -> None:
292
+ """Save state to S3."""
293
+ raise DoryStateError("S3 backend not yet implemented")
294
+
295
+ async def _load_from_s3(self, processor_id: str) -> str | None:
296
+ """Load state from S3."""
297
+ raise DoryStateError("S3 backend not yet implemented")
298
+
299
+ async def _delete_from_s3(self, processor_id: str) -> bool:
300
+ """Delete state from S3."""
301
+ raise DoryStateError("S3 backend not yet implemented")
@@ -0,0 +1,23 @@
1
+ """
2
+ Dory Monitoring and Observability
3
+
4
+ OpenTelemetry integration for distributed tracing and metrics.
5
+ """
6
+
7
+ from dory.monitoring.opentelemetry import (
8
+ OpenTelemetryManager,
9
+ trace_function,
10
+ create_span,
11
+ add_span_attributes,
12
+ record_exception,
13
+ get_tracer,
14
+ )
15
+
16
+ __all__ = [
17
+ "OpenTelemetryManager",
18
+ "trace_function",
19
+ "create_span",
20
+ "add_span_attributes",
21
+ "record_exception",
22
+ "get_tracer",
23
+ ]