dory-processor-sdk 0.0.1__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 (86) hide show
  1. dory/__init__.py +101 -0
  2. dory/auth/__init__.py +10 -0
  3. dory/auth/oauth2.py +153 -0
  4. dory/auto_instrument.py +142 -0
  5. dory/cli/__init__.py +5 -0
  6. dory/cli/main.py +137 -0
  7. dory/cli/templates.py +123 -0
  8. dory/config/__init__.py +23 -0
  9. dory/config/defaults.py +24 -0
  10. dory/config/loader.py +430 -0
  11. dory/config/presets.py +73 -0
  12. dory/config/schema.py +84 -0
  13. dory/core/__init__.py +27 -0
  14. dory/core/app.py +434 -0
  15. dory/core/context.py +209 -0
  16. dory/core/lifecycle.py +214 -0
  17. dory/core/meta.py +121 -0
  18. dory/core/modes.py +479 -0
  19. dory/core/processor.py +564 -0
  20. dory/core/signals.py +122 -0
  21. dory/decorators.py +142 -0
  22. dory/edge/__init__.py +88 -0
  23. dory/edge/adaptive.py +644 -0
  24. dory/edge/detector.py +546 -0
  25. dory/edge/fencing.py +488 -0
  26. dory/edge/heartbeat.py +598 -0
  27. dory/edge/role.py +419 -0
  28. dory/errors/__init__.py +139 -0
  29. dory/errors/classification.py +362 -0
  30. dory/errors/codes.py +498 -0
  31. dory/geo/__init__.py +40 -0
  32. dory/geo/geolocalizer.py +1034 -0
  33. dory/health/__init__.py +12 -0
  34. dory/health/probes.py +210 -0
  35. dory/health/server.py +635 -0
  36. dory/k8s/__init__.py +80 -0
  37. dory/k8s/annotation_watcher.py +184 -0
  38. dory/k8s/client.py +251 -0
  39. dory/k8s/labels.py +505 -0
  40. dory/k8s/pod_metadata.py +182 -0
  41. dory/logging/__init__.py +9 -0
  42. dory/logging/logger.py +148 -0
  43. dory/metrics/__init__.py +7 -0
  44. dory/metrics/collector.py +301 -0
  45. dory/middleware/__init__.py +46 -0
  46. dory/middleware/connection_tracker.py +608 -0
  47. dory/middleware/request_id.py +325 -0
  48. dory/middleware/request_tracker.py +511 -0
  49. dory/migration/__init__.py +33 -0
  50. dory/migration/configmap.py +232 -0
  51. dory/migration/s3_store.py +594 -0
  52. dory/migration/serialization.py +135 -0
  53. dory/migration/state_manager.py +286 -0
  54. dory/migration/transfer.py +382 -0
  55. dory/monitoring/__init__.py +29 -0
  56. dory/monitoring/opentelemetry.py +489 -0
  57. dory/output/__init__.py +31 -0
  58. dory/output/envelope.py +137 -0
  59. dory/output/formatter.py +113 -0
  60. dory/output/rabbitmq.py +632 -0
  61. dory/output/routing.py +318 -0
  62. dory/output/validator.py +199 -0
  63. dory/py.typed +2 -0
  64. dory/recovery/__init__.py +60 -0
  65. dory/recovery/golden_image.py +487 -0
  66. dory/recovery/golden_snapshot.py +713 -0
  67. dory/recovery/golden_validator.py +518 -0
  68. dory/recovery/partial_recovery.py +482 -0
  69. dory/recovery/recovery_decision.py +242 -0
  70. dory/recovery/restart_detector.py +142 -0
  71. dory/recovery/state_validator.py +183 -0
  72. dory/resilience/__init__.py +45 -0
  73. dory/resilience/circuit_breaker.py +457 -0
  74. dory/resilience/retry.py +389 -0
  75. dory/simple.py +342 -0
  76. dory/types.py +68 -0
  77. dory/utils/__init__.py +31 -0
  78. dory/utils/errors.py +59 -0
  79. dory/utils/retry.py +115 -0
  80. dory/utils/timeout.py +80 -0
  81. dory_processor_sdk-0.0.1.dist-info/METADATA +424 -0
  82. dory_processor_sdk-0.0.1.dist-info/RECORD +86 -0
  83. dory_processor_sdk-0.0.1.dist-info/WHEEL +5 -0
  84. dory_processor_sdk-0.0.1.dist-info/entry_points.txt +2 -0
  85. dory_processor_sdk-0.0.1.dist-info/licenses/LICENSE +201 -0
  86. dory_processor_sdk-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,487 @@
