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
dory/output/routing.py ADDED
@@ -0,0 +1,318 @@
1
+ """Routing key builder utilities for topic-based message routing."""
2
+
3
+ import logging
4
+ import os
5
+ from typing import Optional
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ # Base-32 alphabet used by standard geohash encoding
10
+ _GEOHASH_ALPHABET = "0123456789bcdefghjkmnpqrstuvwxyz"
11
+
12
+
13
+ def _encode_geohash(latitude: float, longitude: float, precision: int = 9) -> str:
14
+ """Encode latitude/longitude into a geohash string.
15
+
16
+ Uses the standard base-32 geohash algorithm (no external dependency).
17
+
18
+ Args:
19
+ latitude: Latitude in decimal degrees (-90 to 90).
20
+ longitude: Longitude in decimal degrees (-180 to 180).
21
+ precision: Number of characters in the geohash (default 9).
22
+
23
+ Returns:
24
+ Geohash string of the requested precision.
25
+ """
26
+ lat_range = (-90.0, 90.0)
27
+ lon_range = (-180.0, 180.0)
28
+ bits = 0
29
+ char_index = 0
30
+ is_lon = True
31
+ result: list[str] = []
32
+
33
+ while len(result) < precision:
34
+ if is_lon:
35
+ mid = (lon_range[0] + lon_range[1]) / 2
36
+ if longitude >= mid:
37
+ char_index = (char_index << 1) | 1
38
+ lon_range = (mid, lon_range[1])
39
+ else:
40
+ char_index = char_index << 1
41
+ lon_range = (lon_range[0], mid)
42
+ else:
43
+ mid = (lat_range[0] + lat_range[1]) / 2
44
+ if latitude >= mid:
45
+ char_index = (char_index << 1) | 1
46
+ lat_range = (mid, lat_range[1])
47
+ else:
48
+ char_index = char_index << 1
49
+ lat_range = (lat_range[0], mid)
50
+
51
+ is_lon = not is_lon
52
+ bits += 1
53
+
54
+ if bits == 5:
55
+ result.append(_GEOHASH_ALPHABET[char_index])
56
+ bits = 0
57
+ char_index = 0
58
+
59
+ return "".join(result)
60
+
61
+
62
+ def _resolve_geohash(precision: int = 9) -> Optional[str]:
63
+ """Resolve geohash from environment variables.
64
+
65
+ Checks in order:
66
+ 1. ``DORY_GEOHASH`` - direct geohash string (auto-injected by orchestrator
67
+ from sensors.location_point)
68
+ 2. ``DORY_LATITUDE`` + ``DORY_LONGITUDE`` - computed from coordinates
69
+ (local development fallback)
70
+
71
+ Args:
72
+ precision: Geohash precision when computing from lat/lon (default 9).
73
+
74
+ Returns:
75
+ Geohash string, or None if not configured.
76
+ """
77
+ # Option 1: direct geohash (auto-injected by orchestrator from sensors.location_point)
78
+ geohash = os.environ.get("DORY_GEOHASH", "").strip()
79
+ if geohash:
80
+ return geohash.lower()
81
+
82
+ # Option 2: compute from lat/lon (local development fallback)
83
+ lat_str = os.environ.get("DORY_LATITUDE", "").strip()
84
+ lon_str = os.environ.get("DORY_LONGITUDE", "").strip()
85
+ if lat_str and lon_str:
86
+ try:
87
+ lat = float(lat_str)
88
+ lon = float(lon_str)
89
+ except ValueError:
90
+ logger.warning(
91
+ "DORY_LATITUDE ('%s') and DORY_LONGITUDE ('%s') "
92
+ "must be valid decimal numbers — geohash disabled",
93
+ lat_str,
94
+ lon_str,
95
+ )
96
+ return None
97
+ return _encode_geohash(lat, lon, precision)
98
+
99
+ return None
100
+
101
+
102
+ # Sentinel: distinguishes "not yet resolved" from "resolved to None"
103
+ _UNRESOLVED = object()
104
+
105
+ # Cached geohash — resolved once per process
106
+ _cached_geohash: object = _UNRESOLVED
107
+
108
+ # Cached processor_id — resolved once per process
109
+ _cached_processor_id: object = _UNRESOLVED
110
+
111
+
112
+ def _resolve_processor_id() -> Optional[str]:
113
+ """Resolve processor_id from the ``PROCESSOR_ID`` environment variable.
114
+
115
+ Returns:
116
+ Processor ID string, or None if not configured.
117
+ """
118
+ pid = os.environ.get("PROCESSOR_ID", "").strip()
119
+ if pid:
120
+ return pid
121
+ return None
122
+
123
+
124
+ def get_processor_id() -> Optional[str]:
125
+ """Get the processor_id for this deployment, resolved from environment.
126
+
127
+ The result is cached after the first call. Returns None if not configured.
128
+
129
+ In production the orchestrator auto-injects ``PROCESSOR_ID`` from
130
+ ``processors.id``. For local development you can set ``PROCESSOR_ID``
131
+ manually.
132
+
133
+ Returns:
134
+ Processor ID string, or None if not configured.
135
+ """
136
+ global _cached_processor_id
137
+ if _cached_processor_id is _UNRESOLVED:
138
+ _cached_processor_id = _resolve_processor_id()
139
+ if _cached_processor_id:
140
+ logger.info("Processor ID resolved: %s", _cached_processor_id)
141
+ else:
142
+ logger.warning(
143
+ "No processor_id configured (PROCESSOR_ID not set). "
144
+ "Routing keys will not include processor_id prefix."
145
+ )
146
+ return _cached_processor_id
147
+
148
+
149
+ def get_geohash(precision: int = 9) -> Optional[str]:
150
+ """Get the geohash for this deployment, resolved from environment.
151
+
152
+ The result is cached after the first call. Returns None if no geohash
153
+ is configured (sensor has no location_point).
154
+
155
+ In production the orchestrator auto-injects ``DORY_GEOHASH`` from the
156
+ sensor's ``location_point`` column. For local development you can set
157
+ ``DORY_GEOHASH`` or ``DORY_LATITUDE`` + ``DORY_LONGITUDE`` manually.
158
+
159
+ Args:
160
+ precision: Geohash precision when computing from lat/lon.
161
+
162
+ Returns:
163
+ Geohash string, or None if not configured.
164
+ """
165
+ global _cached_geohash
166
+ if _cached_geohash is _UNRESOLVED:
167
+ _cached_geohash = _resolve_geohash(precision)
168
+ if _cached_geohash:
169
+ logger.info("Geohash resolved: %s", _cached_geohash)
170
+ else:
171
+ logger.warning(
172
+ "No geohash configured (DORY_GEOHASH not set). "
173
+ "Routing keys will not include geohash segments."
174
+ )
175
+ return _cached_geohash
176
+
177
+
178
+ def build_routing_key_from_geo(
179
+ event_type: str,
180
+ latitude: float,
181
+ longitude: float,
182
+ *,
183
+ precision: int = 9,
184
+ segment_length: int = 1,
185
+ ) -> str:
186
+ """Build a routing key from event type and explicit geo coordinates.
187
+
188
+ Converts the latitude/longitude to a geohash and appends it as
189
+ dot-separated segments to the event type. If ``DORY_PROCESSOR_ID``
190
+ is set, it is prepended as the first segment.
191
+
192
+ Produces: ``<processor_id>.<event_type>.<geohash_segments...>`` (with processor_id)
193
+ Produces: ``<event_type>.<geohash_segments...>`` (without processor_id)
194
+
195
+ Examples:
196
+ >>> build_routing_key_from_geo("accident", 39.1, -84.5)
197
+ 'accident.d.h.z.0.6.m.b.8.e'
198
+
199
+ Args:
200
+ event_type: Event type (e.g., "accident", "detection").
201
+ latitude: Latitude in decimal degrees (-90 to 90).
202
+ longitude: Longitude in decimal degrees (-180 to 180).
203
+ precision: Geohash precision (default 9).
204
+ segment_length: Geohash characters per routing segment (default 1).
205
+
206
+ Returns:
207
+ Dot-separated routing key.
208
+
209
+ Raises:
210
+ ValueError: If event_type is empty, segment_length < 1, or
211
+ coordinates are out of range.
212
+ """
213
+ event_type = event_type.strip()
214
+ if not event_type:
215
+ raise ValueError("event_type must not be empty")
216
+ if segment_length < 1:
217
+ raise ValueError("segment_length must be >= 1")
218
+ if not (-90.0 <= latitude <= 90.0):
219
+ raise ValueError(f"latitude must be between -90 and 90, got {latitude}")
220
+ if not (-180.0 <= longitude <= 180.0):
221
+ raise ValueError(f"longitude must be between -180 and 180, got {longitude}")
222
+
223
+ geohash = _encode_geohash(latitude, longitude, precision)
224
+ segments = [
225
+ geohash[i : i + segment_length]
226
+ for i in range(0, len(geohash), segment_length)
227
+ ]
228
+ processor_id = get_processor_id()
229
+ parts = [processor_id, event_type, *segments] if processor_id else [event_type, *segments]
230
+ key = ".".join(parts)
231
+ logger.debug(
232
+ "build_routing_key_from_geo: processor_id=%s event_type=%s lat=%.6f lon=%.6f geohash=%s routing_key=%s",
233
+ processor_id,
234
+ event_type,
235
+ latitude,
236
+ longitude,
237
+ geohash,
238
+ key,
239
+ )
240
+ return key
241
+
242
+
243
+ def build_routing_key(
244
+ event_type: str,
245
+ segment_length: int = 1,
246
+ ) -> str:
247
+ """Build a routing key from just an event type.
248
+
249
+ The processor_id is automatically resolved from ``PROCESSOR_ID``
250
+ (auto-injected by the orchestrator from ``processors.id``). The geohash
251
+ is resolved from ``DORY_GEOHASH`` (auto-injected from
252
+ ``sensors.location_point``). Both prefixes are optional — if either is
253
+ missing the routing key omits that segment.
254
+
255
+ Produces (all present): ``<processor_id>.<event_type>.<geohash_segments...>``
256
+ Produces (no processor): ``<event_type>.<geohash_segments...>``
257
+ Produces (no geohash): ``<processor_id>.<event_type>``
258
+ Produces (neither): ``<event_type>``
259
+
260
+ Examples:
261
+ # With PROCESSOR_ID=abc-123 and DORY_GEOHASH=dhz06mb8e
262
+ >>> build_routing_key("accident")
263
+ 'abc-123.accident.d.h.z.0.6.m.b.8.e'
264
+
265
+ # With only DORY_GEOHASH=dhz06mb8e
266
+ >>> build_routing_key("accident")
267
+ 'accident.d.h.z.0.6.m.b.8.e'
268
+
269
+ # With neither (local dev, processor not in k8s)
270
+ >>> build_routing_key("accident")
271
+ 'accident'
272
+
273
+ Subscriber binding examples (when processor_id is in the key)::
274
+
275
+ *.accident.# -> all accidents from any processor
276
+ <pid>.accident.# -> accidents from one specific processor
277
+ *.accident.d.h.z.# -> accidents in geohash prefix "dhz"
278
+ *.*.d.h.z.0.6.# -> any event in a narrower area
279
+
280
+ Args:
281
+ event_type: Event type (e.g., "accident", "detection", "alert").
282
+ segment_length: Geohash characters per routing segment (default 1).
283
+
284
+ Returns:
285
+ Dot-separated routing key.
286
+
287
+ Raises:
288
+ ValueError: If event_type is empty or segment_length < 1.
289
+ """
290
+ event_type = event_type.strip()
291
+ if not event_type:
292
+ raise ValueError("event_type must not be empty")
293
+
294
+ if segment_length < 1:
295
+ raise ValueError("segment_length must be >= 1")
296
+
297
+ processor_id = get_processor_id()
298
+ geohash = get_geohash()
299
+
300
+ parts: list[str] = []
301
+ if processor_id:
302
+ parts.append(processor_id)
303
+ parts.append(event_type)
304
+ if geohash:
305
+ parts.extend(
306
+ geohash[i : i + segment_length]
307
+ for i in range(0, len(geohash), segment_length)
308
+ )
309
+
310
+ key = ".".join(parts)
311
+ logger.debug(
312
+ "build_routing_key: processor_id=%s event_type=%s geohash=%s routing_key=%s",
313
+ processor_id,
314
+ event_type,
315
+ geohash,
316
+ key,
317
+ )
318
+ return key
@@ -0,0 +1,199 @@
1
+ """
2
+ Envelope validator for subscriber-side schema version dispatch.
3
+
4
+ Provides version-aware message handling so subscribers can process
5
+ messages from publishers running different schema versions.
6
+
7
+ Usage:
8
+ validator = EnvelopeValidator()
9
+
10
+ # Register handlers per schema version
11
+ validator.register("0.1", handle_v0)
12
+ validator.register("1.0", handle_v1)
13
+
14
+ # Process incoming message
15
+ result = await validator.handle(raw_message_dict)
16
+ """
17
+
18
+ import logging
19
+ from typing import Any, Callable, Awaitable
20
+
21
+ from dory.output.envelope import (
22
+ MessageEnvelope,
23
+ ENVELOPE_SCHEMA_VERSION,
24
+ )
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Type for async handler functions
29
+ HandlerFunc = Callable[[MessageEnvelope], Awaitable[Any]]
30
+
31
+ # Type for sync handler functions
32
+ SyncHandlerFunc = Callable[[MessageEnvelope], Any]
33
+
34
+
35
+ class UnsupportedVersionError(Exception):
36
+ """Raised when no handler is registered for a schema version."""
37
+
38
+ def __init__(self, version: str, available: list[str]):
39
+ self.version = version
40
+ self.available = available
41
+ super().__init__(
42
+ f"No handler for schema_version={version}. "
43
+ f"Available: {available}"
44
+ )
45
+
46
+
47
+ class EnvelopeValidator:
48
+ """Subscriber-side envelope validator with version dispatch.
49
+
50
+ Validates incoming envelopes and routes to the appropriate handler
51
+ based on schema_version.
52
+
53
+ Version matching strategy:
54
+ 1. Exact match (e.g., "0.1" -> handler for "0.1")
55
+ 2. Major version fallback (e.g., "0.3" -> handler for "0.1")
56
+ 3. UnsupportedVersionError if no match
57
+
58
+ Args:
59
+ strict_version: If True, require exact version match (no fallback).
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ strict_version: bool = False,
65
+ **kwargs: Any,
66
+ ):
67
+ self._handlers: dict[str, HandlerFunc] = {}
68
+ self._sync_handlers: dict[str, SyncHandlerFunc] = {}
69
+ self._strict_version = strict_version
70
+
71
+ def register(
72
+ self,
73
+ version: str,
74
+ handler: HandlerFunc | SyncHandlerFunc,
75
+ is_async: bool = True,
76
+ ) -> None:
77
+ """Register a handler for a schema version.
78
+
79
+ Args:
80
+ version: Schema version string (e.g., "0.1", "1.0").
81
+ handler: Async or sync callable that receives a MessageEnvelope.
82
+ is_async: Whether the handler is async (default True).
83
+ """
84
+ if is_async:
85
+ self._handlers[version] = handler # type: ignore[assignment]
86
+ else:
87
+ self._sync_handlers[version] = handler # type: ignore[assignment]
88
+ logger.debug(f"Registered {'async' if is_async else 'sync'} handler for v{version}")
89
+
90
+ def unregister(self, version: str) -> None:
91
+ """Remove a handler for a schema version."""
92
+ self._handlers.pop(version, None)
93
+ self._sync_handlers.pop(version, None)
94
+
95
+ @property
96
+ def registered_versions(self) -> list[str]:
97
+ """List all registered version strings."""
98
+ versions = set(self._handlers.keys()) | set(self._sync_handlers.keys())
99
+ return sorted(versions)
100
+
101
+ def validate(self, data: dict[str, Any]) -> MessageEnvelope:
102
+ """Validate and parse raw message data into a MessageEnvelope.
103
+
104
+ Args:
105
+ data: Raw dictionary from deserialized message.
106
+
107
+ Returns:
108
+ Parsed MessageEnvelope.
109
+
110
+ Raises:
111
+ ValueError: If required envelope fields are missing.
112
+ """
113
+ # Check for envelope structure
114
+ if "schema_version" not in data:
115
+ raise ValueError("Missing required field: schema_version")
116
+ if "payload" not in data:
117
+ raise ValueError("Missing required field: payload")
118
+
119
+ envelope = MessageEnvelope.from_dict(data)
120
+
121
+ return envelope
122
+
123
+ def resolve_handler(self, version: str) -> tuple[Any, bool]:
124
+ """Find the best matching handler for a version.
125
+
126
+ Args:
127
+ version: Schema version string.
128
+
129
+ Returns:
130
+ Tuple of (handler, is_async).
131
+
132
+ Raises:
133
+ UnsupportedVersionError: If no handler matches.
134
+ """
135
+ # 1. Exact match in async handlers
136
+ if version in self._handlers:
137
+ return self._handlers[version], True
138
+
139
+ # 2. Exact match in sync handlers
140
+ if version in self._sync_handlers:
141
+ return self._sync_handlers[version], False
142
+
143
+ # 3. Major version fallback (if not strict)
144
+ if not self._strict_version:
145
+ try:
146
+ major = version.split(".")[0]
147
+ major_key = f"{major}.0"
148
+
149
+ if major_key in self._handlers:
150
+ logger.debug(
151
+ f"Using major version fallback: v{version} -> v{major_key}"
152
+ )
153
+ return self._handlers[major_key], True
154
+
155
+ if major_key in self._sync_handlers:
156
+ logger.debug(
157
+ f"Using major version fallback: v{version} -> v{major_key}"
158
+ )
159
+ return self._sync_handlers[major_key], False
160
+ except (ValueError, IndexError):
161
+ pass
162
+
163
+ raise UnsupportedVersionError(version, self.registered_versions)
164
+
165
+ async def handle(self, data: dict[str, Any]) -> Any:
166
+ """Validate an envelope and dispatch to the registered handler.
167
+
168
+ Args:
169
+ data: Raw dictionary from deserialized message.
170
+
171
+ Returns:
172
+ Result from the handler.
173
+
174
+ Raises:
175
+ ValueError: If envelope is invalid.
176
+ UnsupportedVersionError: If no handler for the version.
177
+ """
178
+ envelope = self.validate(data)
179
+ handler, is_async = self.resolve_handler(envelope.schema_version)
180
+
181
+ if is_async:
182
+ return await handler(envelope)
183
+ else:
184
+ return handler(envelope)
185
+
186
+ def can_handle(self, version: str) -> bool:
187
+ """Check if a handler exists for this version (including fallback).
188
+
189
+ Args:
190
+ version: Schema version string.
191
+
192
+ Returns:
193
+ True if a handler would be found for this version.
194
+ """
195
+ try:
196
+ self.resolve_handler(version)
197
+ return True
198
+ except UnsupportedVersionError:
199
+ return False
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
+ ]