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/config/loader.py ADDED
@@ -0,0 +1,361 @@
1
+ """
2
+ Configuration loader for Dory SDK.
3
+
4
+ Supports loading from:
5
+ 1. YAML configuration file
6
+ 2. Environment variables (DORY_ prefix)
7
+ 3. Configuration presets
8
+ 4. Auto-detection (service name, version, environment)
9
+ 5. Default values
10
+ """
11
+
12
+ import logging
13
+ import os
14
+ import re
15
+ import subprocess
16
+ from pathlib import Path
17
+ from typing import Any, Dict, Optional
18
+
19
+ import yaml
20
+
21
+ from dory.config.schema import DoryConfig
22
+ from dory.config.defaults import DEFAULT_CONFIG
23
+ from dory.config.presets import get_preset, list_presets, DEVELOPMENT_PRESET
24
+ from dory.utils.errors import DoryConfigError
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class ConfigLoader:
30
+ """
31
+ Smart configuration loader with:
32
+ - Auto-detection of service name/version
33
+ - Preset support (development, production, high-availability)
34
+ - Zero-config mode
35
+ - Progressive disclosure
36
+
37
+ Priority order:
38
+ 1. Environment variables (highest)
39
+ 2. Config file
40
+ 3. Preset (if specified)
41
+ 4. Auto-detected defaults
42
+ 5. Default values (lowest)
43
+ """
44
+
45
+ ENV_PREFIX = "DORY_"
46
+ DEFAULT_CONFIG_PATHS = [
47
+ "/etc/dory/config.yaml",
48
+ "/app/config/dory.yaml",
49
+ "./dory.yaml",
50
+ ]
51
+
52
+ def __init__(self, config_file: str | None = None):
53
+ """
54
+ Initialize config loader.
55
+
56
+ Args:
57
+ config_file: Optional path to YAML config file
58
+ """
59
+ self._config_file = config_file
60
+
61
+ def load(self) -> DoryConfig:
62
+ """
63
+ Load and validate configuration with smart defaults.
64
+
65
+ Priority:
66
+ 1. Environment variables (DORY_*)
67
+ 2. Config file (dory.yaml)
68
+ 3. Preset (if specified)
69
+ 4. Auto-detected defaults
70
+ 5. Default values
71
+
72
+ Returns:
73
+ Validated DoryConfig instance
74
+
75
+ Raises:
76
+ DoryConfigError: If configuration is invalid
77
+ """
78
+ # Try to load config file
79
+ file_config = self._load_from_file()
80
+
81
+ # Determine which preset to use
82
+ preset_name = self._determine_preset(file_config)
83
+
84
+ # Start with preset or development defaults
85
+ if preset_name in list_presets():
86
+ config_dict = get_preset(preset_name)
87
+ logger.info(f"Using configuration preset: {preset_name}")
88
+ else:
89
+ config_dict = DEVELOPMENT_PRESET.copy()
90
+ if preset_name:
91
+ logger.warning(f"Unknown preset '{preset_name}', using development defaults")
92
+ else:
93
+ logger.info("No preset specified, using development mode with auto-detection")
94
+
95
+ # Auto-detect application info
96
+ app_config = self._auto_detect_app_info(file_config.get("app", {}) if file_config else {})
97
+ config_dict["app"] = app_config
98
+
99
+ # Deep merge file config (overrides preset)
100
+ if file_config:
101
+ config_dict = self._deep_merge(config_dict, file_config)
102
+
103
+ # Apply environment variable overrides (highest priority)
104
+ env_config = self._load_from_env()
105
+ config_dict = self._deep_merge(config_dict, env_config)
106
+
107
+ # Validate and create config object
108
+ try:
109
+ config = DoryConfig(**config_dict)
110
+ logger.info(f"Configuration loaded: {app_config.get('name', 'unknown')} v{app_config.get('version', 'unknown')}")
111
+ logger.debug(f"Full configuration: {config.model_dump()}")
112
+ return config
113
+ except Exception as e:
114
+ raise DoryConfigError(f"Invalid configuration: {e}", cause=e)
115
+
116
+ def _load_from_file(self) -> dict[str, Any] | None:
117
+ """Load configuration from YAML file."""
118
+ config_path = self._find_config_file()
119
+
120
+ if not config_path:
121
+ logger.debug("No config file found, using defaults")
122
+ return None
123
+
124
+ try:
125
+ with open(config_path) as f:
126
+ config = yaml.safe_load(f) or {}
127
+
128
+ logger.info(f"Loaded config from {config_path}")
129
+ return config
130
+
131
+ except yaml.YAMLError as e:
132
+ raise DoryConfigError(f"Invalid YAML in {config_path}: {e}", cause=e)
133
+ except Exception as e:
134
+ raise DoryConfigError(f"Failed to read {config_path}: {e}", cause=e)
135
+
136
+ def _find_config_file(self) -> Path | None:
137
+ """Find config file from explicit path or default locations."""
138
+ # Check explicit path first
139
+ if self._config_file:
140
+ path = Path(self._config_file)
141
+ if path.exists():
142
+ return path
143
+ raise DoryConfigError(f"Config file not found: {self._config_file}")
144
+
145
+ # Check environment variable
146
+ env_path = os.environ.get("DORY_CONFIG_FILE")
147
+ if env_path:
148
+ path = Path(env_path)
149
+ if path.exists():
150
+ return path
151
+ logger.warning(f"DORY_CONFIG_FILE set but not found: {env_path}")
152
+
153
+ # Check default locations
154
+ for default_path in self.DEFAULT_CONFIG_PATHS:
155
+ path = Path(default_path)
156
+ if path.exists():
157
+ return path
158
+
159
+ return None
160
+
161
+ def _load_from_env(self) -> dict[str, Any]:
162
+ """Load configuration from environment variables."""
163
+ config = {}
164
+
165
+ # Map of config keys to environment variable names
166
+ env_mapping = {
167
+ "startup_timeout_sec": "DORY_STARTUP_TIMEOUT_SEC",
168
+ "shutdown_timeout_sec": "DORY_SHUTDOWN_TIMEOUT_SEC",
169
+ "health_check_interval_sec": "DORY_HEALTH_CHECK_INTERVAL_SEC",
170
+ "health_port": "DORY_HEALTH_PORT",
171
+ "health_path": "DORY_HEALTH_PATH",
172
+ "ready_path": "DORY_READY_PATH",
173
+ "metrics_path": "DORY_METRICS_PATH",
174
+ "state_backend": "DORY_STATE_BACKEND",
175
+ "state_pvc_mount": "DORY_STATE_PVC_MOUNT",
176
+ "state_s3_bucket": "DORY_STATE_S3_BUCKET",
177
+ "state_s3_prefix": "DORY_STATE_S3_PREFIX",
178
+ "max_restart_attempts": "DORY_MAX_RESTART_ATTEMPTS",
179
+ "restart_backoff_sec": "DORY_RESTART_BACKOFF_SEC",
180
+ "golden_image_threshold": "DORY_GOLDEN_IMAGE_THRESHOLD",
181
+ "log_level": "DORY_LOG_LEVEL",
182
+ "log_format": "DORY_LOG_FORMAT",
183
+ "metrics_enabled": "DORY_METRICS_ENABLED",
184
+ "metrics_prefix": "DORY_METRICS_PREFIX",
185
+ }
186
+
187
+ for config_key, env_var in env_mapping.items():
188
+ value = os.environ.get(env_var)
189
+ if value is not None:
190
+ # Convert to appropriate type
191
+ config[config_key] = self._convert_env_value(config_key, value)
192
+
193
+ return config
194
+
195
+ def _convert_env_value(self, key: str, value: str) -> Any:
196
+ """Convert environment variable string to appropriate type."""
197
+ # Integer fields
198
+ int_fields = {
199
+ "startup_timeout_sec",
200
+ "shutdown_timeout_sec",
201
+ "health_check_interval_sec",
202
+ "health_port",
203
+ "max_restart_attempts",
204
+ "restart_backoff_sec",
205
+ "golden_image_threshold",
206
+ }
207
+
208
+ # Boolean fields
209
+ bool_fields = {"metrics_enabled"}
210
+
211
+ if key in int_fields:
212
+ try:
213
+ return int(value)
214
+ except ValueError:
215
+ raise DoryConfigError(f"Invalid integer for {key}: {value}")
216
+
217
+ if key in bool_fields:
218
+ return value.lower() in ("true", "1", "yes", "on")
219
+
220
+ return value
221
+
222
+ def _determine_preset(self, file_config: Optional[Dict[str, Any]]) -> Optional[str]:
223
+ """
224
+ Determine which preset to use.
225
+
226
+ Priority:
227
+ 1. DORY_PRESET environment variable
228
+ 2. preset field in config file
229
+ 3. None (uses development preset)
230
+
231
+ Args:
232
+ file_config: Configuration loaded from file
233
+
234
+ Returns:
235
+ Preset name or None
236
+ """
237
+ # Check environment variable first
238
+ preset = os.environ.get("DORY_PRESET")
239
+ if preset:
240
+ logger.debug(f"Preset from environment: {preset}")
241
+ return preset
242
+
243
+ # Check config file
244
+ if file_config and "preset" in file_config:
245
+ preset = file_config["preset"]
246
+ logger.debug(f"Preset from config file: {preset}")
247
+ return preset
248
+
249
+ # No preset specified
250
+ return None
251
+
252
+ def _auto_detect_app_info(self, user_app: Dict[str, Any]) -> Dict[str, Any]:
253
+ """
254
+ Auto-detect service name, version, and environment.
255
+
256
+ Args:
257
+ user_app: User-provided app configuration
258
+
259
+ Returns:
260
+ Dictionary with app configuration
261
+ """
262
+ app = {}
263
+
264
+ # Auto-detect service name
265
+ if "name" in user_app:
266
+ app["name"] = user_app["name"]
267
+ else:
268
+ # Try to detect from directory name
269
+ cwd = Path.cwd()
270
+ app["name"] = cwd.name
271
+ logger.debug(f"Auto-detected service name: {app['name']}")
272
+
273
+ # Auto-detect version
274
+ if "version" in user_app:
275
+ app["version"] = user_app["version"]
276
+ else:
277
+ # Try to detect from git tag, pyproject.toml, or use default
278
+ app["version"] = self._detect_version()
279
+ logger.debug(f"Auto-detected version: {app['version']}")
280
+
281
+ # Auto-detect environment
282
+ if "environment" in user_app:
283
+ app["environment"] = user_app["environment"]
284
+ else:
285
+ app["environment"] = os.environ.get("ENVIRONMENT", "development")
286
+ logger.debug(f"Auto-detected environment: {app['environment']}")
287
+
288
+ # Copy any other user-provided app config
289
+ for key, value in user_app.items():
290
+ if key not in app:
291
+ app[key] = value
292
+
293
+ return app
294
+
295
+ def _detect_version(self) -> str:
296
+ """
297
+ Try to detect version from various sources.
298
+
299
+ Returns:
300
+ Version string
301
+ """
302
+ # Try git tag
303
+ try:
304
+ result = subprocess.run(
305
+ ["git", "describe", "--tags", "--abbrev=0"],
306
+ capture_output=True,
307
+ text=True,
308
+ timeout=1,
309
+ check=False,
310
+ )
311
+ if result.returncode == 0 and result.stdout.strip():
312
+ return result.stdout.strip()
313
+ except Exception:
314
+ pass
315
+
316
+ # Try pyproject.toml
317
+ pyproject_path = Path("pyproject.toml")
318
+ if pyproject_path.exists():
319
+ try:
320
+ with open(pyproject_path) as f:
321
+ content = f.read()
322
+ # Simple regex to extract version
323
+ match = re.search(r'version\s*=\s*"([^"]+)"', content)
324
+ if match:
325
+ return match.group(1)
326
+ except Exception:
327
+ pass
328
+
329
+ # Try package.json (for Node.js projects)
330
+ package_json_path = Path("package.json")
331
+ if package_json_path.exists():
332
+ try:
333
+ with open(package_json_path) as f:
334
+ import json
335
+ data = json.load(f)
336
+ if "version" in data:
337
+ return data["version"]
338
+ except Exception:
339
+ pass
340
+
341
+ # Default
342
+ return "0.1.0-dev"
343
+
344
+ def _deep_merge(self, base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
345
+ """
346
+ Deep merge two dictionaries.
347
+
348
+ Args:
349
+ base: Base dictionary
350
+ override: Dictionary with override values
351
+
352
+ Returns:
353
+ Merged dictionary
354
+ """
355
+ result = base.copy()
356
+ for key, value in override.items():
357
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
358
+ result[key] = self._deep_merge(result[key], value)
359
+ else:
360
+ result[key] = value
361
+ return result
dory/config/presets.py ADDED
@@ -0,0 +1,325 @@
1
+ """Configuration presets for common scenarios."""
2
+
3
+ from typing import Dict, Any
4
+
5
+ # Development preset - developer-friendly defaults
6
+ DEVELOPMENT_PRESET = {
7
+ "environment": "development",
8
+ "log_level": "DEBUG",
9
+ "log_format": "text",
10
+
11
+ # Health server
12
+ "health_port": 8080,
13
+ "health_path": "/healthz",
14
+ "ready_path": "/ready",
15
+ "metrics_path": "/metrics",
16
+
17
+ # State management
18
+ "state_backend": "local",
19
+ "state_pvc_mount": "/data",
20
+
21
+ # Lifecycle timeouts
22
+ "startup_timeout_sec": 30,
23
+ "shutdown_timeout_sec": 30,
24
+ "health_check_interval_sec": 10,
25
+
26
+ # Recovery
27
+ "max_restart_attempts": 3,
28
+ "restart_backoff_sec": 10,
29
+ "golden_image_threshold": 5,
30
+
31
+ # Metrics
32
+ "metrics_enabled": True,
33
+ "metrics_prefix": "dory",
34
+
35
+ # Retry configuration
36
+ "retry": {
37
+ "max_attempts": 3,
38
+ "initial_delay": 0.1,
39
+ "multiplier": 2.0,
40
+ "max_delay": 30.0,
41
+ "jitter": True,
42
+ "budget_percent": 20.0,
43
+ },
44
+
45
+ # Circuit breaker configuration
46
+ "circuit_breaker": {
47
+ "failure_threshold": 5,
48
+ "success_threshold": 2,
49
+ "timeout": 30.0,
50
+ "half_open_max_calls": 3,
51
+ },
52
+
53
+ # Error classification
54
+ "error_classification": {
55
+ "enabled": True,
56
+ "unknown_as_transient": False,
57
+ },
58
+
59
+ # OpenTelemetry configuration
60
+ "opentelemetry": {
61
+ "enabled": True,
62
+ "console_export": True,
63
+ "sampling": {"ratio": 1.0},
64
+ "otlp": {
65
+ "endpoint": "",
66
+ "console_export": True,
67
+ },
68
+ },
69
+
70
+ # Bookkeeping configuration
71
+ "bookkeeping": {
72
+ "request_tracking": {
73
+ "enabled": True,
74
+ "max_concurrent": 100,
75
+ "timeout": 30.0,
76
+ "collect_metrics": True,
77
+ },
78
+ "request_id": {
79
+ "enabled": True,
80
+ "format": "uuid4",
81
+ "add_to_response": True,
82
+ },
83
+ "connection_tracking": {
84
+ "enabled": True,
85
+ "collect_metrics": True,
86
+ },
87
+ },
88
+
89
+ # Middleware configuration
90
+ "middleware": {
91
+ "enabled": True,
92
+ "order": [
93
+ "request_id",
94
+ "request_tracker",
95
+ "opentelemetry",
96
+ "connection_tracker",
97
+ ],
98
+ },
99
+ }
100
+
101
+ # Production preset - production-ready defaults
102
+ PRODUCTION_PRESET = {
103
+ "environment": "production",
104
+ "log_level": "INFO",
105
+ "log_format": "json",
106
+
107
+ # Health server
108
+ "health_port": 8080,
109
+ "health_path": "/healthz",
110
+ "ready_path": "/ready",
111
+ "metrics_path": "/metrics",
112
+
113
+ # State management
114
+ "state_backend": "configmap",
115
+ "state_pvc_mount": "/data",
116
+
117
+ # Lifecycle timeouts
118
+ "startup_timeout_sec": 30,
119
+ "shutdown_timeout_sec": 30,
120
+ "health_check_interval_sec": 10,
121
+
122
+ # Recovery
123
+ "max_restart_attempts": 3,
124
+ "restart_backoff_sec": 10,
125
+ "golden_image_threshold": 5,
126
+
127
+ # Metrics
128
+ "metrics_enabled": True,
129
+ "metrics_prefix": "dory",
130
+
131
+ # Retry configuration
132
+ "retry": {
133
+ "max_attempts": 3,
134
+ "initial_delay": 0.1,
135
+ "multiplier": 2.0,
136
+ "max_delay": 30.0,
137
+ "jitter": True,
138
+ "budget_percent": 20.0,
139
+ },
140
+
141
+ # Circuit breaker configuration
142
+ "circuit_breaker": {
143
+ "failure_threshold": 5,
144
+ "success_threshold": 2,
145
+ "timeout": 30.0,
146
+ "half_open_max_calls": 3,
147
+ },
148
+
149
+ # Error classification
150
+ "error_classification": {
151
+ "enabled": True,
152
+ "unknown_as_transient": False,
153
+ },
154
+
155
+ # OpenTelemetry configuration
156
+ "opentelemetry": {
157
+ "enabled": True,
158
+ "console_export": False,
159
+ "sampling": {"ratio": 0.1}, # Sample 10% in production
160
+ "otlp": {
161
+ "endpoint": "",
162
+ "console_export": False,
163
+ },
164
+ },
165
+
166
+ # Bookkeeping configuration
167
+ "bookkeeping": {
168
+ "request_tracking": {
169
+ "enabled": True,
170
+ "max_concurrent": 1000,
171
+ "timeout": 30.0,
172
+ "collect_metrics": True,
173
+ },
174
+ "request_id": {
175
+ "enabled": True,
176
+ "format": "uuid4",
177
+ "add_to_response": True,
178
+ },
179
+ "connection_tracking": {
180
+ "enabled": True,
181
+ "collect_metrics": True,
182
+ },
183
+ },
184
+
185
+ # Middleware configuration
186
+ "middleware": {
187
+ "enabled": True,
188
+ "order": [
189
+ "request_id",
190
+ "request_tracker",
191
+ "opentelemetry",
192
+ "connection_tracker",
193
+ ],
194
+ },
195
+ }
196
+
197
+ # High-availability preset - aggressive fault tolerance
198
+ HIGH_AVAILABILITY_PRESET = {
199
+ "environment": "production",
200
+ "log_level": "INFO",
201
+ "log_format": "json",
202
+
203
+ # Health server
204
+ "health_port": 8080,
205
+ "health_path": "/healthz",
206
+ "ready_path": "/ready",
207
+ "metrics_path": "/metrics",
208
+
209
+ # State management
210
+ "state_backend": "pvc", # More reliable than configmap
211
+ "state_pvc_mount": "/data",
212
+
213
+ # Lifecycle timeouts
214
+ "startup_timeout_sec": 60, # More time for startup
215
+ "shutdown_timeout_sec": 60, # More time for graceful shutdown
216
+ "health_check_interval_sec": 5, # More frequent checks
217
+
218
+ # Recovery
219
+ "max_restart_attempts": 5, # More restart attempts
220
+ "restart_backoff_sec": 15, # Longer backoff
221
+ "golden_image_threshold": 10, # Higher threshold
222
+
223
+ # Metrics
224
+ "metrics_enabled": True,
225
+ "metrics_prefix": "dory",
226
+
227
+ # Retry configuration - more aggressive
228
+ "retry": {
229
+ "max_attempts": 5, # More retries
230
+ "initial_delay": 0.05,
231
+ "multiplier": 1.5,
232
+ "max_delay": 60.0,
233
+ "jitter": True,
234
+ "budget_percent": 30.0, # Higher budget
235
+ },
236
+
237
+ # Circuit breaker configuration - more sensitive
238
+ "circuit_breaker": {
239
+ "failure_threshold": 3, # Trip faster
240
+ "success_threshold": 5, # Need more successes to close
241
+ "timeout": 60.0, # Longer timeout
242
+ "half_open_max_calls": 5,
243
+ },
244
+
245
+ # Error classification
246
+ "error_classification": {
247
+ "enabled": True,
248
+ "unknown_as_transient": True, # Retry unknown errors
249
+ },
250
+
251
+ # OpenTelemetry configuration
252
+ "opentelemetry": {
253
+ "enabled": True,
254
+ "console_export": False,
255
+ "sampling": {"ratio": 1.0}, # Full sampling for debugging
256
+ "otlp": {
257
+ "endpoint": "",
258
+ "console_export": False,
259
+ },
260
+ },
261
+
262
+ # Bookkeeping configuration
263
+ "bookkeeping": {
264
+ "request_tracking": {
265
+ "enabled": True,
266
+ "max_concurrent": 2000,
267
+ "timeout": 60.0,
268
+ "collect_metrics": True,
269
+ },
270
+ "request_id": {
271
+ "enabled": True,
272
+ "format": "uuid4",
273
+ "add_to_response": True,
274
+ },
275
+ "connection_tracking": {
276
+ "enabled": True,
277
+ "collect_metrics": True,
278
+ },
279
+ },
280
+
281
+ # Middleware configuration
282
+ "middleware": {
283
+ "enabled": True,
284
+ "order": [
285
+ "request_id",
286
+ "request_tracker",
287
+ "opentelemetry",
288
+ "connection_tracker",
289
+ ],
290
+ },
291
+ }
292
+
293
+ PRESETS: Dict[str, Dict[str, Any]] = {
294
+ "development": DEVELOPMENT_PRESET,
295
+ "production": PRODUCTION_PRESET,
296
+ "high-availability": HIGH_AVAILABILITY_PRESET,
297
+ }
298
+
299
+
300
+ def get_preset(name: str) -> Dict[str, Any]:
301
+ """
302
+ Get configuration preset by name.
303
+
304
+ Args:
305
+ name: Preset name (development, production, high-availability)
306
+
307
+ Returns:
308
+ Dictionary with preset configuration
309
+
310
+ Raises:
311
+ ValueError: If preset name is unknown
312
+ """
313
+ if name not in PRESETS:
314
+ raise ValueError(f"Unknown preset: {name}. Available: {list(PRESETS.keys())}")
315
+ return PRESETS[name].copy()
316
+
317
+
318
+ def list_presets() -> list[str]:
319
+ """
320
+ Get list of available preset names.
321
+
322
+ Returns:
323
+ List of preset names
324
+ """
325
+ return list(PRESETS.keys())