isa-model 0.4.0__py3-none-any.whl → 0.4.4__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 (189) hide show
  1. isa_model/client.py +466 -43
  2. isa_model/core/cache/redis_cache.py +12 -3
  3. isa_model/core/config/config_manager.py +230 -3
  4. isa_model/core/config.py +90 -0
  5. isa_model/core/database/direct_db_client.py +114 -0
  6. isa_model/core/database/migration_manager.py +563 -0
  7. isa_model/core/database/migrations.py +21 -1
  8. isa_model/core/database/supabase_client.py +154 -19
  9. isa_model/core/dependencies.py +316 -0
  10. isa_model/core/discovery/__init__.py +19 -0
  11. isa_model/core/discovery/consul_discovery.py +190 -0
  12. isa_model/core/logging/__init__.py +54 -0
  13. isa_model/core/logging/influx_logger.py +523 -0
  14. isa_model/core/logging/loki_logger.py +160 -0
  15. isa_model/core/models/__init__.py +27 -18
  16. isa_model/core/models/config_models.py +625 -0
  17. isa_model/core/models/deployment_billing_tracker.py +430 -0
  18. isa_model/core/models/model_manager.py +35 -80
  19. isa_model/core/models/model_metadata.py +690 -0
  20. isa_model/core/models/model_repo.py +174 -18
  21. isa_model/core/models/system_models.py +857 -0
  22. isa_model/core/repositories/__init__.py +9 -0
  23. isa_model/core/repositories/config_repository.py +912 -0
  24. isa_model/core/services/intelligent_model_selector.py +399 -21
  25. isa_model/core/types.py +1 -0
  26. isa_model/deployment/__init__.py +5 -48
  27. isa_model/deployment/core/__init__.py +2 -31
  28. isa_model/deployment/core/deployment_manager.py +1278 -370
  29. isa_model/deployment/modal/__init__.py +8 -0
  30. isa_model/deployment/modal/config.py +136 -0
  31. isa_model/deployment/{services/auto_hf_modal_deployer.py → modal/deployer.py} +1 -1
  32. isa_model/deployment/modal/services/__init__.py +3 -0
  33. isa_model/deployment/modal/services/audio/__init__.py +1 -0
  34. isa_model/deployment/modal/services/embedding/__init__.py +1 -0
  35. isa_model/deployment/modal/services/llm/__init__.py +1 -0
  36. isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
  37. isa_model/deployment/modal/services/video/__init__.py +1 -0
  38. isa_model/deployment/modal/services/vision/__init__.py +1 -0
  39. isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
  40. isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
  41. isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
  42. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
  43. isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
  44. isa_model/deployment/storage/__init__.py +5 -0
  45. isa_model/deployment/storage/deployment_repository.py +824 -0
  46. isa_model/deployment/triton/__init__.py +10 -0
  47. isa_model/deployment/triton/config.py +196 -0
  48. isa_model/deployment/triton/configs/__init__.py +1 -0
  49. isa_model/deployment/triton/provider.py +512 -0
  50. isa_model/deployment/triton/scripts/__init__.py +1 -0
  51. isa_model/deployment/triton/templates/__init__.py +1 -0
  52. isa_model/inference/__init__.py +47 -1
  53. isa_model/inference/ai_factory.py +137 -10
  54. isa_model/inference/legacy_services/__init__.py +21 -0
  55. isa_model/inference/legacy_services/model_evaluation.py +637 -0
  56. isa_model/inference/legacy_services/model_service.py +573 -0
  57. isa_model/inference/legacy_services/model_serving.py +717 -0
  58. isa_model/inference/legacy_services/model_training.py +561 -0
  59. isa_model/inference/models/__init__.py +21 -0
  60. isa_model/inference/models/inference_config.py +551 -0
  61. isa_model/inference/models/inference_record.py +675 -0
  62. isa_model/inference/models/performance_models.py +714 -0
  63. isa_model/inference/repositories/__init__.py +9 -0
  64. isa_model/inference/repositories/inference_repository.py +828 -0
  65. isa_model/inference/services/audio/base_stt_service.py +184 -11
  66. isa_model/inference/services/audio/openai_stt_service.py +22 -6
  67. isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
  68. isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
  69. isa_model/inference/services/llm/__init__.py +10 -2
  70. isa_model/inference/services/llm/base_llm_service.py +335 -24
  71. isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
  72. isa_model/inference/services/llm/helpers/llm_adapter.py +9 -4
  73. isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
  74. isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
  75. isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
  76. isa_model/inference/services/llm/ollama_llm_service.py +9 -2
  77. isa_model/inference/services/llm/openai_llm_service.py +33 -16
  78. isa_model/inference/services/llm/yyds_llm_service.py +8 -2
  79. isa_model/inference/services/vision/__init__.py +22 -1
  80. isa_model/inference/services/vision/helpers/image_utils.py +8 -5
  81. isa_model/inference/services/vision/isa_vision_service.py +65 -4
  82. isa_model/inference/services/vision/openai_vision_service.py +19 -10
  83. isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
  84. isa_model/serving/api/cache_manager.py +245 -0
  85. isa_model/serving/api/dependencies/__init__.py +1 -0
  86. isa_model/serving/api/dependencies/auth.py +194 -0
  87. isa_model/serving/api/dependencies/database.py +139 -0
  88. isa_model/serving/api/error_handlers.py +284 -0
  89. isa_model/serving/api/fastapi_server.py +172 -22
  90. isa_model/serving/api/middleware/auth.py +8 -2
  91. isa_model/serving/api/middleware/security.py +23 -33
  92. isa_model/serving/api/middleware/tenant_context.py +414 -0
  93. isa_model/serving/api/routes/analytics.py +4 -1
  94. isa_model/serving/api/routes/config.py +645 -0
  95. isa_model/serving/api/routes/deployment_billing.py +315 -0
  96. isa_model/serving/api/routes/deployments.py +138 -2
  97. isa_model/serving/api/routes/gpu_gateway.py +440 -0
  98. isa_model/serving/api/routes/health.py +32 -12
  99. isa_model/serving/api/routes/inference_monitoring.py +486 -0
  100. isa_model/serving/api/routes/local_deployments.py +448 -0
  101. isa_model/serving/api/routes/tenants.py +575 -0
  102. isa_model/serving/api/routes/unified.py +680 -18
  103. isa_model/serving/api/routes/webhooks.py +479 -0
  104. isa_model/serving/api/startup.py +68 -54
  105. isa_model/utils/gpu_utils.py +311 -0
  106. {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/METADATA +71 -24
  107. isa_model-0.4.4.dist-info/RECORD +180 -0
  108. isa_model/core/security/secrets.py +0 -358
  109. isa_model/core/storage/hf_storage.py +0 -419
  110. isa_model/core/storage/minio_storage.py +0 -0
  111. isa_model/deployment/cloud/__init__.py +0 -9
  112. isa_model/deployment/cloud/modal/__init__.py +0 -10
  113. isa_model/deployment/core/deployment_config.py +0 -356
  114. isa_model/deployment/core/isa_deployment_service.py +0 -401
  115. isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
  116. isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
  117. isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
  118. isa_model/deployment/runtime/deployed_service.py +0 -338
  119. isa_model/deployment/services/__init__.py +0 -9
  120. isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
  121. isa_model/deployment/services/model_service.py +0 -332
  122. isa_model/deployment/services/service_monitor.py +0 -356
  123. isa_model/deployment/services/service_registry.py +0 -527
  124. isa_model/eval/__init__.py +0 -92
  125. isa_model/eval/benchmarks/__init__.py +0 -27
  126. isa_model/eval/benchmarks/multimodal_datasets.py +0 -460
  127. isa_model/eval/benchmarks.py +0 -701
  128. isa_model/eval/config/__init__.py +0 -10
  129. isa_model/eval/config/evaluation_config.py +0 -108
  130. isa_model/eval/evaluators/__init__.py +0 -24
  131. isa_model/eval/evaluators/audio_evaluator.py +0 -727
  132. isa_model/eval/evaluators/base_evaluator.py +0 -503
  133. isa_model/eval/evaluators/embedding_evaluator.py +0 -742
  134. isa_model/eval/evaluators/llm_evaluator.py +0 -472
  135. isa_model/eval/evaluators/vision_evaluator.py +0 -564
  136. isa_model/eval/example_evaluation.py +0 -395
  137. isa_model/eval/factory.py +0 -798
  138. isa_model/eval/infrastructure/__init__.py +0 -24
  139. isa_model/eval/infrastructure/experiment_tracker.py +0 -466
  140. isa_model/eval/isa_benchmarks.py +0 -700
  141. isa_model/eval/isa_integration.py +0 -582
  142. isa_model/eval/metrics.py +0 -951
  143. isa_model/eval/tests/unit/test_basic.py +0 -396
  144. isa_model/serving/api/routes/evaluations.py +0 -579
  145. isa_model/training/__init__.py +0 -168
  146. isa_model/training/annotation/annotation_schema.py +0 -47
  147. isa_model/training/annotation/processors/annotation_processor.py +0 -126
  148. isa_model/training/annotation/storage/dataset_manager.py +0 -131
  149. isa_model/training/annotation/storage/dataset_schema.py +0 -44
  150. isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
  151. isa_model/training/annotation/tests/test_minio copy.py +0 -113
  152. isa_model/training/annotation/tests/test_minio_upload.py +0 -43
  153. isa_model/training/annotation/views/annotation_controller.py +0 -158
  154. isa_model/training/cloud/__init__.py +0 -22
  155. isa_model/training/cloud/job_orchestrator.py +0 -402
  156. isa_model/training/cloud/runpod_trainer.py +0 -454
  157. isa_model/training/cloud/storage_manager.py +0 -482
  158. isa_model/training/core/__init__.py +0 -26
  159. isa_model/training/core/config.py +0 -181
  160. isa_model/training/core/dataset.py +0 -222
  161. isa_model/training/core/trainer.py +0 -720
  162. isa_model/training/core/utils.py +0 -213
  163. isa_model/training/examples/intelligent_training_example.py +0 -281
  164. isa_model/training/factory.py +0 -424
  165. isa_model/training/intelligent/__init__.py +0 -25
  166. isa_model/training/intelligent/decision_engine.py +0 -643
  167. isa_model/training/intelligent/intelligent_factory.py +0 -888
  168. isa_model/training/intelligent/knowledge_base.py +0 -751
  169. isa_model/training/intelligent/resource_optimizer.py +0 -839
  170. isa_model/training/intelligent/task_classifier.py +0 -576
  171. isa_model/training/storage/__init__.py +0 -24
  172. isa_model/training/storage/core_integration.py +0 -439
  173. isa_model/training/storage/training_repository.py +0 -552
  174. isa_model/training/storage/training_storage.py +0 -628
  175. isa_model-0.4.0.dist-info/RECORD +0 -182
  176. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_chatTTS_service.py +0 -0
  177. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_fish_service.py +0 -0
  178. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_openvoice_service.py +0 -0
  179. /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_service_v2.py +0 -0
  180. /isa_model/deployment/{cloud/modal → modal/services/embedding}/isa_embed_rerank_service.py +0 -0
  181. /isa_model/deployment/{cloud/modal → modal/services/video}/isa_video_hunyuan_service.py +0 -0
  182. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ocr_service.py +0 -0
  183. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_qwen25_service.py +0 -0
  184. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_table_service.py +0 -0
  185. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service.py +0 -0
  186. /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service_optimized.py +0 -0
  187. /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
  188. {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/WHEEL +0 -0
  189. {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,645 @@
1
+ """
2
+ Configuration API Routes
3
+
4
+ Provides comprehensive configuration management capabilities including:
5
+ - Provider configuration management
6
+ - Environment configuration
7
+ - System settings management
8
+ - Configuration validation
9
+ - Configuration backup and restore
10
+ """
11
+
12
+ from fastapi import APIRouter, HTTPException, Query, Depends
13
+ from pydantic import BaseModel, Field
14
+ from typing import Optional, List, Dict, Any, Union
15
+ import logging
16
+ from datetime import datetime
17
+ import json
18
+ import os
19
+
20
+ try:
21
+ from ..middleware.auth import optional_auth, require_read_access, require_write_access
22
+ except ImportError:
23
+ # For development/testing when auth is not required
24
+ def optional_auth():
25
+ return {"user_id": "test_user"}
26
+
27
+ def require_read_access():
28
+ return {"user_id": "test_user"}
29
+
30
+ def require_write_access():
31
+ return {"user_id": "test_user"}
32
+
33
+ # Import configuration modules
34
+ from isa_model.core.config.config_manager import ConfigManager
35
+
36
+ logger = logging.getLogger(__name__)
37
+ router = APIRouter()
38
+
39
+ # Request/Response Models
40
+ class ProviderConfigRequest(BaseModel):
41
+ """Request model for provider configuration"""
42
+ provider_name: str = Field(..., description="Provider name (e.g., 'openai', 'replicate')")
43
+ config_data: Dict[str, Any] = Field(..., description="Provider configuration data")
44
+ is_active: bool = Field(True, description="Whether the provider is active")
45
+
46
+ class EnvironmentConfigRequest(BaseModel):
47
+ """Request model for environment configuration"""
48
+ environment: str = Field(..., description="Environment name (e.g., 'development', 'production')")
49
+ config_data: Dict[str, Any] = Field(..., description="Environment configuration data")
50
+
51
+ class ConfigValidationRequest(BaseModel):
52
+ """Request model for configuration validation"""
53
+ config_type: str = Field(..., description="Type of configuration to validate")
54
+ config_data: Dict[str, Any] = Field(..., description="Configuration data to validate")
55
+
56
+ class ConfigBackupRequest(BaseModel):
57
+ """Request model for configuration backup"""
58
+ backup_name: str = Field(..., description="Name for the backup")
59
+ include_secrets: bool = Field(False, description="Whether to include sensitive data")
60
+ components: Optional[List[str]] = Field(None, description="Specific components to backup")
61
+
62
+ class ConfigResponse(BaseModel):
63
+ """Response model for configuration operations"""
64
+ success: bool
65
+ message: str
66
+ data: Optional[Dict[str, Any]] = None
67
+ error: Optional[str] = None
68
+
69
+ # Global config manager instance
70
+ config_manager = ConfigManager()
71
+
72
+ @router.get("/health")
73
+ async def config_health():
74
+ """Health check for configuration service"""
75
+ try:
76
+ health_status = config_manager.get_health_status()
77
+ return {
78
+ "status": "healthy",
79
+ "service": "configuration",
80
+ "components": health_status
81
+ }
82
+ except Exception as e:
83
+ logger.error(f"Config health check failed: {e}")
84
+ return {
85
+ "status": "error",
86
+ "service": "configuration",
87
+ "error": str(e)
88
+ }
89
+
90
+ @router.get("/providers")
91
+ async def get_all_providers(
92
+ include_inactive: bool = Query(False, description="Include inactive providers"),
93
+ user = Depends(require_read_access)
94
+ ):
95
+ """
96
+ Get all provider configurations
97
+ """
98
+ try:
99
+ providers = config_manager.get_all_providers(include_inactive=include_inactive)
100
+
101
+ return {
102
+ "success": True,
103
+ "providers": providers,
104
+ "total_count": len(providers)
105
+ }
106
+
107
+ except Exception as e:
108
+ logger.error(f"Failed to get providers: {e}")
109
+ raise HTTPException(status_code=500, detail=f"Failed to get providers: {str(e)}")
110
+
111
+ @router.get("/providers/{provider_name}")
112
+ async def get_provider_config(
113
+ provider_name: str,
114
+ mask_secrets: bool = Query(True, description="Whether to mask sensitive data"),
115
+ user = Depends(require_read_access)
116
+ ):
117
+ """
118
+ Get configuration for a specific provider
119
+ """
120
+ try:
121
+ config = config_manager.get_provider_config(
122
+ provider_name=provider_name,
123
+ mask_secrets=mask_secrets
124
+ )
125
+
126
+ if not config:
127
+ raise HTTPException(status_code=404, detail=f"Provider not found: {provider_name}")
128
+
129
+ return {
130
+ "success": True,
131
+ "provider_name": provider_name,
132
+ "config": config,
133
+ "masked_secrets": mask_secrets
134
+ }
135
+
136
+ except HTTPException:
137
+ raise
138
+ except Exception as e:
139
+ logger.error(f"Failed to get provider config for {provider_name}: {e}")
140
+ raise HTTPException(status_code=500, detail=f"Failed to get provider config: {str(e)}")
141
+
142
+ @router.put("/providers/{provider_name}")
143
+ async def update_provider_config(
144
+ provider_name: str,
145
+ request: ProviderConfigRequest,
146
+ user = Depends(require_write_access)
147
+ ):
148
+ """
149
+ Update configuration for a specific provider
150
+ """
151
+ try:
152
+ # Validate configuration first
153
+ validation_result = config_manager.validate_provider_config(
154
+ provider_name=provider_name,
155
+ config_data=request.config_data
156
+ )
157
+
158
+ if not validation_result["valid"]:
159
+ return ConfigResponse(
160
+ success=False,
161
+ message="Configuration validation failed",
162
+ error=validation_result.get("error"),
163
+ data={"validation_errors": validation_result.get("errors", [])}
164
+ )
165
+
166
+ # Update configuration
167
+ success = config_manager.update_provider_config(
168
+ provider_name=provider_name,
169
+ config_data=request.config_data,
170
+ is_active=request.is_active,
171
+ updated_by=user.get("user_id") if user else None
172
+ )
173
+
174
+ if not success:
175
+ raise HTTPException(status_code=500, detail="Failed to update provider configuration")
176
+
177
+ return ConfigResponse(
178
+ success=True,
179
+ message=f"Provider {provider_name} configuration updated successfully",
180
+ data={
181
+ "provider_name": provider_name,
182
+ "is_active": request.is_active,
183
+ "config_keys": list(request.config_data.keys())
184
+ }
185
+ )
186
+
187
+ except HTTPException:
188
+ raise
189
+ except Exception as e:
190
+ logger.error(f"Failed to update provider config for {provider_name}: {e}")
191
+ raise HTTPException(status_code=500, detail=f"Failed to update provider config: {str(e)}")
192
+
193
+ @router.post("/providers/{provider_name}/test")
194
+ async def test_provider_config(
195
+ provider_name: str,
196
+ config_data: Optional[Dict[str, Any]] = None,
197
+ user = Depends(require_read_access)
198
+ ):
199
+ """
200
+ Test provider configuration connectivity
201
+ """
202
+ try:
203
+ # Use provided config or get existing config
204
+ test_config = config_data
205
+ if not test_config:
206
+ test_config = config_manager.get_provider_config(provider_name, mask_secrets=False)
207
+ if not test_config:
208
+ raise HTTPException(status_code=404, detail=f"Provider not found: {provider_name}")
209
+
210
+ # Test the configuration
211
+ test_result = config_manager.test_provider_connection(
212
+ provider_name=provider_name,
213
+ config_data=test_config
214
+ )
215
+
216
+ return {
217
+ "success": test_result["success"],
218
+ "provider_name": provider_name,
219
+ "test_result": test_result,
220
+ "message": test_result.get("message", "")
221
+ }
222
+
223
+ except HTTPException:
224
+ raise
225
+ except Exception as e:
226
+ logger.error(f"Failed to test provider config for {provider_name}: {e}")
227
+ raise HTTPException(status_code=500, detail=f"Failed to test provider config: {str(e)}")
228
+
229
+ @router.delete("/providers/{provider_name}")
230
+ async def delete_provider_config(
231
+ provider_name: str,
232
+ user = Depends(require_write_access)
233
+ ):
234
+ """
235
+ Delete provider configuration
236
+ """
237
+ try:
238
+ success = config_manager.delete_provider_config(
239
+ provider_name=provider_name,
240
+ deleted_by=user.get("user_id") if user else None
241
+ )
242
+
243
+ if not success:
244
+ raise HTTPException(status_code=404, detail=f"Provider not found: {provider_name}")
245
+
246
+ return {
247
+ "success": True,
248
+ "message": f"Provider {provider_name} configuration deleted successfully"
249
+ }
250
+
251
+ except HTTPException:
252
+ raise
253
+ except Exception as e:
254
+ logger.error(f"Failed to delete provider config for {provider_name}: {e}")
255
+ raise HTTPException(status_code=500, detail=f"Failed to delete provider config: {str(e)}")
256
+
257
+ @router.get("/environments")
258
+ async def get_all_environments(user = Depends(require_read_access)):
259
+ """
260
+ Get all environment configurations
261
+ """
262
+ try:
263
+ environments = config_manager.get_all_environments()
264
+
265
+ return {
266
+ "success": True,
267
+ "environments": environments,
268
+ "total_count": len(environments)
269
+ }
270
+
271
+ except Exception as e:
272
+ logger.error(f"Failed to get environments: {e}")
273
+ raise HTTPException(status_code=500, detail=f"Failed to get environments: {str(e)}")
274
+
275
+ @router.get("/environments/{environment}")
276
+ async def get_environment_config(
277
+ environment: str,
278
+ mask_secrets: bool = Query(True, description="Whether to mask sensitive data"),
279
+ user = Depends(require_read_access)
280
+ ):
281
+ """
282
+ Get configuration for a specific environment
283
+ """
284
+ try:
285
+ config = config_manager.get_environment_config(
286
+ environment=environment,
287
+ mask_secrets=mask_secrets
288
+ )
289
+
290
+ if not config:
291
+ raise HTTPException(status_code=404, detail=f"Environment not found: {environment}")
292
+
293
+ return {
294
+ "success": True,
295
+ "environment": environment,
296
+ "config": config,
297
+ "masked_secrets": mask_secrets
298
+ }
299
+
300
+ except HTTPException:
301
+ raise
302
+ except Exception as e:
303
+ logger.error(f"Failed to get environment config for {environment}: {e}")
304
+ raise HTTPException(status_code=500, detail=f"Failed to get environment config: {str(e)}")
305
+
306
+ @router.put("/environments/{environment}")
307
+ async def update_environment_config(
308
+ environment: str,
309
+ request: EnvironmentConfigRequest,
310
+ user = Depends(require_write_access)
311
+ ):
312
+ """
313
+ Update configuration for a specific environment
314
+ """
315
+ try:
316
+ # Validate environment configuration
317
+ validation_result = config_manager.validate_environment_config(
318
+ environment=environment,
319
+ config_data=request.config_data
320
+ )
321
+
322
+ if not validation_result["valid"]:
323
+ return ConfigResponse(
324
+ success=False,
325
+ message="Environment configuration validation failed",
326
+ error=validation_result.get("error"),
327
+ data={"validation_errors": validation_result.get("errors", [])}
328
+ )
329
+
330
+ # Update configuration
331
+ success = config_manager.update_environment_config(
332
+ environment=environment,
333
+ config_data=request.config_data,
334
+ updated_by=user.get("user_id") if user else None
335
+ )
336
+
337
+ if not success:
338
+ raise HTTPException(status_code=500, detail="Failed to update environment configuration")
339
+
340
+ return ConfigResponse(
341
+ success=True,
342
+ message=f"Environment {environment} configuration updated successfully",
343
+ data={
344
+ "environment": environment,
345
+ "config_keys": list(request.config_data.keys())
346
+ }
347
+ )
348
+
349
+ except HTTPException:
350
+ raise
351
+ except Exception as e:
352
+ logger.error(f"Failed to update environment config for {environment}: {e}")
353
+ raise HTTPException(status_code=500, detail=f"Failed to update environment config: {str(e)}")
354
+
355
+ @router.post("/validate")
356
+ async def validate_configuration(
357
+ request: ConfigValidationRequest,
358
+ user = Depends(require_read_access)
359
+ ):
360
+ """
361
+ Validate configuration data
362
+ """
363
+ try:
364
+ if request.config_type == "provider":
365
+ # Extract provider name from config data or use a default validation
366
+ provider_name = request.config_data.get("provider_name", "generic")
367
+ validation_result = config_manager.validate_provider_config(
368
+ provider_name=provider_name,
369
+ config_data=request.config_data
370
+ )
371
+ elif request.config_type == "environment":
372
+ environment = request.config_data.get("environment", "generic")
373
+ validation_result = config_manager.validate_environment_config(
374
+ environment=environment,
375
+ config_data=request.config_data
376
+ )
377
+ else:
378
+ # Generic validation
379
+ validation_result = config_manager.validate_generic_config(
380
+ config_type=request.config_type,
381
+ config_data=request.config_data
382
+ )
383
+
384
+ return {
385
+ "success": True,
386
+ "config_type": request.config_type,
387
+ "validation_result": validation_result
388
+ }
389
+
390
+ except Exception as e:
391
+ logger.error(f"Failed to validate configuration: {e}")
392
+ raise HTTPException(status_code=500, detail=f"Failed to validate configuration: {str(e)}")
393
+
394
+ @router.get("/settings")
395
+ async def get_system_settings(
396
+ category: Optional[str] = Query(None, description="Filter by settings category"),
397
+ user = Depends(require_read_access)
398
+ ):
399
+ """
400
+ Get system settings
401
+ """
402
+ try:
403
+ settings = config_manager.get_system_settings(category=category)
404
+
405
+ return {
406
+ "success": True,
407
+ "settings": settings,
408
+ "category": category
409
+ }
410
+
411
+ except Exception as e:
412
+ logger.error(f"Failed to get system settings: {e}")
413
+ raise HTTPException(status_code=500, detail=f"Failed to get system settings: {str(e)}")
414
+
415
+ @router.put("/settings/{setting_key}")
416
+ async def update_system_setting(
417
+ setting_key: str,
418
+ setting_value: Any,
419
+ user = Depends(require_write_access)
420
+ ):
421
+ """
422
+ Update a specific system setting
423
+ """
424
+ try:
425
+ success = config_manager.update_system_setting(
426
+ setting_key=setting_key,
427
+ setting_value=setting_value,
428
+ updated_by=user.get("user_id") if user else None
429
+ )
430
+
431
+ if not success:
432
+ raise HTTPException(status_code=500, detail="Failed to update system setting")
433
+
434
+ return {
435
+ "success": True,
436
+ "message": f"System setting {setting_key} updated successfully",
437
+ "setting_key": setting_key,
438
+ "setting_value": setting_value
439
+ }
440
+
441
+ except HTTPException:
442
+ raise
443
+ except Exception as e:
444
+ logger.error(f"Failed to update system setting {setting_key}: {e}")
445
+ raise HTTPException(status_code=500, detail=f"Failed to update system setting: {str(e)}")
446
+
447
+ @router.post("/backup")
448
+ async def create_configuration_backup(
449
+ request: ConfigBackupRequest,
450
+ user = Depends(require_write_access)
451
+ ):
452
+ """
453
+ Create a backup of configuration data
454
+ """
455
+ try:
456
+ backup_result = config_manager.create_backup(
457
+ backup_name=request.backup_name,
458
+ include_secrets=request.include_secrets,
459
+ components=request.components,
460
+ created_by=user.get("user_id") if user else None
461
+ )
462
+
463
+ return {
464
+ "success": backup_result["success"],
465
+ "message": backup_result.get("message", "Backup created successfully"),
466
+ "backup_info": {
467
+ "backup_name": request.backup_name,
468
+ "backup_id": backup_result.get("backup_id"),
469
+ "created_at": backup_result.get("created_at"),
470
+ "include_secrets": request.include_secrets,
471
+ "components": request.components or "all"
472
+ }
473
+ }
474
+
475
+ except Exception as e:
476
+ logger.error(f"Failed to create configuration backup: {e}")
477
+ raise HTTPException(status_code=500, detail=f"Failed to create backup: {str(e)}")
478
+
479
+ @router.get("/backups")
480
+ async def list_configuration_backups(
481
+ limit: int = Query(50, ge=1, le=200, description="Maximum number of backups to return"),
482
+ user = Depends(require_read_access)
483
+ ):
484
+ """
485
+ List available configuration backups
486
+ """
487
+ try:
488
+ backups = config_manager.list_backups(limit=limit)
489
+
490
+ return {
491
+ "success": True,
492
+ "backups": backups,
493
+ "total_count": len(backups)
494
+ }
495
+
496
+ except Exception as e:
497
+ logger.error(f"Failed to list configuration backups: {e}")
498
+ raise HTTPException(status_code=500, detail=f"Failed to list backups: {str(e)}")
499
+
500
+ @router.post("/restore/{backup_id}")
501
+ async def restore_configuration_backup(
502
+ backup_id: str,
503
+ components: Optional[List[str]] = None,
504
+ dry_run: bool = Query(False, description="Perform a dry run without making changes"),
505
+ user = Depends(require_write_access)
506
+ ):
507
+ """
508
+ Restore configuration from a backup
509
+ """
510
+ try:
511
+ restore_result = config_manager.restore_backup(
512
+ backup_id=backup_id,
513
+ components=components,
514
+ dry_run=dry_run,
515
+ restored_by=user.get("user_id") if user else None
516
+ )
517
+
518
+ return {
519
+ "success": restore_result["success"],
520
+ "message": restore_result.get("message", "Configuration restored successfully"),
521
+ "restore_info": {
522
+ "backup_id": backup_id,
523
+ "components": components or "all",
524
+ "dry_run": dry_run,
525
+ "changes_applied": restore_result.get("changes_applied", [])
526
+ }
527
+ }
528
+
529
+ except Exception as e:
530
+ logger.error(f"Failed to restore configuration backup {backup_id}: {e}")
531
+ raise HTTPException(status_code=500, detail=f"Failed to restore backup: {str(e)}")
532
+
533
+ @router.get("/export")
534
+ async def export_configuration(
535
+ format_type: str = Query("json", description="Export format (json, yaml)"),
536
+ components: Optional[List[str]] = Query(None, description="Specific components to export"),
537
+ mask_secrets: bool = Query(True, description="Whether to mask sensitive data"),
538
+ user = Depends(require_read_access)
539
+ ):
540
+ """
541
+ Export configuration data
542
+ """
543
+ try:
544
+ export_result = config_manager.export_configuration(
545
+ format_type=format_type,
546
+ components=components,
547
+ mask_secrets=mask_secrets
548
+ )
549
+
550
+ return {
551
+ "success": True,
552
+ "export_format": format_type,
553
+ "components": components or "all",
554
+ "masked_secrets": mask_secrets,
555
+ "data": export_result["data"],
556
+ "metadata": export_result.get("metadata", {})
557
+ }
558
+
559
+ except Exception as e:
560
+ logger.error(f"Failed to export configuration: {e}")
561
+ raise HTTPException(status_code=500, detail=f"Failed to export configuration: {str(e)}")
562
+
563
+ @router.post("/import")
564
+ async def import_configuration(
565
+ import_data: Dict[str, Any],
566
+ merge_strategy: str = Query("merge", description="Import strategy (merge, replace)"),
567
+ validate_only: bool = Query(False, description="Only validate without importing"),
568
+ user = Depends(require_write_access)
569
+ ):
570
+ """
571
+ Import configuration data
572
+ """
573
+ try:
574
+ import_result = config_manager.import_configuration(
575
+ import_data=import_data,
576
+ merge_strategy=merge_strategy,
577
+ validate_only=validate_only,
578
+ imported_by=user.get("user_id") if user else None
579
+ )
580
+
581
+ return {
582
+ "success": import_result["success"],
583
+ "message": import_result.get("message", "Configuration imported successfully"),
584
+ "import_info": {
585
+ "merge_strategy": merge_strategy,
586
+ "validate_only": validate_only,
587
+ "changes_applied": import_result.get("changes_applied", []),
588
+ "validation_errors": import_result.get("validation_errors", [])
589
+ }
590
+ }
591
+
592
+ except Exception as e:
593
+ logger.error(f"Failed to import configuration: {e}")
594
+ raise HTTPException(status_code=500, detail=f"Failed to import configuration: {str(e)}")
595
+
596
+ @router.get("/audit")
597
+ async def get_configuration_audit_log(
598
+ component: Optional[str] = Query(None, description="Filter by component"),
599
+ action: Optional[str] = Query(None, description="Filter by action type"),
600
+ user_id: Optional[str] = Query(None, description="Filter by user ID"),
601
+ start_date: Optional[str] = Query(None, description="Start date (ISO format)"),
602
+ end_date: Optional[str] = Query(None, description="End date (ISO format)"),
603
+ limit: int = Query(100, ge=1, le=1000, description="Maximum number of records"),
604
+ user = Depends(require_read_access)
605
+ ):
606
+ """
607
+ Get configuration audit log
608
+ """
609
+ try:
610
+ # Parse dates if provided
611
+ from datetime import datetime
612
+ start_dt = None
613
+ end_dt = None
614
+
615
+ if start_date:
616
+ start_dt = datetime.fromisoformat(start_date.replace('Z', '+00:00'))
617
+ if end_date:
618
+ end_dt = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
619
+
620
+ audit_log = config_manager.get_audit_log(
621
+ component=component,
622
+ action=action,
623
+ user_id=user_id,
624
+ start_date=start_dt,
625
+ end_date=end_dt,
626
+ limit=limit
627
+ )
628
+
629
+ return {
630
+ "success": True,
631
+ "audit_log": audit_log,
632
+ "filters": {
633
+ "component": component,
634
+ "action": action,
635
+ "user_id": user_id,
636
+ "start_date": start_date,
637
+ "end_date": end_date,
638
+ "limit": limit
639
+ },
640
+ "total_records": len(audit_log)
641
+ }
642
+
643
+ except Exception as e:
644
+ logger.error(f"Failed to get configuration audit log: {e}")
645
+ raise HTTPException(status_code=500, detail=f"Failed to get audit log: {str(e)}")