dory-sdk 2.1.2__tar.gz

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 (87) hide show
  1. dory_sdk-2.1.2/PKG-INFO +663 -0
  2. dory_sdk-2.1.2/README.md +614 -0
  3. dory_sdk-2.1.2/pyproject.toml +175 -0
  4. dory_sdk-2.1.2/setup.cfg +4 -0
  5. dory_sdk-2.1.2/src/dory/__init__.py +70 -0
  6. dory_sdk-2.1.2/src/dory/auto_instrument.py +142 -0
  7. dory_sdk-2.1.2/src/dory/cli/__init__.py +5 -0
  8. dory_sdk-2.1.2/src/dory/cli/main.py +290 -0
  9. dory_sdk-2.1.2/src/dory/cli/templates.py +333 -0
  10. dory_sdk-2.1.2/src/dory/config/__init__.py +23 -0
  11. dory_sdk-2.1.2/src/dory/config/defaults.py +50 -0
  12. dory_sdk-2.1.2/src/dory/config/loader.py +361 -0
  13. dory_sdk-2.1.2/src/dory/config/presets.py +325 -0
  14. dory_sdk-2.1.2/src/dory/config/schema.py +152 -0
  15. dory_sdk-2.1.2/src/dory/core/__init__.py +27 -0
  16. dory_sdk-2.1.2/src/dory/core/app.py +404 -0
  17. dory_sdk-2.1.2/src/dory/core/context.py +209 -0
  18. dory_sdk-2.1.2/src/dory/core/lifecycle.py +214 -0
  19. dory_sdk-2.1.2/src/dory/core/meta.py +121 -0
  20. dory_sdk-2.1.2/src/dory/core/modes.py +479 -0
  21. dory_sdk-2.1.2/src/dory/core/processor.py +654 -0
  22. dory_sdk-2.1.2/src/dory/core/signals.py +122 -0
  23. dory_sdk-2.1.2/src/dory/decorators.py +142 -0
  24. dory_sdk-2.1.2/src/dory/errors/__init__.py +117 -0
  25. dory_sdk-2.1.2/src/dory/errors/classification.py +362 -0
  26. dory_sdk-2.1.2/src/dory/errors/codes.py +495 -0
  27. dory_sdk-2.1.2/src/dory/health/__init__.py +10 -0
  28. dory_sdk-2.1.2/src/dory/health/probes.py +210 -0
  29. dory_sdk-2.1.2/src/dory/health/server.py +306 -0
  30. dory_sdk-2.1.2/src/dory/k8s/__init__.py +11 -0
  31. dory_sdk-2.1.2/src/dory/k8s/annotation_watcher.py +184 -0
  32. dory_sdk-2.1.2/src/dory/k8s/client.py +251 -0
  33. dory_sdk-2.1.2/src/dory/k8s/pod_metadata.py +182 -0
  34. dory_sdk-2.1.2/src/dory/logging/__init__.py +9 -0
  35. dory_sdk-2.1.2/src/dory/logging/logger.py +175 -0
  36. dory_sdk-2.1.2/src/dory/metrics/__init__.py +7 -0
  37. dory_sdk-2.1.2/src/dory/metrics/collector.py +301 -0
  38. dory_sdk-2.1.2/src/dory/middleware/__init__.py +36 -0
  39. dory_sdk-2.1.2/src/dory/middleware/connection_tracker.py +608 -0
  40. dory_sdk-2.1.2/src/dory/middleware/request_id.py +321 -0
  41. dory_sdk-2.1.2/src/dory/middleware/request_tracker.py +501 -0
  42. dory_sdk-2.1.2/src/dory/migration/__init__.py +11 -0
  43. dory_sdk-2.1.2/src/dory/migration/configmap.py +260 -0
  44. dory_sdk-2.1.2/src/dory/migration/serialization.py +167 -0
  45. dory_sdk-2.1.2/src/dory/migration/state_manager.py +301 -0
  46. dory_sdk-2.1.2/src/dory/monitoring/__init__.py +23 -0
  47. dory_sdk-2.1.2/src/dory/monitoring/opentelemetry.py +462 -0
  48. dory_sdk-2.1.2/src/dory/py.typed +2 -0
  49. dory_sdk-2.1.2/src/dory/recovery/__init__.py +60 -0
  50. dory_sdk-2.1.2/src/dory/recovery/golden_image.py +480 -0
  51. dory_sdk-2.1.2/src/dory/recovery/golden_snapshot.py +561 -0
  52. dory_sdk-2.1.2/src/dory/recovery/golden_validator.py +518 -0
  53. dory_sdk-2.1.2/src/dory/recovery/partial_recovery.py +479 -0
  54. dory_sdk-2.1.2/src/dory/recovery/recovery_decision.py +242 -0
  55. dory_sdk-2.1.2/src/dory/recovery/restart_detector.py +142 -0
  56. dory_sdk-2.1.2/src/dory/recovery/state_validator.py +187 -0
  57. dory_sdk-2.1.2/src/dory/resilience/__init__.py +45 -0
  58. dory_sdk-2.1.2/src/dory/resilience/circuit_breaker.py +454 -0
  59. dory_sdk-2.1.2/src/dory/resilience/retry.py +389 -0
  60. dory_sdk-2.1.2/src/dory/sidecar/__init__.py +6 -0
  61. dory_sdk-2.1.2/src/dory/sidecar/main.py +75 -0
  62. dory_sdk-2.1.2/src/dory/sidecar/server.py +329 -0
  63. dory_sdk-2.1.2/src/dory/simple.py +342 -0
  64. dory_sdk-2.1.2/src/dory/types.py +75 -0
  65. dory_sdk-2.1.2/src/dory/utils/__init__.py +25 -0
  66. dory_sdk-2.1.2/src/dory/utils/errors.py +59 -0
  67. dory_sdk-2.1.2/src/dory/utils/retry.py +115 -0
  68. dory_sdk-2.1.2/src/dory/utils/timeout.py +80 -0
  69. dory_sdk-2.1.2/src/dory_sdk.egg-info/PKG-INFO +663 -0
  70. dory_sdk-2.1.2/src/dory_sdk.egg-info/SOURCES.txt +85 -0
  71. dory_sdk-2.1.2/src/dory_sdk.egg-info/dependency_links.txt +1 -0
  72. dory_sdk-2.1.2/src/dory_sdk.egg-info/entry_points.txt +3 -0
  73. dory_sdk-2.1.2/src/dory_sdk.egg-info/requires.txt +32 -0
  74. dory_sdk-2.1.2/src/dory_sdk.egg-info/top_level.txt +1 -0
  75. dory_sdk-2.1.2/tests/test_circuit_breaker.py +410 -0
  76. dory_sdk-2.1.2/tests/test_config.py +148 -0
  77. dory_sdk-2.1.2/tests/test_decorators.py +245 -0
  78. dory_sdk-2.1.2/tests/test_error_classification.py +308 -0
  79. dory_sdk-2.1.2/tests/test_error_codes.py +219 -0
  80. dory_sdk-2.1.2/tests/test_golden_snapshot.py +308 -0
  81. dory_sdk-2.1.2/tests/test_health.py +334 -0
  82. dory_sdk-2.1.2/tests/test_processor.py +178 -0
  83. dory_sdk-2.1.2/tests/test_recovery.py +230 -0
  84. dory_sdk-2.1.2/tests/test_request_tracker.py +209 -0
  85. dory_sdk-2.1.2/tests/test_retry.py +301 -0
  86. dory_sdk-2.1.2/tests/test_serialization.py +149 -0
  87. dory_sdk-2.1.2/tests/test_sidecar.py +153 -0
