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/cli/templates.py ADDED
@@ -0,0 +1,333 @@
1
+ """
2
+ Templates for generating Kubernetes manifests and project files.
3
+ """
4
+
5
+
6
+ def generate_rbac(name: str, namespace: str = "default") -> str:
7
+ """Generate RBAC manifest (ServiceAccount, Role, RoleBinding)."""
8
+ return f'''# RBAC for Dory processor: {name}
9
+ # Allows the processor to manage ConfigMaps for state persistence
10
+
11
+ apiVersion: v1
12
+ kind: ServiceAccount
13
+ metadata:
14
+ name: {name}
15
+ namespace: {namespace}
16
+ ---
17
+ apiVersion: rbac.authorization.k8s.io/v1
18
+ kind: Role
19
+ metadata:
20
+ name: {name}-state-manager
21
+ namespace: {namespace}
22
+ rules:
23
+ - apiGroups: [""]
24
+ resources: ["configmaps"]
25
+ verbs: ["get", "create", "update", "patch", "delete"]
26
+ ---
27
+ apiVersion: rbac.authorization.k8s.io/v1
28
+ kind: RoleBinding
29
+ metadata:
30
+ name: {name}-state-manager
31
+ namespace: {namespace}
32
+ subjects:
33
+ - kind: ServiceAccount
34
+ name: {name}
35
+ namespace: {namespace}
36
+ roleRef:
37
+ kind: Role
38
+ name: {name}-state-manager
39
+ apiGroup: rbac.authorization.k8s.io
40
+ '''
41
+
42
+
43
+ def generate_deployment(
44
+ name: str,
45
+ image: str,
46
+ namespace: str = "default",
47
+ replicas: int = 1,
48
+ health_port: int = 8080,
49
+ app_port: int = 8081,
50
+ ) -> str:
51
+ """Generate Deployment manifest with probes and PreStop hook."""
52
+ return f'''# Deployment for Dory processor: {name}
53
+
54
+ apiVersion: apps/v1
55
+ kind: Deployment
56
+ metadata:
57
+ name: {name}
58
+ namespace: {namespace}
59
+ labels:
60
+ app: {name}
61
+ spec:
62
+ replicas: {replicas}
63
+ selector:
64
+ matchLabels:
65
+ app: {name}
66
+ template:
67
+ metadata:
68
+ labels:
69
+ app: {name}
70
+ spec:
71
+ serviceAccountName: {name}
72
+ terminationGracePeriodSeconds: 35
73
+
74
+ containers:
75
+ - name: {name}
76
+ image: {image}
77
+ ports:
78
+ - name: health
79
+ containerPort: {health_port}
80
+ - name: app
81
+ containerPort: {app_port}
82
+
83
+ env:
84
+ # Pod metadata (required by Dory SDK)
85
+ - name: DORY_POD_NAME
86
+ valueFrom:
87
+ fieldRef:
88
+ fieldPath: metadata.name
89
+ - name: DORY_POD_NAMESPACE
90
+ valueFrom:
91
+ fieldRef:
92
+ fieldPath: metadata.namespace
93
+ - name: NODE_NAME
94
+ valueFrom:
95
+ fieldRef:
96
+ fieldPath: spec.nodeName
97
+
98
+ # Liveness probe - is the process alive?
99
+ livenessProbe:
100
+ httpGet:
101
+ path: /healthz
102
+ port: {health_port}
103
+ initialDelaySeconds: 10
104
+ periodSeconds: 10
105
+ timeoutSeconds: 5
106
+ failureThreshold: 3
107
+
108
+ # Readiness probe - ready for traffic?
109
+ readinessProbe:
110
+ httpGet:
111
+ path: /ready
112
+ port: {health_port}
113
+ initialDelaySeconds: 5
114
+ periodSeconds: 5
115
+ timeoutSeconds: 3
116
+ failureThreshold: 3
117
+
118
+ # PreStop hook - save state before shutdown
119
+ lifecycle:
120
+ preStop:
121
+ httpGet:
122
+ path: /prestop
123
+ port: {health_port}
124
+
125
+ resources:
126
+ requests:
127
+ memory: "256Mi"
128
+ cpu: "100m"
129
+ limits:
130
+ memory: "512Mi"
131
+ cpu: "500m"
132
+ '''
133
+
134
+
135
+ def generate_pod(
136
+ name: str,
137
+ image: str,
138
+ namespace: str = "default",
139
+ health_port: int = 8080,
140
+ app_port: int = 8081,
141
+ ) -> str:
142
+ """Generate standalone Pod manifest (for testing)."""
143
+ return f'''# Pod for Dory processor: {name}
144
+ # Use this for testing. For production, use Deployment.
145
+
146
+ apiVersion: v1
147
+ kind: Pod
148
+ metadata:
149
+ name: {name}
150
+ namespace: {namespace}
151
+ labels:
152
+ app: {name}
153
+ spec:
154
+ serviceAccountName: {name}
155
+ terminationGracePeriodSeconds: 35
156
+
157
+ containers:
158
+ - name: {name}
159
+ image: {image}
160
+ ports:
161
+ - name: health
162
+ containerPort: {health_port}
163
+ - name: app
164
+ containerPort: {app_port}
165
+
166
+ env:
167
+ - name: DORY_POD_NAME
168
+ valueFrom:
169
+ fieldRef:
170
+ fieldPath: metadata.name
171
+ - name: DORY_POD_NAMESPACE
172
+ valueFrom:
173
+ fieldRef:
174
+ fieldPath: metadata.namespace
175
+ - name: NODE_NAME
176
+ valueFrom:
177
+ fieldRef:
178
+ fieldPath: spec.nodeName
179
+
180
+ livenessProbe:
181
+ httpGet:
182
+ path: /healthz
183
+ port: {health_port}
184
+ initialDelaySeconds: 10
185
+ periodSeconds: 10
186
+
187
+ readinessProbe:
188
+ httpGet:
189
+ path: /ready
190
+ port: {health_port}
191
+ initialDelaySeconds: 5
192
+ periodSeconds: 5
193
+
194
+ lifecycle:
195
+ preStop:
196
+ httpGet:
197
+ path: /prestop
198
+ port: {health_port}
199
+ '''
200
+
201
+
202
+ def generate_all(
203
+ name: str,
204
+ image: str,
205
+ namespace: str = "default",
206
+ replicas: int = 1,
207
+ health_port: int = 8080,
208
+ app_port: int = 8081,
209
+ ) -> str:
210
+ """Generate all manifests in a single file."""
211
+ rbac = generate_rbac(name, namespace)
212
+ deployment = generate_deployment(name, image, namespace, replicas, health_port, app_port)
213
+ return f"{rbac}---\n{deployment}"
214
+
215
+
216
+ def generate_dockerfile(name: str) -> str:
217
+ """Generate Dockerfile for a Dory processor."""
218
+ return f'''# Dockerfile for Dory processor: {name}
219
+
220
+ FROM python:3.11-slim
221
+
222
+ WORKDIR /app
223
+
224
+ # Install system dependencies
225
+ RUN apt-get update && apt-get install -y --no-install-recommends \\
226
+ curl \\
227
+ && rm -rf /var/lib/apt/lists/*
228
+
229
+ # Install Dory SDK with Kubernetes support
230
+ RUN pip install --no-cache-dir dory-sdk[kubernetes]
231
+
232
+ # Copy application
233
+ COPY main.py .
234
+
235
+ # Expose ports (8080 for health, 8081 for app)
236
+ EXPOSE 8080 8081
237
+
238
+ # Health check
239
+ HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=3 \\
240
+ CMD curl -f http://localhost:8080/healthz || exit 1
241
+
242
+ # Run the application
243
+ CMD ["python", "main.py"]
244
+ '''
245
+
246
+
247
+ def generate_processor_template(name: str) -> str:
248
+ """Generate a template processor Python file."""
249
+ class_name = "".join(word.capitalize() for word in name.replace("-", "_").split("_"))
250
+
251
+ return f'''"""
252
+ {name} - A Dory-powered stateful processor.
253
+
254
+ Features:
255
+ - Zero-downtime migration
256
+ - Automatic state persistence
257
+ - Graceful shutdown
258
+ - Health monitoring
259
+
260
+ Generated by: dory init {name}
261
+ """
262
+
263
+ import asyncio
264
+ from dory import DoryApp, BaseProcessor, stateful
265
+
266
+
267
+ class {class_name}(BaseProcessor):
268
+ """
269
+ Your processor implementation.
270
+
271
+ The @stateful decorator automatically handles state save/restore.
272
+ Just implement the run() method with your business logic.
273
+ """
274
+
275
+ # Stateful variables (automatically saved and restored)
276
+ counter = stateful(0)
277
+ data = stateful(dict)
278
+
279
+ async def startup(self) -> None:
280
+ """Initialize resources (optional)."""
281
+ self.context.logger().info("Starting up...")
282
+ # Load models, open connections, etc.
283
+
284
+ async def run(self) -> None:
285
+ """Main processing loop."""
286
+ logger = self.context.logger()
287
+
288
+ # Use run_loop() for automatic shutdown detection
289
+ async for i in self.run_loop(interval=1):
290
+ self.counter += 1
291
+ logger.info(f"Iteration {{i}}: counter={{self.counter}}")
292
+
293
+ # Your business logic here
294
+ # ...
295
+
296
+ async def shutdown(self) -> None:
297
+ """Cleanup resources (optional)."""
298
+ self.context.logger().info(f"Shutting down. Final counter: {{self.counter}}")
299
+ # Close connections, flush buffers, etc.
300
+
301
+
302
+ if __name__ == "__main__":
303
+ DoryApp().run({class_name})
304
+ '''
305
+
306
+
307
+ def generate_simple_processor_template(name: str) -> str:
308
+ """Generate a minimal processor using function-based API."""
309
+ return f'''"""
310
+ {name} - A minimal Dory processor using function-based API.
311
+
312
+ Generated by: dory init {name} --simple
313
+ """
314
+
315
+ from dory.simple import processor, state
316
+
317
+ # State variables (automatically saved and restored)
318
+ counter = state(0)
319
+ data = state(dict)
320
+
321
+
322
+ @processor
323
+ async def main(ctx):
324
+ """Main processing loop."""
325
+ logger = ctx.logger()
326
+
327
+ async for i in ctx.run_loop(interval=1):
328
+ counter.value += 1
329
+ logger.info(f"Iteration {{i}}: counter={{counter.value}}")
330
+
331
+ # Your business logic here
332
+ # ...
333
+ '''
@@ -0,0 +1,23 @@
1
+ """Configuration loading and schema definitions."""
2
+
3
+ from dory.config.schema import DoryConfig
4
+ from dory.config.loader import ConfigLoader
5
+ from dory.config.defaults import DEFAULT_CONFIG
6
+ from dory.config.presets import (
7
+ get_preset,
8
+ list_presets,
9
+ DEVELOPMENT_PRESET,
10
+ PRODUCTION_PRESET,
11
+ HIGH_AVAILABILITY_PRESET,
12
+ )
13
+
14
+ __all__ = [
15
+ "DoryConfig",
16
+ "ConfigLoader",
17
+ "DEFAULT_CONFIG",
18
+ "get_preset",
19
+ "list_presets",
20
+ "DEVELOPMENT_PRESET",
21
+ "PRODUCTION_PRESET",
22
+ "HIGH_AVAILABILITY_PRESET",
23
+ ]
@@ -0,0 +1,50 @@
1
+ """Default configuration values for Dory SDK."""
2
+
3
+ from dory.types import StateBackend, LogFormat
4
+
5
+ # Default configuration dictionary
6
+ DEFAULT_CONFIG = {
7
+ # Lifecycle timeouts
8
+ "startup_timeout_sec": 30,
9
+ "shutdown_timeout_sec": 30,
10
+ "health_check_interval_sec": 10,
11
+
12
+ # Health server
13
+ "health_port": 8080,
14
+ "health_path": "/healthz",
15
+ "ready_path": "/ready",
16
+ "metrics_path": "/metrics",
17
+
18
+ # State management
19
+ "state_backend": StateBackend.CONFIGMAP.value,
20
+ "state_pvc_mount": "/data",
21
+ "state_s3_bucket": None,
22
+ "state_s3_prefix": "dory-state",
23
+
24
+ # Recovery
25
+ "max_restart_attempts": 3,
26
+ "restart_backoff_sec": 10,
27
+ "golden_image_threshold": 3,
28
+
29
+ # Logging
30
+ "log_level": "INFO",
31
+ "log_format": LogFormat.JSON.value,
32
+
33
+ # Metrics
34
+ "metrics_enabled": True,
35
+ "metrics_prefix": "dory",
36
+ }
37
+
38
+
39
+ def get_default(key: str, default=None):
40
+ """
41
+ Get a default configuration value.
42
+
43
+ Args:
44
+ key: Configuration key
45
+ default: Value to return if key not found
46
+
47
+ Returns:
48
+ Default value for the key
49
+ """
50
+ return DEFAULT_CONFIG.get(key, default)