1
+ """
2
+ Golden image reset manager.
3
+
4
+ Handles state cleanup for fresh-start recovery after
5
+ repeated failures.
6
+
7
+ Implements graduated reset levels:
8
+ - SOFT: Clear caches only
9
+ - MODERATE: Clear session state, keep persistent data
10
+ - FULL: Delete all state
11
+ - FACTORY: Full reset + clear all metadata
12
+ """
13
+
14
+ import asyncio
15
+ import logging
16
+ from enum import Enum
17
+ from typing import TYPE_CHECKING, Optional, Dict, Any, List, Callable
18
+ from dataclasses import dataclass
19
+
20
+ from dory.utils.errors import DoryStateError
21
+
22
+ if TYPE_CHECKING:
23
+ from dory.migration.state_manager import StateManager
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ class ResetLevel(Enum):
29
+ """
30
+ Graduated reset levels from least to most destructive.
31
+ """
32
+ SOFT = "soft" # Clear caches only, preserve all state
33
+ MODERATE = "moderate" # Clear session state, keep persistent data
34
+ FULL = "full" # Delete all persisted state
35
+ FACTORY = "factory" # Full reset + clear metadata, restart counts
36
+
37
+
38
+ @dataclass
39
+ class ResetResult:
40
+ """
41
+ Result of a reset operation.
42
+ """
43
+ success: bool
44
+ level: ResetLevel
45
+ processor_id: str
46
+ items_cleared: int = 0
47
+ errors: List[str] = None
48
+
49
+ def __post_init__(self):
50
+ if self.errors is None:
51
+ self.errors = []
52
+
53
+
54
+ class GoldenImageManager:
55
+ """
56
+ Manages golden image reset operations with graduated reset levels.
57
+
58
+ Reset Levels (in order of severity):
59
+ 1. SOFT: Clear caches only, preserve all state
60
+ 2. MODERATE: Clear session state, keep persistent data
61
+ 3. FULL: Delete all persisted state
62
+ 4. FACTORY: Full reset + clear metadata
63
+
64
+ Golden image reset = delete all persisted state and restart fresh.
65
+ Used when:
66
+ 1. State corruption is detected
67
+ 2. Restart count exceeds threshold
68
+ 3. Manual reset requested
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ state_manager: "StateManager",
74
+ reset_threshold: int = 3,
75
+ soft_threshold: int = 1,
76
+ moderate_threshold: int = 2,
77
+ cache_manager: Optional["CacheResetManager"] = None,
78
+ on_reset: Optional[Callable] = None,
79
+ ):
80
+ """
81
+ Initialize golden image manager.
82
+
83
+ Args:
84
+ state_manager: State manager for state deletion
85
+ reset_threshold: Restart count that triggers FULL reset
86
+ soft_threshold: Restart count that triggers SOFT reset
87
+ moderate_threshold: Restart count that triggers MODERATE reset
88
+ cache_manager: Optional cache manager for SOFT resets
89
+ on_reset: Optional callback when reset occurs
90
+ """
91
+ self._state_manager = state_manager
92
+ self._reset_threshold = reset_threshold
93
+ self._soft_threshold = soft_threshold
94
+ self._moderate_threshold = moderate_threshold
95
+ self._cache_manager = cache_manager or CacheResetManager()
96
+ self._on_reset = on_reset
97
+
98
+ # Metrics
99
+ self._reset_counts = {
100
+ ResetLevel.SOFT: 0,
101
+ ResetLevel.MODERATE: 0,
102
+ ResetLevel.FULL: 0,
103
+ ResetLevel.FACTORY: 0,
104
+ }
105
+
106
+ def should_reset(self, restart_count: int) -> bool:
107
+ """
108
+ Check if golden image reset should be triggered.
109
+
110
+ Args:
111
+ restart_count: Current restart count
112
+
113
+ Returns:
114
+ True if reset should be triggered
115
+ """
116
+ if restart_count >= self._soft_threshold:
117
+ logger.warning(
118
+ f"Restart count {restart_count} >= threshold {self._soft_threshold}, "
119
+ "recommending reset"
120
+ )
121
+ return True
122
+ return False
123
+
124
+ def determine_reset_level(
125
+ self,
126
+ restart_count: int,
127
+ state_corrupted: bool = False,
128
+ manual_factory: bool = False,
129
+ ) -> Optional[ResetLevel]:
130
+ """
131
+ Determine appropriate reset level based on conditions.
132
+
133
+ Args:
134
+ restart_count: Current restart count
135
+ state_corrupted: Whether state corruption is detected
136
+ manual_factory: Whether factory reset is manually requested
137
+
138
+ Returns:
139
+ Recommended reset level, or None if no reset needed
140
+
141
+ Logic:
142
+ - Factory reset if manually requested
143
+ - Full reset if state corrupted
144
+ - Graduated by restart count: SOFT -> MODERATE -> FULL
145
+ """
146
+ # Manual factory reset
147
+ if manual_factory:
148
+ logger.info("Factory reset manually requested")
149
+ return ResetLevel.FACTORY
150
+
151
+ # State corruption detected -> FULL reset
152
+ if state_corrupted:
153
+ logger.warning("State corruption detected, recommending FULL reset")
154
+ return ResetLevel.FULL
155
+
156
+ # Graduated by restart count
157
+ if restart_count >= self._reset_threshold:
158
+ return ResetLevel.FULL
159
+ elif restart_count >= self._moderate_threshold:
160
+ return ResetLevel.MODERATE
161
+ elif restart_count >= self._soft_threshold:
162
+ return ResetLevel.SOFT
163
+ else:
164
+ # No reset needed
165
+ return None
166
+
167
+ async def reset(
168
+ self,
169
+ processor_id: str,
170
+ level: Optional[ResetLevel] = None,
171
+ restart_count: int = 0,
172
+ ) -> ResetResult:
173
+ """
174
+ Perform golden image reset with specified or auto-determined level.
175
+
176
+ Args:
177
+ processor_id: Processor ID to reset
178
+ level: Reset level (auto-determined if None)
179
+ restart_count: Current restart count (for auto-determination)
180
+
181
+ Returns:
182
+ ResetResult with success status and details
183
+ """
184
+ # Determine level if not specified
185
+ if level is None:
186
+ level = self.determine_reset_level(restart_count)
187
+
188
+ # No reset needed
189
+ if level is None:
190
+ logger.info(f"No reset needed for processor {processor_id}")
191
+ return ResetResult(
192
+ success=True,
193
+ level=ResetLevel.SOFT,
194
+ processor_id=processor_id,
195
+ items_cleared=0,
196
+ )
197
+
198
+ logger.warning(
199
+ f"Performing {level.value.upper()} reset for processor {processor_id}"
200
+ )
201
+
202
+ # Perform reset based on level
203
+ if level == ResetLevel.SOFT:
204
+ result = await self._soft_reset(processor_id)
205
+ elif level == ResetLevel.MODERATE:
206
+ result = await self._moderate_reset(processor_id)
207
+ elif level == ResetLevel.FULL:
208
+ result = await self._full_reset(processor_id)
209
+ elif level == ResetLevel.FACTORY:
210
+ result = await self._factory_reset(processor_id)
211
+ else:
212
+ logger.error(f"Unknown reset level: {level}")
213
+ return ResetResult(
214
+ success=False,
215
+ level=level,
216
+ processor_id=processor_id,
217
+ errors=[f"Unknown reset level: {level}"],
218
+ )
219
+
220
+ # Update metrics
221
+ if result.success:
222
+ self._reset_counts[level] += 1
223
+
224
+ # Call reset callback
225
+ if self._on_reset and result.success:
226
+ try:
227
+ if asyncio.iscoroutinefunction(self._on_reset):
228
+ await self._on_reset(result)
229
+ else:
230
+ self._on_reset(result)
231
+ except Exception as e:
232
+ logger.warning(f"Reset callback failed: {e}")
233
+
234
+ return result
235
+
236
+ async def _soft_reset(self, processor_id: str) -> ResetResult:
237
+ """
238
+ SOFT reset: Clear caches only, preserve all state.
239
+
240
+ Args:
241
+ processor_id: Processor ID
242
+
243
+ Returns:
244
+ ResetResult
245
+ """
246
+ logger.info(f"Performing SOFT reset for {processor_id} (cache clear only)")
247
+
248
+ try:
249
+ cleared_count = await self._cache_manager.clear_all_caches()
250
+
251
+ return ResetResult(
252
+ success=True,
253
+ level=ResetLevel.SOFT,
254
+ processor_id=processor_id,
255
+ items_cleared=cleared_count,
256
+ )
257
+
258
+ except Exception as e:
259
+ logger.error(f"SOFT reset failed: {e}")
260
+ return ResetResult(
261
+ success=False,
262
+ level=ResetLevel.SOFT,
263
+ processor_id=processor_id,
264
+ errors=[str(e)],
265
+ )
266
+
267
+ async def _moderate_reset(self, processor_id: str) -> ResetResult:
268
+ """
269
+ MODERATE reset: Clear session state, keep persistent data.
270
+
271
+ Args:
272
+ processor_id: Processor ID
273
+
274
+ Returns:
275
+ ResetResult
276
+ """
277
+ logger.info(f"Performing MODERATE reset for {processor_id}")
278
+
279
+ errors = []
280
+ items_cleared = 0
281
+
282
+ try:
283
+ # Clear caches
284
+ cleared_count = await self._cache_manager.clear_all_caches()
285
+ items_cleared += cleared_count
286
+
287
+ # Clear session-level state (if state manager supports it)
288
+ # For now, this is a placeholder - implement based on state_manager API
289
+ # TODO: Add session-level state clearing when available
290
+
291
+ return ResetResult(
292
+ success=True,
293
+ level=ResetLevel.MODERATE,
294
+ processor_id=processor_id,
295
+ items_cleared=items_cleared,
296
+ )
297
+
298
+ except Exception as e:
299
+ logger.error(f"MODERATE reset failed: {e}")
300
+ errors.append(str(e))
301
+ return ResetResult(
302
+ success=False,
303
+ level=ResetLevel.MODERATE,
304
+ processor_id=processor_id,
305
+ items_cleared=items_cleared,
306
+ errors=errors,
307
+ )
308
+
309
+ async def _full_reset(self, processor_id: str) -> ResetResult:
310
+ """
311
+ FULL reset: Delete all persisted state.
312
+
313
+ Args:
314
+ processor_id: Processor ID
315
+
316
+ Returns:
317
+ ResetResult
318
+ """
319
+ logger.info(f"Performing FULL reset for {processor_id}")
320
+
321
+ try:
322
+ # Clear caches first
323
+ await self._cache_manager.clear_all_caches()
324
+
325
+ # Delete all state
326
+ deleted = await self._state_manager.delete_state(processor_id)
327
+
328
+ if deleted:
329
+ logger.info(f"FULL reset complete for {processor_id}")
330
+ else:
331
+ logger.info(f"No state to delete for {processor_id}")
332
+
333
+ return ResetResult(
334
+ success=True,
335
+ level=ResetLevel.FULL,
336
+ processor_id=processor_id,
337
+ items_cleared=1 if deleted else 0,
338
+ )
339
+
340
+ except DoryStateError as e:
341
+ logger.error(f"FULL reset failed: {e}")
342
+ return ResetResult(
343
+ success=False,
344
+ level=ResetLevel.FULL,
345
+ processor_id=processor_id,
346
+ errors=[str(e)],
347
+ )
348
+
349
+ async def _factory_reset(self, processor_id: str) -> ResetResult:
350
+ """
351
+ FACTORY reset: Full reset + clear all metadata and counters.
352
+
353
+ Args:
354
+ processor_id: Processor ID
355
+
356
+ Returns:
357
+ ResetResult
358
+ """
359
+ logger.warning(f"Performing FACTORY reset for {processor_id}")
360
+
361
+ errors = []
362
+ items_cleared = 0
363
+
364
+ try:
365
+ # Clear caches
366
+ cache_cleared = await self._cache_manager.clear_all_caches()
367
+ items_cleared += cache_cleared
368
+
369
+ # Delete all state
370
+ deleted = await self._state_manager.delete_state(processor_id)
371
+ if deleted:
372
+ items_cleared += 1
373
+
374
+ # TODO: Clear restart counts, metrics, snapshots
375
+ # This would require additional manager references
376
+
377
+ logger.info(f"FACTORY reset complete for {processor_id}")
378
+
379
+ return ResetResult(
380
+ success=True,
381
+ level=ResetLevel.FACTORY,
382
+ processor_id=processor_id,
383
+ items_cleared=items_cleared,
384
+ )
385
+
386
+ except Exception as e:
387
+ logger.error(f"FACTORY reset failed: {e}")
388
+ errors.append(str(e))
389
+ return ResetResult(
390
+ success=False,
391
+ level=ResetLevel.FACTORY,
392
+ processor_id=processor_id,
393
+ items_cleared=items_cleared,
394
+ errors=errors,
395
+ )
396
+
397
+ def get_reset_stats(self) -> Dict[str, int]:
398
+ """
399
+ Get reset statistics.
400
+
401
+ Returns:
402
+ Dictionary of reset counts by level
403
+ """
404
+ return {
405
+ level.value: count
406
+ for level, count in self._reset_counts.items()
407
+ }
408
+
409
+ async def reset_with_callback(
410
+ self,
411
+ processor_id: str,
412
+ pre_reset_callback=None,
413
+ post_reset_callback=None,
414
+ ) -> bool:
415
+ """
416
+ Perform golden image reset with callbacks.
417
+
418
+ Args:
419
+ processor_id: Processor ID to reset
420
+ pre_reset_callback: Async callback before reset
421
+ post_reset_callback: Async callback after reset
422
+
423
+ Returns:
424
+ True if reset was successful
425
+ """
426
+ # Pre-reset callback
427
+ if pre_reset_callback:
428
+ try:
429
+ await pre_reset_callback()
430
+ except Exception as e:
431
+ logger.error(f"Pre-reset callback failed: {e}")
432
+
433
+ # Perform reset
434
+ success = await self.reset(processor_id)
435
+
436
+ # Post-reset callback
437
+ if post_reset_callback and success:
438
+ try:
439
+ await post_reset_callback()
440
+ except Exception as e:
441
+ logger.error(f"Post-reset callback failed: {e}")
442
+
443
+ return success
444
+
445
+
446
+ class CacheResetManager:
447
+ """
448
+ Manages cache clearing during recovery.
449
+
450
+ Clears in-memory caches while preserving persisted state.
451
+ Used for lighter recovery than full golden image reset.
452
+ """
453
+
454
+ def __init__(self):
455
+ """Initialize cache reset manager."""
456
+ self._cache_clear_callbacks: list = []
457
+
458
+ def register_cache(self, clear_callback) -> None:
459
+ """
460
+ Register a cache clear callback.
461
+
462
+ Args:
463
+ clear_callback: Function to call to clear cache
464
+ """
465
+ self._cache_clear_callbacks.append(clear_callback)
466
+
467
+ async def clear_all_caches(self) -> int:
468
+ """
469
+ Clear all registered caches.
470
+
471
+ Returns:
472
+ Number of caches cleared
473
+ """
474
+ cleared = 0
475
+
476
+ for callback in self._cache_clear_callbacks:
477
+ try:
478
+ if asyncio.iscoroutinefunction(callback):
479
+ await callback()
480
+ else:
481
+ callback()
482
+ cleared += 1
483
+ except Exception as e:
484
+ logger.error(f"Cache clear failed: {e}")
485
+
486
+ logger.info(f"Cleared {cleared} caches")
487
+ return cleared