@@ -0,0 +1,663 @@
1
+ Metadata-Version: 2.4
2
+ Name: dory-sdk
3
+ Version: 2.1.2
4
+ Summary: Python SDK for building stateful processors with zero-downtime migration, auto-initialization, and smart instrumentation
5
+ Author-email: Dory Team <dory@example.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/example/dory-sdk
8
+ Project-URL: Documentation, https://dory-sdk.readthedocs.io
9
+ Project-URL: Repository, https://github.com/example/dory-sdk
10
+ Project-URL: Issues, https://github.com/example/dory-sdk/issues
11
+ Keywords: kubernetes,stateful,migration,orchestration,sdk
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Distributed Computing
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Description-Content-Type: text/markdown
25
+ Requires-Dist: aiohttp>=3.8.0
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: PyYAML>=6.0
28
+ Provides-Extra: kubernetes
29
+ Requires-Dist: kubernetes>=28.0.0; extra == "kubernetes"
30
+ Provides-Extra: s3
31
+ Requires-Dist: boto3>=1.28.0; extra == "s3"
32
+ Provides-Extra: tracing
33
+ Requires-Dist: opentelemetry-api>=1.20.0; extra == "tracing"
34
+ Requires-Dist: opentelemetry-sdk>=1.20.0; extra == "tracing"
35
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0; extra == "tracing"
36
+ Provides-Extra: resilience
37
+ Provides-Extra: monitoring
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
41
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
42
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
43
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
44
+ Requires-Dist: black>=23.0.0; extra == "dev"
45
+ Provides-Extra: production
46
+ Requires-Dist: dory-sdk[kubernetes,tracing]; extra == "production"
47
+ Provides-Extra: all
48
+ Requires-Dist: dory-sdk[dev,kubernetes,monitoring,resilience,s3,tracing]; extra == "all"
49
+
50
+ # Dory SDK for Python
51
+
52
+ A Python SDK for building stateful processors with **zero-downtime migration**, **graceful shutdown**, and **state transfer** on Kubernetes.
53
+
54
+ ## What Does This SDK Do For You?
55
+
56
+ | Feature | Without Dory SDK | With Dory SDK |
57
+ |---------|------------------|---------------|
58
+ | Pod shutdown | App killed, state lost | State saved automatically, restored on new pod |
59
+ | Node maintenance | Downtime, manual intervention | Zero-downtime migration to new node |
60
+ | Crash recovery | Start from scratch | Resume from last checkpoint |
61
+ | Health monitoring | DIY implementation | Built-in `/healthz`, `/ready`, `/metrics` |
62
+
63
+ ---
64
+
65
+ ## Quick Start (Choose Your Style)
66
+
67
+ ### Option A: Minimal (7 lines)
68
+
69
+ ```python
70
+ from dory import DoryApp, BaseProcessor, stateful
71
+
72
+ class MyApp(BaseProcessor):
73
+ counter = stateful(0) # Auto-saved and restored!
74
+
75
+ async def run(self):
76
+ async for _ in self.run_loop(interval=1):
77
+ self.counter += 1
78
+
79
+ if __name__ == "__main__":
80
+ DoryApp().run(MyApp)
81
+ ```
82
+
83
+ ### Option B: Function-Based (6 lines)
84
+
85
+ ```python
86
+ from dory.simple import processor, state
87
+
88
+ counter = state(0)
89
+
90
+ @processor
91
+ async def main(ctx):
92
+ async for _ in ctx.run_loop(interval=1):
93
+ counter.value += 1
94
+ ```
95
+
96
+ ### Option C: Full Control
97
+
98
+ ```python
99
+ from dory import DoryApp, BaseProcessor, ExecutionContext
100
+
101
+ class MyApp(BaseProcessor):
102
+ def __init__(self, context: ExecutionContext):
103
+ super().__init__(context)
104
+ self.counter = 0
105
+
106
+ async def startup(self):
107
+ self.context.logger().info("Starting...")
108
+
109
+ async def run(self):
110
+ while not self.context.is_shutdown_requested():
111
+ self.counter += 1
112
+ await asyncio.sleep(1)
113
+
114
+ async def shutdown(self):
115
+ self.context.logger().info(f"Final count: {self.counter}")
116
+
117
+ def get_state(self):
118
+ return {"counter": self.counter}
119
+
120
+ async def restore_state(self, state):
121
+ self.counter = state.get("counter", 0)
122
+
123
+ if __name__ == "__main__":
124
+ DoryApp().run(MyApp)
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Installation
130
+
131
+ ```bash
132
+ pip install dory-sdk[kubernetes]
133
+ ```
134
+
135
+ ---
136
+
137
+ ## CLI Tool
138
+
139
+ The SDK includes a CLI for generating Kubernetes manifests:
140
+
141
+ ```bash
142
+ # Initialize a new project with all files
143
+ dory init my-app --image my-app:latest
144
+
145
+ # Output:
146
+ # Created main.py
147
+ # Created Dockerfile
148
+ # Created k8s/rbac.yaml
149
+ # Created k8s/deployment.yaml
150
+
151
+ # Generate specific manifests
152
+ dory generate rbac --name my-app
153
+ dory generate deployment --name my-app --image my-app:latest
154
+ dory generate all --name my-app --image my-app:latest
155
+
156
+ # Validate configuration
157
+ dory validate
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Deployment Options
163
+
164
+ ### Option 1: Helm Chart
165
+
166
+ ```bash
167
+ helm install my-app ./helm/dory-processor \
168
+ --set name=my-app \
169
+ --set image.repository=my-app \
170
+ --set image.tag=latest
171
+ ```
172
+
173
+ With values file:
174
+ ```bash
175
+ helm install my-app ./helm/dory-processor -f values.yaml
176
+ ```
177
+
178
+ ### Option 2: Kustomize
179
+
180
+ ```bash
181
+ # Deploy to dev
182
+ kubectl apply -k kustomize/overlays/dev
183
+
184
+ # Deploy to production
185
+ kubectl apply -k kustomize/overlays/production
186
+ ```
187
+
188
+ ### Option 3: CLI Generated Manifests
189
+
190
+ ```bash
191
+ dory init my-app --image my-app:latest
192
+ kubectl apply -f k8s/
193
+ ```
194
+
195
+ ### When to Use Which
196
+
197
+ | Choose | When |
198
+ |--------|------|
199
+ | **Helm** | Need release management, rollback, existing Helm workflow |
200
+ | **Kustomize** | GitOps (ArgoCD/Flux), prefer patch-based config |
201
+ | **CLI** | Quick start, simple deployments |
202
+
203
+ ---
204
+
205
+ ## Sidecar Mode (No SDK Required)
206
+
207
+ Don't want to integrate the SDK? Use **sidecar mode** - your app runs unchanged, a lightweight sidecar handles Kubernetes health endpoints.
208
+
209
+ ### What You Get
210
+
211
+ | Feature | With SDK | Sidecar Mode |
212
+ |---------|----------|--------------|
213
+ | Health endpoints | Yes | Yes |
214
+ | Graceful shutdown | Yes | Yes |
215
+ | State migration | Yes | **No** |
216
+ | Zero code changes | No | **Yes** |
217
+
218
+ ### Deploy with Sidecar
219
+
220
+ **Using Helm:**
221
+ ```bash
222
+ helm install my-app ./helm/dory-processor \
223
+ --set image.repository=your-app \
224
+ --set image.tag=latest \
225
+ --set sidecar.enabled=true
226
+ ```
227
+
228
+ **Using Kustomize:**
229
+ ```bash
230
+ kubectl apply -k kustomize/overlays/sidecar
231
+ ```
232
+
233
+ ### How It Works
234
+
235
+ ```
236
+ ┌─────────────────────────────────────────┐
237
+ │ Pod │
238
+ │ ┌─────────────┐ ┌────────────────┐ │
239
+ │ │ Your App │ │ Dory Sidecar │ │
240
+ │ │ (no SDK) │ │ │ │
241
+ │ │ │ │ /healthz ←────┼──┼── K8s liveness
242
+ │ │ port 8081 ←┼────┼→ /ready ←────┼──┼── K8s readiness
243
+ │ │ │ │ /prestop ←────┼──┼── K8s preStop
244
+ │ │ │ │ /metrics │ │
245
+ │ └─────────────┘ └────────────────┘ │
246
+ └─────────────────────────────────────────┘
247
+ ```
248
+
249
+ The sidecar:
250
+ - Responds to Kubernetes health probes
251
+ - Optionally monitors your app's port/health endpoint
252
+ - Handles graceful shutdown signals
253
+
254
+ ### Sidecar Configuration
255
+
256
+ | Environment Variable | Default | Description |
257
+ |---------------------|---------|-------------|
258
+ | `DORY_APP_PORT` | - | Your app's port (optional monitoring) |
259
+ | `DORY_APP_HEALTH_PATH` | - | Your app's health endpoint to check |
260
+ | `DORY_APP_PRESTOP_PATH` | - | Your app's shutdown endpoint to call |
261
+ | `DORY_READY_REQUIRES_APP` | false | Fail /ready if app doesn't respond |
262
+
263
+ ### Build the Sidecar Image
264
+
265
+ ```bash
266
+ docker build -f Dockerfile.sidecar -t dory-sidecar:1.0.0 .
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Integration Guide
272
+
273
+ ### Step 1: Install SDK
274
+
275
+ ```bash
276
+ pip install dory-sdk[kubernetes]
277
+ ```
278
+
279
+ ### Step 2: Write Your Processor
280
+
281
+ **Minimal (with `@stateful`):**
282
+ ```python
283
+ from dory import DoryApp, BaseProcessor, stateful
284
+
285
+ class MyApp(BaseProcessor):
286
+ # These are automatically saved/restored
287
+ counter = stateful(0)
288
+ data = stateful(dict)
289
+
290
+ async def run(self):
291
+ async for i in self.run_loop(interval=1):
292
+ self.counter += 1
293
+
294
+ if __name__ == "__main__":
295
+ DoryApp().run(MyApp)
296
+ ```
297
+
298
+ **That's it!** The SDK handles:
299
+ - `get_state()` - auto-generated from `@stateful` vars
300
+ - `restore_state()` - auto-generated from `@stateful` vars
301
+ - `startup()` - default no-op
302
+ - `shutdown()` - default no-op
303
+
304
+ ### Step 3: Deploy to Kubernetes
305
+
306
+ **Option A: Use CLI**
307
+ ```bash
308
+ dory init my-app --image my-app:latest
309
+ kubectl apply -f k8s/
310
+ ```
311
+
312
+ **Option B: Use Helm**
313
+ ```bash
314
+ helm install my-app ./helm/dory-processor --set image.repository=my-app
315
+ ```
316
+
317
+ **Option C: Use Kustomize**
318
+ ```bash
319
+ # Edit kustomize/overlays/dev/kustomization.yaml to set your image
320
+ kubectl apply -k kustomize/overlays/dev
321
+ ```
322
+
323
+ **Option D: Manual Setup**
324
+
325
+ 1. Create RBAC:
326
+ ```yaml
327
+ apiVersion: v1
328
+ kind: ServiceAccount
329
+ metadata:
330
+ name: my-app
331
+ ---
332
+ apiVersion: rbac.authorization.k8s.io/v1
333
+ kind: Role
334
+ metadata:
335
+ name: my-app-state-manager
336
+ rules:
337
+ - apiGroups: [""]
338
+ resources: ["configmaps"]
339
+ verbs: ["get", "create", "update", "patch", "delete"]
340
+ ---
341
+ apiVersion: rbac.authorization.k8s.io/v1
342
+ kind: RoleBinding
343
+ metadata:
344
+ name: my-app-state-manager
345
+ subjects:
346
+ - kind: ServiceAccount
347
+ name: my-app
348
+ roleRef:
349
+ kind: Role
350
+ name: my-app-state-manager
351
+ apiGroup: rbac.authorization.k8s.io
352
+ ```
353
+
354
+ 2. Create Deployment:
355
+ ```yaml
356
+ apiVersion: apps/v1
357
+ kind: Deployment
358
+ metadata:
359
+ name: my-app
360
+ spec:
361
+ replicas: 1
362
+ selector:
363
+ matchLabels:
364
+ app: my-app
365
+ template:
366
+ metadata:
367
+ labels:
368
+ app: my-app
369
+ spec:
370
+ serviceAccountName: my-app
371
+ terminationGracePeriodSeconds: 35
372
+ containers:
373
+ - name: my-app
374
+ image: my-app:latest
375
+ env:
376
+ - name: DORY_POD_NAME
377
+ valueFrom:
378
+ fieldRef:
379
+ fieldPath: metadata.name
380
+ - name: DORY_POD_NAMESPACE
381
+ valueFrom:
382
+ fieldRef:
383
+ fieldPath: metadata.namespace
384
+ livenessProbe:
385
+ httpGet:
386
+ path: /healthz
387
+ port: 8080
388
+ readinessProbe:
389
+ httpGet:
390
+ path: /ready
391
+ port: 8080
392
+ lifecycle:
393
+ preStop:
394
+ httpGet:
395
+ path: /prestop
396
+ port: 8080
397
+ ```
398
+
399
+ ---
400
+
401
+ ## Features
402
+
403
+ ### `@stateful` Decorator
404
+
405
+ Mark variables for automatic state management:
406
+
407
+ ```python
408
+ from dory import BaseProcessor, stateful
409
+
410
+ class MyApp(BaseProcessor):
411
+ # Simple values
412
+ counter = stateful(0)
413
+ name = stateful("default")
414
+
415
+ # Mutable defaults (use factory)
416
+ data = stateful(dict) # Creates new dict for each instance
417
+ items = stateful(list) # Creates new list for each instance
418
+
419
+ async def run(self):
420
+ # Just use them normally - SDK handles save/restore
421
+ self.counter += 1
422
+ self.data["key"] = "value"
423
+ ```
424
+
425
+ ### `run_loop()` Helper
426
+
427
+ Simplifies the shutdown check pattern:
428
+
429
+ ```python
430
+ # Instead of:
431
+ async def run(self):
432
+ while not self.context.is_shutdown_requested():
433
+ self.counter += 1
434
+ await asyncio.sleep(1)
435
+
436
+ # Use:
437
+ async def run(self):
438
+ async for i in self.run_loop(interval=1):
439
+ self.counter += 1
440
+ print(f"Iteration {i}")
441
+ ```
442
+
443
+ ### Function-Based API
444
+
445
+ For simple apps that don't need a class:
446
+
447
+ ```python
448
+ from dory.simple import processor, state
449
+
450
+ counter = state(0)
451
+ sessions = state(dict)
452
+
453
+ @processor
454
+ async def main(ctx):
455
+ logger = ctx.logger()
456
+
457
+ async for i in ctx.run_loop(interval=1):
458
+ counter.value += 1
459
+ logger.info(f"Count: {counter.value}")
460
+ ```
461
+
462
+ ### ExecutionContext
463
+
464
+ Access pod metadata and utilities:
465
+
466
+ ```python
467
+ async def run(self):
468
+ ctx = self.context
469
+
470
+ # Logging with pod context
471
+ ctx.logger().info("Processing...")
472
+
473
+ # Pod metadata
474
+ print(f"Pod: {ctx.pod_name}")
475
+ print(f"Namespace: {ctx.pod_namespace}")
476
+ print(f"Processor ID: {ctx.processor_id}")
477
+ print(f"Restart count: {ctx.attempt_number}")
478
+
479
+ # App config (env vars except DORY_*)
480
+ config = ctx.config()
481
+ model_path = config.get("MODEL_PATH")
482
+
483
+ # Shutdown detection
484
+ while not ctx.is_shutdown_requested():
485
+ if ctx.is_migration_imminent():
486
+ print("Migration coming, finishing batch...")
487
+ await process()
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Configuration
493
+
494
+ ### Environment Variables
495
+
496
+ | Variable | Default | Description |
497
+ |----------|---------|-------------|
498
+ | `DORY_HEALTH_PORT` | 8080 | Health server port |
499
+ | `DORY_LOG_LEVEL` | INFO | Log level |
500
+ | `DORY_LOG_FORMAT` | json | Log format (json/text) |
501
+ | `DORY_STATE_BACKEND` | configmap | State storage backend |
502
+ | `DORY_STARTUP_TIMEOUT_SEC` | 30 | Startup timeout |
503
+ | `DORY_SHUTDOWN_TIMEOUT_SEC` | 30 | Shutdown timeout |
504
+
505
+ ### Config File (dory.yaml)
506
+
507
+ ```yaml
508
+ health_port: 8080
509
+ log_level: INFO
510
+ log_format: json
511
+ state_backend: configmap
512
+ startup_timeout_sec: 30
513
+ shutdown_timeout_sec: 30
514
+ ```
515
+
516
+ ### Local Development
517
+
518
+ Test locally without Kubernetes:
519
+
520
+ ```bash
521
+ DORY_STATE_BACKEND=local python main.py
522
+ ```
523
+
524
+ ---
525
+
526
+ ## HTTP Endpoints
527
+
528
+ | Endpoint | Description |
529
+ |----------|-------------|
530
+ | `GET /healthz` | Liveness probe (200=alive) |
531
+ | `GET /ready` | Readiness probe (200=ready) |
532
+ | `GET /metrics` | Prometheus metrics |
533
+ | `GET /state` | Get processor state |
534
+ | `POST /state` | Restore processor state |
535
+ | `GET /prestop` | PreStop hook handler |
536
+
537
+ ---
538
+
539
+ ## API Reference
540
+
541
+ ### BaseProcessor Methods
542
+
543
+ | Method | Required | Description |
544
+ |--------|----------|-------------|
545
+ | `run()` | **Yes** | Main processing loop |
546
+ | `startup()` | No | Initialize resources (default: no-op) |
547
+ | `shutdown()` | No | Cleanup resources (default: no-op) |
548
+ | `get_state()` | No | Return state dict (default: `@stateful` vars) |
549
+ | `restore_state(state)` | No | Restore state (default: `@stateful` vars) |
550
+
551
+ ### Helper Methods
552
+
553
+ | Method | Description |
554
+ |--------|-------------|
555
+ | `run_loop(interval)` | Async iterator with auto shutdown check |
556
+ | `is_shutting_down()` | Check if shutdown requested |
557
+
558
+ ### Fault Handling Hooks (Optional)
559
+
560
+ | Method | Description |
561
+ |--------|-------------|
562
+ | `on_state_restore_failed(error)` | Handle restore errors |
563
+ | `on_rapid_restart_detected(count)` | Handle restart loops |
564
+ | `on_health_check_failed(error)` | Handle health failures |
565
+ | `reset_caches()` | Clear caches on golden reset |
566
+
567
+ ---
568
+
569
+ ## How State Migration Works
570
+
571
+ ### Pod Shutdown
572
+ ```
573
+ 1. Kubernetes calls /prestop
574
+ 2. SDK saves state to ConfigMap
575
+ 3. Pod marked not-ready
576
+ 4. Your run() exits
577
+ 5. Your shutdown() called
578
+ 6. Pod terminates
579
+ ```
580
+
581
+ ### New Pod Startup
582
+ ```
583
+ 1. SDK finds state in ConfigMap
584
+ 2. Your startup() called
585
+ 3. Your restore_state() called
586
+ 4. Pod marked ready
587
+ 5. Your run() starts
588
+ ```
589
+
590
+ ---
591
+
592
+ ## Comparison: Before vs After
593
+
594
+ ### Before (25+ lines)
595
+
596
+ ```python
597
+ import asyncio
598
+ from dory import DoryApp, BaseProcessor, ExecutionContext
599
+
600
+ class MyApp(BaseProcessor):
601
+ def __init__(self, context: ExecutionContext):
602
+ super().__init__(context)
603
+ self.counter = 0
604
+ self.sessions = {}
605
+
606
+ async def startup(self) -> None:
607
+ pass
608
+
609
+ async def run(self) -> None:
610
+ while not self.context.is_shutdown_requested():
611
+ self.counter += 1
612
+ await asyncio.sleep(1)
613
+
614
+ async def shutdown(self) -> None:
615
+ pass
616
+
617
+ def get_state(self) -> dict:
618
+ return {"counter": self.counter, "sessions": self.sessions}
619
+
620
+ async def restore_state(self, state: dict) -> None:
621
+ self.counter = state.get("counter", 0)
622
+ self.sessions = state.get("sessions", {})
623
+
624
+ if __name__ == "__main__":
625
+ DoryApp().run(MyApp)
626
+ ```
627
+
628
+ ### After (7 lines)
629
+
630
+ ```python
631
+ from dory import DoryApp, BaseProcessor, stateful
632
+
633
+ class MyApp(BaseProcessor):
634
+ counter = stateful(0)
635
+ sessions = stateful(dict)
636
+
637
+ async def run(self):
638
+ async for _ in self.run_loop(interval=1):
639
+ self.counter += 1
640
+
641
+ if __name__ == "__main__":
642
+ DoryApp().run(MyApp)
643
+ ```
644
+
645
+ ---
646
+
647
+ ## Documentation
648
+
649
+ - [Developer Guide](docs/DEVELOPER_GUIDE.md) - Advanced topics
650
+
651
+ ## Examples
652
+
653
+ | Example | Description | Pattern |
654
+ |---------|-------------|---------|
655
+ | [`minimal-processor-py`](../examples/minimal-processor-py/) | Simplest possible processor (~95 lines) | `@stateful` + `run_loop()` |
656
+ | [`dory-info-logger-py`](../examples/dory-info-logger-py/) | Full demo with HTTP dashboard | `@stateful` + fault hooks |
657
+ | [`dory-edge-logger-py`](../examples/dory-edge-logger-py/) | Edge workload with DB logging | Manual state management |
658
+
659
+ **Start here**: Use `minimal-processor-py` as a template for new processors.
660
+
661
+ ## License
662
+
663
+ Apache 2.0