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.
- isa_model/client.py +466 -43
- isa_model/core/cache/redis_cache.py +12 -3
- isa_model/core/config/config_manager.py +230 -3
- isa_model/core/config.py +90 -0
- isa_model/core/database/direct_db_client.py +114 -0
- isa_model/core/database/migration_manager.py +563 -0
- isa_model/core/database/migrations.py +21 -1
- isa_model/core/database/supabase_client.py +154 -19
- isa_model/core/dependencies.py +316 -0
- isa_model/core/discovery/__init__.py +19 -0
- isa_model/core/discovery/consul_discovery.py +190 -0
- isa_model/core/logging/__init__.py +54 -0
- isa_model/core/logging/influx_logger.py +523 -0
- isa_model/core/logging/loki_logger.py +160 -0
- isa_model/core/models/__init__.py +27 -18
- isa_model/core/models/config_models.py +625 -0
- isa_model/core/models/deployment_billing_tracker.py +430 -0
- isa_model/core/models/model_manager.py +35 -80
- isa_model/core/models/model_metadata.py +690 -0
- isa_model/core/models/model_repo.py +174 -18
- isa_model/core/models/system_models.py +857 -0
- isa_model/core/repositories/__init__.py +9 -0
- isa_model/core/repositories/config_repository.py +912 -0
- isa_model/core/services/intelligent_model_selector.py +399 -21
- isa_model/core/types.py +1 -0
- isa_model/deployment/__init__.py +5 -48
- isa_model/deployment/core/__init__.py +2 -31
- isa_model/deployment/core/deployment_manager.py +1278 -370
- isa_model/deployment/modal/__init__.py +8 -0
- isa_model/deployment/modal/config.py +136 -0
- isa_model/deployment/{services/auto_hf_modal_deployer.py → modal/deployer.py} +1 -1
- isa_model/deployment/modal/services/__init__.py +3 -0
- isa_model/deployment/modal/services/audio/__init__.py +1 -0
- isa_model/deployment/modal/services/embedding/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/__init__.py +1 -0
- isa_model/deployment/modal/services/llm/isa_llm_service.py +424 -0
- isa_model/deployment/modal/services/video/__init__.py +1 -0
- isa_model/deployment/modal/services/vision/__init__.py +1 -0
- isa_model/deployment/models/org-org-acme-corp-tenant-a-service-llm-20250825-225822/tenant-a-service_modal_service.py +48 -0
- isa_model/deployment/models/org-test-org-123-prefix-test-service-llm-20250825-225822/prefix-test-service_modal_service.py +48 -0
- isa_model/deployment/models/test-llm-service-llm-20250825-204442/test-llm-service_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-212906/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/models/test-monitoring-gpt2-llm-20250825-213009/test-monitoring-gpt2_modal_service.py +48 -0
- isa_model/deployment/storage/__init__.py +5 -0
- isa_model/deployment/storage/deployment_repository.py +824 -0
- isa_model/deployment/triton/__init__.py +10 -0
- isa_model/deployment/triton/config.py +196 -0
- isa_model/deployment/triton/configs/__init__.py +1 -0
- isa_model/deployment/triton/provider.py +512 -0
- isa_model/deployment/triton/scripts/__init__.py +1 -0
- isa_model/deployment/triton/templates/__init__.py +1 -0
- isa_model/inference/__init__.py +47 -1
- isa_model/inference/ai_factory.py +137 -10
- isa_model/inference/legacy_services/__init__.py +21 -0
- isa_model/inference/legacy_services/model_evaluation.py +637 -0
- isa_model/inference/legacy_services/model_service.py +573 -0
- isa_model/inference/legacy_services/model_serving.py +717 -0
- isa_model/inference/legacy_services/model_training.py +561 -0
- isa_model/inference/models/__init__.py +21 -0
- isa_model/inference/models/inference_config.py +551 -0
- isa_model/inference/models/inference_record.py +675 -0
- isa_model/inference/models/performance_models.py +714 -0
- isa_model/inference/repositories/__init__.py +9 -0
- isa_model/inference/repositories/inference_repository.py +828 -0
- isa_model/inference/services/audio/base_stt_service.py +184 -11
- isa_model/inference/services/audio/openai_stt_service.py +22 -6
- isa_model/inference/services/embedding/ollama_embed_service.py +15 -3
- isa_model/inference/services/embedding/resilient_embed_service.py +285 -0
- isa_model/inference/services/llm/__init__.py +10 -2
- isa_model/inference/services/llm/base_llm_service.py +335 -24
- isa_model/inference/services/llm/cerebras_llm_service.py +628 -0
- isa_model/inference/services/llm/helpers/llm_adapter.py +9 -4
- isa_model/inference/services/llm/helpers/llm_prompts.py +342 -0
- isa_model/inference/services/llm/helpers/llm_utils.py +321 -23
- isa_model/inference/services/llm/huggingface_llm_service.py +581 -0
- isa_model/inference/services/llm/ollama_llm_service.py +9 -2
- isa_model/inference/services/llm/openai_llm_service.py +33 -16
- isa_model/inference/services/llm/yyds_llm_service.py +8 -2
- isa_model/inference/services/vision/__init__.py +22 -1
- isa_model/inference/services/vision/helpers/image_utils.py +8 -5
- isa_model/inference/services/vision/isa_vision_service.py +65 -4
- isa_model/inference/services/vision/openai_vision_service.py +19 -10
- isa_model/inference/services/vision/vgg16_vision_service.py +257 -0
- isa_model/serving/api/cache_manager.py +245 -0
- isa_model/serving/api/dependencies/__init__.py +1 -0
- isa_model/serving/api/dependencies/auth.py +194 -0
- isa_model/serving/api/dependencies/database.py +139 -0
- isa_model/serving/api/error_handlers.py +284 -0
- isa_model/serving/api/fastapi_server.py +172 -22
- isa_model/serving/api/middleware/auth.py +8 -2
- isa_model/serving/api/middleware/security.py +23 -33
- isa_model/serving/api/middleware/tenant_context.py +414 -0
- isa_model/serving/api/routes/analytics.py +4 -1
- isa_model/serving/api/routes/config.py +645 -0
- isa_model/serving/api/routes/deployment_billing.py +315 -0
- isa_model/serving/api/routes/deployments.py +138 -2
- isa_model/serving/api/routes/gpu_gateway.py +440 -0
- isa_model/serving/api/routes/health.py +32 -12
- isa_model/serving/api/routes/inference_monitoring.py +486 -0
- isa_model/serving/api/routes/local_deployments.py +448 -0
- isa_model/serving/api/routes/tenants.py +575 -0
- isa_model/serving/api/routes/unified.py +680 -18
- isa_model/serving/api/routes/webhooks.py +479 -0
- isa_model/serving/api/startup.py +68 -54
- isa_model/utils/gpu_utils.py +311 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/METADATA +71 -24
- isa_model-0.4.4.dist-info/RECORD +180 -0
- isa_model/core/security/secrets.py +0 -358
- isa_model/core/storage/hf_storage.py +0 -419
- isa_model/core/storage/minio_storage.py +0 -0
- isa_model/deployment/cloud/__init__.py +0 -9
- isa_model/deployment/cloud/modal/__init__.py +0 -10
- isa_model/deployment/core/deployment_config.py +0 -356
- isa_model/deployment/core/isa_deployment_service.py +0 -401
- isa_model/deployment/gpu_int8_ds8/app/server.py +0 -66
- isa_model/deployment/gpu_int8_ds8/scripts/test_client.py +0 -43
- isa_model/deployment/gpu_int8_ds8/scripts/test_client_os.py +0 -35
- isa_model/deployment/runtime/deployed_service.py +0 -338
- isa_model/deployment/services/__init__.py +0 -9
- isa_model/deployment/services/auto_deploy_vision_service.py +0 -538
- isa_model/deployment/services/model_service.py +0 -332
- isa_model/deployment/services/service_monitor.py +0 -356
- isa_model/deployment/services/service_registry.py +0 -527
- isa_model/eval/__init__.py +0 -92
- isa_model/eval/benchmarks/__init__.py +0 -27
- isa_model/eval/benchmarks/multimodal_datasets.py +0 -460
- isa_model/eval/benchmarks.py +0 -701
- isa_model/eval/config/__init__.py +0 -10
- isa_model/eval/config/evaluation_config.py +0 -108
- isa_model/eval/evaluators/__init__.py +0 -24
- isa_model/eval/evaluators/audio_evaluator.py +0 -727
- isa_model/eval/evaluators/base_evaluator.py +0 -503
- isa_model/eval/evaluators/embedding_evaluator.py +0 -742
- isa_model/eval/evaluators/llm_evaluator.py +0 -472
- isa_model/eval/evaluators/vision_evaluator.py +0 -564
- isa_model/eval/example_evaluation.py +0 -395
- isa_model/eval/factory.py +0 -798
- isa_model/eval/infrastructure/__init__.py +0 -24
- isa_model/eval/infrastructure/experiment_tracker.py +0 -466
- isa_model/eval/isa_benchmarks.py +0 -700
- isa_model/eval/isa_integration.py +0 -582
- isa_model/eval/metrics.py +0 -951
- isa_model/eval/tests/unit/test_basic.py +0 -396
- isa_model/serving/api/routes/evaluations.py +0 -579
- isa_model/training/__init__.py +0 -168
- isa_model/training/annotation/annotation_schema.py +0 -47
- isa_model/training/annotation/processors/annotation_processor.py +0 -126
- isa_model/training/annotation/storage/dataset_manager.py +0 -131
- isa_model/training/annotation/storage/dataset_schema.py +0 -44
- isa_model/training/annotation/tests/test_annotation_flow.py +0 -109
- isa_model/training/annotation/tests/test_minio copy.py +0 -113
- isa_model/training/annotation/tests/test_minio_upload.py +0 -43
- isa_model/training/annotation/views/annotation_controller.py +0 -158
- isa_model/training/cloud/__init__.py +0 -22
- isa_model/training/cloud/job_orchestrator.py +0 -402
- isa_model/training/cloud/runpod_trainer.py +0 -454
- isa_model/training/cloud/storage_manager.py +0 -482
- isa_model/training/core/__init__.py +0 -26
- isa_model/training/core/config.py +0 -181
- isa_model/training/core/dataset.py +0 -222
- isa_model/training/core/trainer.py +0 -720
- isa_model/training/core/utils.py +0 -213
- isa_model/training/examples/intelligent_training_example.py +0 -281
- isa_model/training/factory.py +0 -424
- isa_model/training/intelligent/__init__.py +0 -25
- isa_model/training/intelligent/decision_engine.py +0 -643
- isa_model/training/intelligent/intelligent_factory.py +0 -888
- isa_model/training/intelligent/knowledge_base.py +0 -751
- isa_model/training/intelligent/resource_optimizer.py +0 -839
- isa_model/training/intelligent/task_classifier.py +0 -576
- isa_model/training/storage/__init__.py +0 -24
- isa_model/training/storage/core_integration.py +0 -439
- isa_model/training/storage/training_repository.py +0 -552
- isa_model/training/storage/training_storage.py +0 -628
- isa_model-0.4.0.dist-info/RECORD +0 -182
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_chatTTS_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_fish_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_openvoice_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/audio}/isa_audio_service_v2.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/embedding}/isa_embed_rerank_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/video}/isa_video_hunyuan_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ocr_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_qwen25_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_table_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service.py +0 -0
- /isa_model/deployment/{cloud/modal → modal/services/vision}/isa_vision_ui_service_optimized.py +0 -0
- /isa_model/deployment/{services → modal/services/vision}/simple_auto_deploy_vision_service.py +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/WHEEL +0 -0
- {isa_model-0.4.0.dist-info → isa_model-0.4.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,912 @@
|
|
1
|
+
"""
|
2
|
+
Configuration Repository - Data persistence layer for configuration management
|
3
|
+
|
4
|
+
Provides standardized data access for configuration data, provider settings, and environment configurations
|
5
|
+
following the ISA Model architecture pattern.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import logging
|
9
|
+
import json
|
10
|
+
import os
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
from typing import Dict, List, Optional, Any, Union
|
13
|
+
from pathlib import Path
|
14
|
+
from dataclasses import dataclass, asdict
|
15
|
+
from enum import Enum
|
16
|
+
|
17
|
+
try:
|
18
|
+
# Try to import Supabase for centralized data storage
|
19
|
+
from ...core.database.supabase_client import get_supabase_client
|
20
|
+
SUPABASE_AVAILABLE = True
|
21
|
+
except ImportError:
|
22
|
+
SUPABASE_AVAILABLE = False
|
23
|
+
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
class ConfigType(str, Enum):
|
27
|
+
"""Configuration type enumeration"""
|
28
|
+
PROVIDER = "provider"
|
29
|
+
ENVIRONMENT = "environment"
|
30
|
+
SYSTEM = "system"
|
31
|
+
USER = "user"
|
32
|
+
API_KEY = "api_key"
|
33
|
+
FEATURE_FLAG = "feature_flag"
|
34
|
+
|
35
|
+
@dataclass
|
36
|
+
class ConfigRecord:
|
37
|
+
"""Configuration record model"""
|
38
|
+
config_id: str
|
39
|
+
config_type: str
|
40
|
+
config_key: str
|
41
|
+
config_value: Any
|
42
|
+
environment: str = "production"
|
43
|
+
is_active: bool = True
|
44
|
+
is_encrypted: bool = False
|
45
|
+
created_at: datetime = None
|
46
|
+
updated_at: datetime = None
|
47
|
+
created_by: Optional[str] = None
|
48
|
+
updated_by: Optional[str] = None
|
49
|
+
description: Optional[str] = None
|
50
|
+
tags: Optional[Dict[str, str]] = None
|
51
|
+
metadata: Optional[Dict[str, Any]] = None
|
52
|
+
|
53
|
+
def __post_init__(self):
|
54
|
+
if self.created_at is None:
|
55
|
+
self.created_at = datetime.now(timezone.utc)
|
56
|
+
if self.updated_at is None:
|
57
|
+
self.updated_at = self.created_at
|
58
|
+
|
59
|
+
@dataclass
|
60
|
+
class ProviderConfig:
|
61
|
+
"""Provider configuration model"""
|
62
|
+
provider_name: str
|
63
|
+
config_data: Dict[str, Any]
|
64
|
+
is_active: bool = True
|
65
|
+
environment: str = "production"
|
66
|
+
created_at: datetime = None
|
67
|
+
updated_at: datetime = None
|
68
|
+
created_by: Optional[str] = None
|
69
|
+
updated_by: Optional[str] = None
|
70
|
+
|
71
|
+
def __post_init__(self):
|
72
|
+
if self.created_at is None:
|
73
|
+
self.created_at = datetime.now(timezone.utc)
|
74
|
+
if self.updated_at is None:
|
75
|
+
self.updated_at = self.created_at
|
76
|
+
|
77
|
+
@dataclass
|
78
|
+
class EnvironmentConfig:
|
79
|
+
"""Environment configuration model"""
|
80
|
+
environment: str
|
81
|
+
config_data: Dict[str, Any]
|
82
|
+
is_active: bool = True
|
83
|
+
created_at: datetime = None
|
84
|
+
updated_at: datetime = None
|
85
|
+
created_by: Optional[str] = None
|
86
|
+
updated_by: Optional[str] = None
|
87
|
+
|
88
|
+
def __post_init__(self):
|
89
|
+
if self.created_at is None:
|
90
|
+
self.created_at = datetime.now(timezone.utc)
|
91
|
+
if self.updated_at is None:
|
92
|
+
self.updated_at = self.created_at
|
93
|
+
|
94
|
+
@dataclass
|
95
|
+
class ConfigAuditLog:
|
96
|
+
"""Configuration audit log model"""
|
97
|
+
audit_id: str
|
98
|
+
config_id: str
|
99
|
+
action: str # create, update, delete, read
|
100
|
+
old_value: Optional[Any] = None
|
101
|
+
new_value: Optional[Any] = None
|
102
|
+
user_id: Optional[str] = None
|
103
|
+
timestamp: datetime = None
|
104
|
+
ip_address: Optional[str] = None
|
105
|
+
user_agent: Optional[str] = None
|
106
|
+
|
107
|
+
def __post_init__(self):
|
108
|
+
if self.timestamp is None:
|
109
|
+
self.timestamp = datetime.now(timezone.utc)
|
110
|
+
|
111
|
+
class ConfigRepository:
|
112
|
+
"""
|
113
|
+
Repository for configuration data persistence
|
114
|
+
|
115
|
+
Supports multiple backend storage options:
|
116
|
+
1. Environment variables (for sensitive data)
|
117
|
+
2. File system (for development and local configs)
|
118
|
+
3. Supabase (for centralized storage)
|
119
|
+
4. In-memory (for testing)
|
120
|
+
"""
|
121
|
+
|
122
|
+
def __init__(self, storage_backend: str = "auto", **kwargs):
|
123
|
+
"""
|
124
|
+
Initialize configuration repository
|
125
|
+
|
126
|
+
Args:
|
127
|
+
storage_backend: "env", "file", "supabase", "memory", or "auto"
|
128
|
+
**kwargs: Backend-specific configuration
|
129
|
+
"""
|
130
|
+
self.storage_backend = self._determine_backend(storage_backend)
|
131
|
+
self.config = kwargs
|
132
|
+
|
133
|
+
# Initialize storage backend
|
134
|
+
if self.storage_backend == "supabase":
|
135
|
+
self._init_supabase()
|
136
|
+
elif self.storage_backend == "env":
|
137
|
+
self._init_env()
|
138
|
+
elif self.storage_backend == "memory":
|
139
|
+
self._init_memory()
|
140
|
+
else: # file system
|
141
|
+
self._init_file_system()
|
142
|
+
|
143
|
+
logger.info(f"Configuration repository initialized with {self.storage_backend} backend")
|
144
|
+
|
145
|
+
def _determine_backend(self, preference: str) -> str:
|
146
|
+
"""Determine the best available storage backend"""
|
147
|
+
if preference == "supabase" and SUPABASE_AVAILABLE:
|
148
|
+
return "supabase"
|
149
|
+
elif preference in ["env", "file", "memory", "supabase"]:
|
150
|
+
return preference
|
151
|
+
|
152
|
+
# Auto-select best available backend
|
153
|
+
if SUPABASE_AVAILABLE:
|
154
|
+
return "supabase"
|
155
|
+
else:
|
156
|
+
return "file"
|
157
|
+
|
158
|
+
def _init_supabase(self):
|
159
|
+
"""Initialize Supabase backend"""
|
160
|
+
try:
|
161
|
+
self.supabase_client = get_supabase_client()
|
162
|
+
self._ensure_supabase_tables()
|
163
|
+
logger.info("Supabase backend initialized for configurations")
|
164
|
+
except Exception as e:
|
165
|
+
logger.error(f"Failed to initialize Supabase backend: {e}")
|
166
|
+
self.storage_backend = "file"
|
167
|
+
self._init_file_system()
|
168
|
+
|
169
|
+
def _init_file_system(self):
|
170
|
+
"""Initialize file system backend"""
|
171
|
+
self.config_dir = Path(self.config.get("config_dir", "./config_data"))
|
172
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
173
|
+
|
174
|
+
# Create subdirectories
|
175
|
+
(self.config_dir / "providers").mkdir(exist_ok=True)
|
176
|
+
(self.config_dir / "environments").mkdir(exist_ok=True)
|
177
|
+
(self.config_dir / "system").mkdir(exist_ok=True)
|
178
|
+
(self.config_dir / "audit").mkdir(exist_ok=True)
|
179
|
+
|
180
|
+
logger.info(f"File system backend initialized: {self.config_dir}")
|
181
|
+
|
182
|
+
def _init_env(self):
|
183
|
+
"""Initialize environment variables backend"""
|
184
|
+
self.env_prefix = self.config.get("env_prefix", "ISA_")
|
185
|
+
logger.info(f"Environment variables backend initialized with prefix: {self.env_prefix}")
|
186
|
+
|
187
|
+
def _init_memory(self):
|
188
|
+
"""Initialize in-memory backend for testing"""
|
189
|
+
self.configs = {}
|
190
|
+
self.providers = {}
|
191
|
+
self.environments = {}
|
192
|
+
self.audit_logs = []
|
193
|
+
logger.info("In-memory backend initialized for configurations")
|
194
|
+
|
195
|
+
def _ensure_supabase_tables(self):
|
196
|
+
"""Ensure required Supabase tables exist"""
|
197
|
+
try:
|
198
|
+
self.supabase_client.table("config_records").select("config_id").limit(1).execute()
|
199
|
+
self.supabase_client.table("provider_configs").select("provider_name").limit(1).execute()
|
200
|
+
self.supabase_client.table("environment_configs").select("environment").limit(1).execute()
|
201
|
+
self.supabase_client.table("config_audit_logs").select("audit_id").limit(1).execute()
|
202
|
+
except Exception as e:
|
203
|
+
logger.warning(f"Some configuration tables may not exist in Supabase: {e}")
|
204
|
+
|
205
|
+
# Provider Configuration Methods
|
206
|
+
|
207
|
+
def get_provider_config(
|
208
|
+
self,
|
209
|
+
provider_name: str,
|
210
|
+
environment: str = "production",
|
211
|
+
mask_secrets: bool = True
|
212
|
+
) -> Optional[Dict[str, Any]]:
|
213
|
+
"""Get provider configuration"""
|
214
|
+
if self.storage_backend == "supabase":
|
215
|
+
return self._get_provider_config_supabase(provider_name, environment, mask_secrets)
|
216
|
+
elif self.storage_backend == "env":
|
217
|
+
return self._get_provider_config_env(provider_name, environment, mask_secrets)
|
218
|
+
elif self.storage_backend == "memory":
|
219
|
+
return self._get_provider_config_memory(provider_name, environment, mask_secrets)
|
220
|
+
else:
|
221
|
+
return self._get_provider_config_file(provider_name, environment, mask_secrets)
|
222
|
+
|
223
|
+
def update_provider_config(
|
224
|
+
self,
|
225
|
+
provider_name: str,
|
226
|
+
config_data: Dict[str, Any],
|
227
|
+
environment: str = "production",
|
228
|
+
is_active: bool = True,
|
229
|
+
updated_by: Optional[str] = None
|
230
|
+
) -> bool:
|
231
|
+
"""Update provider configuration"""
|
232
|
+
if self.storage_backend == "supabase":
|
233
|
+
return self._update_provider_config_supabase(provider_name, config_data, environment, is_active, updated_by)
|
234
|
+
elif self.storage_backend == "env":
|
235
|
+
return self._update_provider_config_env(provider_name, config_data, environment, is_active, updated_by)
|
236
|
+
elif self.storage_backend == "memory":
|
237
|
+
return self._update_provider_config_memory(provider_name, config_data, environment, is_active, updated_by)
|
238
|
+
else:
|
239
|
+
return self._update_provider_config_file(provider_name, config_data, environment, is_active, updated_by)
|
240
|
+
|
241
|
+
def list_provider_configs(
|
242
|
+
self,
|
243
|
+
environment: str = "production",
|
244
|
+
include_inactive: bool = False
|
245
|
+
) -> List[ProviderConfig]:
|
246
|
+
"""List all provider configurations"""
|
247
|
+
if self.storage_backend == "supabase":
|
248
|
+
return self._list_provider_configs_supabase(environment, include_inactive)
|
249
|
+
elif self.storage_backend == "env":
|
250
|
+
return self._list_provider_configs_env(environment, include_inactive)
|
251
|
+
elif self.storage_backend == "memory":
|
252
|
+
return self._list_provider_configs_memory(environment, include_inactive)
|
253
|
+
else:
|
254
|
+
return self._list_provider_configs_file(environment, include_inactive)
|
255
|
+
|
256
|
+
def delete_provider_config(
|
257
|
+
self,
|
258
|
+
provider_name: str,
|
259
|
+
environment: str = "production",
|
260
|
+
deleted_by: Optional[str] = None
|
261
|
+
) -> bool:
|
262
|
+
"""Delete provider configuration"""
|
263
|
+
if self.storage_backend == "supabase":
|
264
|
+
return self._delete_provider_config_supabase(provider_name, environment, deleted_by)
|
265
|
+
elif self.storage_backend == "env":
|
266
|
+
return self._delete_provider_config_env(provider_name, environment, deleted_by)
|
267
|
+
elif self.storage_backend == "memory":
|
268
|
+
return self._delete_provider_config_memory(provider_name, environment, deleted_by)
|
269
|
+
else:
|
270
|
+
return self._delete_provider_config_file(provider_name, environment, deleted_by)
|
271
|
+
|
272
|
+
# Environment Configuration Methods
|
273
|
+
|
274
|
+
def get_environment_config(
|
275
|
+
self,
|
276
|
+
environment: str,
|
277
|
+
mask_secrets: bool = True
|
278
|
+
) -> Optional[Dict[str, Any]]:
|
279
|
+
"""Get environment configuration"""
|
280
|
+
if self.storage_backend == "supabase":
|
281
|
+
return self._get_environment_config_supabase(environment, mask_secrets)
|
282
|
+
elif self.storage_backend == "env":
|
283
|
+
return self._get_environment_config_env(environment, mask_secrets)
|
284
|
+
elif self.storage_backend == "memory":
|
285
|
+
return self._get_environment_config_memory(environment, mask_secrets)
|
286
|
+
else:
|
287
|
+
return self._get_environment_config_file(environment, mask_secrets)
|
288
|
+
|
289
|
+
def update_environment_config(
|
290
|
+
self,
|
291
|
+
environment: str,
|
292
|
+
config_data: Dict[str, Any],
|
293
|
+
updated_by: Optional[str] = None
|
294
|
+
) -> bool:
|
295
|
+
"""Update environment configuration"""
|
296
|
+
if self.storage_backend == "supabase":
|
297
|
+
return self._update_environment_config_supabase(environment, config_data, updated_by)
|
298
|
+
elif self.storage_backend == "env":
|
299
|
+
return self._update_environment_config_env(environment, config_data, updated_by)
|
300
|
+
elif self.storage_backend == "memory":
|
301
|
+
return self._update_environment_config_memory(environment, config_data, updated_by)
|
302
|
+
else:
|
303
|
+
return self._update_environment_config_file(environment, config_data, updated_by)
|
304
|
+
|
305
|
+
def list_environment_configs(self) -> List[EnvironmentConfig]:
|
306
|
+
"""List all environment configurations"""
|
307
|
+
if self.storage_backend == "supabase":
|
308
|
+
return self._list_environment_configs_supabase()
|
309
|
+
elif self.storage_backend == "env":
|
310
|
+
return self._list_environment_configs_env()
|
311
|
+
elif self.storage_backend == "memory":
|
312
|
+
return self._list_environment_configs_memory()
|
313
|
+
else:
|
314
|
+
return self._list_environment_configs_file()
|
315
|
+
|
316
|
+
# Generic Configuration Methods
|
317
|
+
|
318
|
+
def get_config(
|
319
|
+
self,
|
320
|
+
config_key: str,
|
321
|
+
config_type: str = "system",
|
322
|
+
environment: str = "production",
|
323
|
+
default_value: Any = None
|
324
|
+
) -> Any:
|
325
|
+
"""Get generic configuration value"""
|
326
|
+
if self.storage_backend == "env":
|
327
|
+
return self._get_config_env(config_key, config_type, environment, default_value)
|
328
|
+
elif self.storage_backend == "memory":
|
329
|
+
return self._get_config_memory(config_key, config_type, environment, default_value)
|
330
|
+
elif self.storage_backend == "supabase":
|
331
|
+
return self._get_config_supabase(config_key, config_type, environment, default_value)
|
332
|
+
else:
|
333
|
+
return self._get_config_file(config_key, config_type, environment, default_value)
|
334
|
+
|
335
|
+
def set_config(
|
336
|
+
self,
|
337
|
+
config_key: str,
|
338
|
+
config_value: Any,
|
339
|
+
config_type: str = "system",
|
340
|
+
environment: str = "production",
|
341
|
+
updated_by: Optional[str] = None,
|
342
|
+
description: Optional[str] = None
|
343
|
+
) -> bool:
|
344
|
+
"""Set generic configuration value"""
|
345
|
+
if self.storage_backend == "env":
|
346
|
+
return self._set_config_env(config_key, config_value, config_type, environment, updated_by, description)
|
347
|
+
elif self.storage_backend == "memory":
|
348
|
+
return self._set_config_memory(config_key, config_value, config_type, environment, updated_by, description)
|
349
|
+
elif self.storage_backend == "supabase":
|
350
|
+
return self._set_config_supabase(config_key, config_value, config_type, environment, updated_by, description)
|
351
|
+
else:
|
352
|
+
return self._set_config_file(config_key, config_value, config_type, environment, updated_by, description)
|
353
|
+
|
354
|
+
# Validation Methods
|
355
|
+
|
356
|
+
def validate_provider_config(
|
357
|
+
self,
|
358
|
+
provider_name: str,
|
359
|
+
config_data: Dict[str, Any]
|
360
|
+
) -> Dict[str, Any]:
|
361
|
+
"""Validate provider configuration"""
|
362
|
+
validation_result = {
|
363
|
+
"valid": True,
|
364
|
+
"errors": [],
|
365
|
+
"warnings": []
|
366
|
+
}
|
367
|
+
|
368
|
+
# Basic validation rules
|
369
|
+
if not provider_name:
|
370
|
+
validation_result["valid"] = False
|
371
|
+
validation_result["errors"].append("Provider name is required")
|
372
|
+
|
373
|
+
if not config_data:
|
374
|
+
validation_result["valid"] = False
|
375
|
+
validation_result["errors"].append("Configuration data is required")
|
376
|
+
|
377
|
+
# Provider-specific validation
|
378
|
+
if provider_name == "openai":
|
379
|
+
if "api_key" not in config_data:
|
380
|
+
validation_result["valid"] = False
|
381
|
+
validation_result["errors"].append("OpenAI API key is required")
|
382
|
+
elif provider_name == "anthropic":
|
383
|
+
if "api_key" not in config_data:
|
384
|
+
validation_result["valid"] = False
|
385
|
+
validation_result["errors"].append("Anthropic API key is required")
|
386
|
+
elif provider_name == "replicate":
|
387
|
+
if "api_token" not in config_data:
|
388
|
+
validation_result["valid"] = False
|
389
|
+
validation_result["errors"].append("Replicate API token is required")
|
390
|
+
|
391
|
+
return validation_result
|
392
|
+
|
393
|
+
def validate_environment_config(
|
394
|
+
self,
|
395
|
+
environment: str,
|
396
|
+
config_data: Dict[str, Any]
|
397
|
+
) -> Dict[str, Any]:
|
398
|
+
"""Validate environment configuration"""
|
399
|
+
validation_result = {
|
400
|
+
"valid": True,
|
401
|
+
"errors": [],
|
402
|
+
"warnings": []
|
403
|
+
}
|
404
|
+
|
405
|
+
if not environment:
|
406
|
+
validation_result["valid"] = False
|
407
|
+
validation_result["errors"].append("Environment name is required")
|
408
|
+
|
409
|
+
if environment not in ["development", "staging", "production"]:
|
410
|
+
validation_result["warnings"].append(f"Unusual environment name: {environment}")
|
411
|
+
|
412
|
+
return validation_result
|
413
|
+
|
414
|
+
# Audit and Logging Methods
|
415
|
+
|
416
|
+
def log_config_change(
|
417
|
+
self,
|
418
|
+
config_id: str,
|
419
|
+
action: str,
|
420
|
+
old_value: Optional[Any] = None,
|
421
|
+
new_value: Optional[Any] = None,
|
422
|
+
user_id: Optional[str] = None,
|
423
|
+
ip_address: Optional[str] = None
|
424
|
+
) -> str:
|
425
|
+
"""Log configuration change for audit purposes"""
|
426
|
+
audit_id = f"audit_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{hash(config_id) % 10000}"
|
427
|
+
|
428
|
+
audit_log = ConfigAuditLog(
|
429
|
+
audit_id=audit_id,
|
430
|
+
config_id=config_id,
|
431
|
+
action=action,
|
432
|
+
old_value=old_value,
|
433
|
+
new_value=new_value,
|
434
|
+
user_id=user_id,
|
435
|
+
ip_address=ip_address
|
436
|
+
)
|
437
|
+
|
438
|
+
if self.storage_backend == "memory":
|
439
|
+
self.audit_logs.append(audit_log)
|
440
|
+
elif self.storage_backend == "file":
|
441
|
+
self._save_audit_log_file(audit_log)
|
442
|
+
# Supabase implementation would go here
|
443
|
+
|
444
|
+
return audit_id
|
445
|
+
|
446
|
+
def get_audit_logs(
|
447
|
+
self,
|
448
|
+
config_id: Optional[str] = None,
|
449
|
+
action: Optional[str] = None,
|
450
|
+
user_id: Optional[str] = None,
|
451
|
+
limit: int = 100
|
452
|
+
) -> List[ConfigAuditLog]:
|
453
|
+
"""Get configuration audit logs"""
|
454
|
+
if self.storage_backend == "memory":
|
455
|
+
return self._get_audit_logs_memory(config_id, action, user_id, limit)
|
456
|
+
elif self.storage_backend == "file":
|
457
|
+
return self._get_audit_logs_file(config_id, action, user_id, limit)
|
458
|
+
else:
|
459
|
+
return [] # Supabase implementation needed
|
460
|
+
|
461
|
+
# Backend-specific implementations (File System)
|
462
|
+
|
463
|
+
def _get_provider_config_file(self, provider_name: str, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
464
|
+
"""Get provider config from file system"""
|
465
|
+
try:
|
466
|
+
config_file = self.config_dir / "providers" / f"{provider_name}_{environment}.json"
|
467
|
+
if not config_file.exists():
|
468
|
+
return None
|
469
|
+
|
470
|
+
with open(config_file, 'r') as f:
|
471
|
+
config_data = json.load(f)
|
472
|
+
|
473
|
+
if mask_secrets:
|
474
|
+
config_data = self._mask_sensitive_data(config_data)
|
475
|
+
|
476
|
+
return config_data.get("config_data")
|
477
|
+
except Exception as e:
|
478
|
+
logger.error(f"Failed to get provider config from file: {e}")
|
479
|
+
return None
|
480
|
+
|
481
|
+
def _update_provider_config_file(
|
482
|
+
self, provider_name: str, config_data: Dict[str, Any],
|
483
|
+
environment: str, is_active: bool, updated_by: Optional[str]
|
484
|
+
) -> bool:
|
485
|
+
"""Update provider config in file system"""
|
486
|
+
try:
|
487
|
+
config_file = self.config_dir / "providers" / f"{provider_name}_{environment}.json"
|
488
|
+
|
489
|
+
provider_config = ProviderConfig(
|
490
|
+
provider_name=provider_name,
|
491
|
+
config_data=config_data,
|
492
|
+
is_active=is_active,
|
493
|
+
environment=environment,
|
494
|
+
updated_by=updated_by,
|
495
|
+
updated_at=datetime.now(timezone.utc)
|
496
|
+
)
|
497
|
+
|
498
|
+
# Preserve created_at if file exists
|
499
|
+
if config_file.exists():
|
500
|
+
with open(config_file, 'r') as f:
|
501
|
+
existing_data = json.load(f)
|
502
|
+
if 'created_at' in existing_data:
|
503
|
+
provider_config.created_at = datetime.fromisoformat(existing_data['created_at'])
|
504
|
+
|
505
|
+
config_dict = asdict(provider_config)
|
506
|
+
# Convert datetime objects to ISO strings
|
507
|
+
for key in ['created_at', 'updated_at']:
|
508
|
+
if config_dict[key] and isinstance(config_dict[key], datetime):
|
509
|
+
config_dict[key] = config_dict[key].isoformat()
|
510
|
+
|
511
|
+
with open(config_file, 'w') as f:
|
512
|
+
json.dump(config_dict, f, indent=2, ensure_ascii=False)
|
513
|
+
|
514
|
+
# Log the change
|
515
|
+
self.log_config_change(
|
516
|
+
config_id=f"provider_{provider_name}_{environment}",
|
517
|
+
action="update",
|
518
|
+
new_value=config_data,
|
519
|
+
user_id=updated_by
|
520
|
+
)
|
521
|
+
|
522
|
+
return True
|
523
|
+
except Exception as e:
|
524
|
+
logger.error(f"Failed to update provider config in file: {e}")
|
525
|
+
return False
|
526
|
+
|
527
|
+
def _list_provider_configs_file(self, environment: str, include_inactive: bool) -> List[ProviderConfig]:
|
528
|
+
"""List provider configs from file system"""
|
529
|
+
try:
|
530
|
+
configs = []
|
531
|
+
providers_dir = self.config_dir / "providers"
|
532
|
+
|
533
|
+
for config_file in providers_dir.glob(f"*_{environment}.json"):
|
534
|
+
with open(config_file, 'r') as f:
|
535
|
+
config_data = json.load(f)
|
536
|
+
|
537
|
+
if not include_inactive and not config_data.get('is_active', True):
|
538
|
+
continue
|
539
|
+
|
540
|
+
# Convert datetime fields
|
541
|
+
for key in ['created_at', 'updated_at']:
|
542
|
+
if config_data[key]:
|
543
|
+
config_data[key] = datetime.fromisoformat(config_data[key])
|
544
|
+
|
545
|
+
configs.append(ProviderConfig(**config_data))
|
546
|
+
|
547
|
+
return configs
|
548
|
+
except Exception as e:
|
549
|
+
logger.error(f"Failed to list provider configs from file: {e}")
|
550
|
+
return []
|
551
|
+
|
552
|
+
def _get_environment_config_file(self, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
553
|
+
"""Get environment config from file system"""
|
554
|
+
try:
|
555
|
+
config_file = self.config_dir / "environments" / f"{environment}.json"
|
556
|
+
if not config_file.exists():
|
557
|
+
return None
|
558
|
+
|
559
|
+
with open(config_file, 'r') as f:
|
560
|
+
config_data = json.load(f)
|
561
|
+
|
562
|
+
if mask_secrets:
|
563
|
+
config_data = self._mask_sensitive_data(config_data)
|
564
|
+
|
565
|
+
return config_data.get("config_data")
|
566
|
+
except Exception as e:
|
567
|
+
logger.error(f"Failed to get environment config from file: {e}")
|
568
|
+
return None
|
569
|
+
|
570
|
+
def _get_config_file(self, config_key: str, config_type: str, environment: str, default_value: Any) -> Any:
|
571
|
+
"""Get generic config from file system"""
|
572
|
+
try:
|
573
|
+
config_file = self.config_dir / "system" / f"{config_type}_{environment}.json"
|
574
|
+
if not config_file.exists():
|
575
|
+
return default_value
|
576
|
+
|
577
|
+
with open(config_file, 'r') as f:
|
578
|
+
config_data = json.load(f)
|
579
|
+
|
580
|
+
return config_data.get(config_key, default_value)
|
581
|
+
except Exception as e:
|
582
|
+
logger.error(f"Failed to get config from file: {e}")
|
583
|
+
return default_value
|
584
|
+
|
585
|
+
def _set_config_file(
|
586
|
+
self, config_key: str, config_value: Any, config_type: str,
|
587
|
+
environment: str, updated_by: Optional[str], description: Optional[str]
|
588
|
+
) -> bool:
|
589
|
+
"""Set generic config in file system"""
|
590
|
+
try:
|
591
|
+
config_file = self.config_dir / "system" / f"{config_type}_{environment}.json"
|
592
|
+
|
593
|
+
# Load existing config or create new
|
594
|
+
if config_file.exists():
|
595
|
+
with open(config_file, 'r') as f:
|
596
|
+
config_data = json.load(f)
|
597
|
+
else:
|
598
|
+
config_data = {}
|
599
|
+
|
600
|
+
config_data[config_key] = config_value
|
601
|
+
config_data['_updated_at'] = datetime.now(timezone.utc).isoformat()
|
602
|
+
config_data['_updated_by'] = updated_by
|
603
|
+
|
604
|
+
with open(config_file, 'w') as f:
|
605
|
+
json.dump(config_data, f, indent=2, ensure_ascii=False)
|
606
|
+
|
607
|
+
return True
|
608
|
+
except Exception as e:
|
609
|
+
logger.error(f"Failed to set config in file: {e}")
|
610
|
+
return False
|
611
|
+
|
612
|
+
# Backend-specific implementations (Environment Variables)
|
613
|
+
|
614
|
+
def _get_provider_config_env(self, provider_name: str, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
615
|
+
"""Get provider config from environment variables"""
|
616
|
+
env_key = f"{self.env_prefix}{provider_name.upper()}_{environment.upper()}_CONFIG"
|
617
|
+
config_json = os.getenv(env_key)
|
618
|
+
|
619
|
+
if not config_json:
|
620
|
+
return None
|
621
|
+
|
622
|
+
try:
|
623
|
+
config_data = json.loads(config_json)
|
624
|
+
if mask_secrets:
|
625
|
+
config_data = self._mask_sensitive_data(config_data)
|
626
|
+
return config_data
|
627
|
+
except json.JSONDecodeError:
|
628
|
+
logger.error(f"Invalid JSON in environment variable: {env_key}")
|
629
|
+
return None
|
630
|
+
|
631
|
+
def _get_config_env(self, config_key: str, config_type: str, environment: str, default_value: Any) -> Any:
|
632
|
+
"""Get config from environment variables"""
|
633
|
+
env_key = f"{self.env_prefix}{config_type.upper()}_{environment.upper()}_{config_key.upper()}"
|
634
|
+
return os.getenv(env_key, default_value)
|
635
|
+
|
636
|
+
# Backend-specific implementations (Memory)
|
637
|
+
|
638
|
+
def _get_provider_config_memory(self, provider_name: str, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
639
|
+
"""Get provider config from memory"""
|
640
|
+
key = f"{provider_name}_{environment}"
|
641
|
+
config = self.providers.get(key)
|
642
|
+
if not config:
|
643
|
+
return None
|
644
|
+
|
645
|
+
config_data = config.config_data.copy()
|
646
|
+
if mask_secrets:
|
647
|
+
config_data = self._mask_sensitive_data(config_data)
|
648
|
+
return config_data
|
649
|
+
|
650
|
+
def _update_provider_config_memory(
|
651
|
+
self, provider_name: str, config_data: Dict[str, Any],
|
652
|
+
environment: str, is_active: bool, updated_by: Optional[str]
|
653
|
+
) -> bool:
|
654
|
+
"""Update provider config in memory"""
|
655
|
+
key = f"{provider_name}_{environment}"
|
656
|
+
|
657
|
+
provider_config = ProviderConfig(
|
658
|
+
provider_name=provider_name,
|
659
|
+
config_data=config_data,
|
660
|
+
is_active=is_active,
|
661
|
+
environment=environment,
|
662
|
+
updated_by=updated_by,
|
663
|
+
updated_at=datetime.now(timezone.utc)
|
664
|
+
)
|
665
|
+
|
666
|
+
self.providers[key] = provider_config
|
667
|
+
return True
|
668
|
+
|
669
|
+
def _get_config_memory(self, config_key: str, config_type: str, environment: str, default_value: Any) -> Any:
|
670
|
+
"""Get config from memory"""
|
671
|
+
key = f"{config_type}_{environment}_{config_key}"
|
672
|
+
return self.configs.get(key, default_value)
|
673
|
+
|
674
|
+
def _set_config_memory(
|
675
|
+
self, config_key: str, config_value: Any, config_type: str,
|
676
|
+
environment: str, updated_by: Optional[str], description: Optional[str]
|
677
|
+
) -> bool:
|
678
|
+
"""Set config in memory"""
|
679
|
+
key = f"{config_type}_{environment}_{config_key}"
|
680
|
+
self.configs[key] = config_value
|
681
|
+
return True
|
682
|
+
|
683
|
+
# Utility Methods
|
684
|
+
|
685
|
+
def _mask_sensitive_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
686
|
+
"""Mask sensitive configuration data"""
|
687
|
+
masked_data = data.copy()
|
688
|
+
sensitive_keys = ['api_key', 'api_token', 'secret', 'password', 'private_key']
|
689
|
+
|
690
|
+
for key, value in masked_data.items():
|
691
|
+
if any(sensitive_key in key.lower() for sensitive_key in sensitive_keys):
|
692
|
+
if isinstance(value, str) and len(value) > 8:
|
693
|
+
masked_data[key] = value[:4] + "***" + value[-4:]
|
694
|
+
else:
|
695
|
+
masked_data[key] = "***"
|
696
|
+
|
697
|
+
return masked_data
|
698
|
+
|
699
|
+
def _save_audit_log_file(self, audit_log: ConfigAuditLog):
|
700
|
+
"""Save audit log to file system"""
|
701
|
+
try:
|
702
|
+
audit_file = self.config_dir / "audit" / f"{audit_log.audit_id}.json"
|
703
|
+
audit_data = asdict(audit_log)
|
704
|
+
|
705
|
+
if audit_data['timestamp'] and isinstance(audit_data['timestamp'], datetime):
|
706
|
+
audit_data['timestamp'] = audit_data['timestamp'].isoformat()
|
707
|
+
|
708
|
+
with open(audit_file, 'w') as f:
|
709
|
+
json.dump(audit_data, f, indent=2, ensure_ascii=False)
|
710
|
+
except Exception as e:
|
711
|
+
logger.error(f"Failed to save audit log to file: {e}")
|
712
|
+
|
713
|
+
def _get_audit_logs_file(self, config_id: Optional[str], action: Optional[str], user_id: Optional[str], limit: int) -> List[ConfigAuditLog]:
|
714
|
+
"""Get audit logs from file system"""
|
715
|
+
try:
|
716
|
+
logs = []
|
717
|
+
audit_dir = self.config_dir / "audit"
|
718
|
+
|
719
|
+
for audit_file in audit_dir.glob("*.json"):
|
720
|
+
with open(audit_file, 'r') as f:
|
721
|
+
audit_data = json.load(f)
|
722
|
+
|
723
|
+
# Apply filters
|
724
|
+
if config_id and audit_data.get('config_id') != config_id:
|
725
|
+
continue
|
726
|
+
if action and audit_data.get('action') != action:
|
727
|
+
continue
|
728
|
+
if user_id and audit_data.get('user_id') != user_id:
|
729
|
+
continue
|
730
|
+
|
731
|
+
# Convert timestamp
|
732
|
+
if audit_data['timestamp']:
|
733
|
+
audit_data['timestamp'] = datetime.fromisoformat(audit_data['timestamp'])
|
734
|
+
|
735
|
+
logs.append(ConfigAuditLog(**audit_data))
|
736
|
+
|
737
|
+
if len(logs) >= limit:
|
738
|
+
break
|
739
|
+
|
740
|
+
return sorted(logs, key=lambda x: x.timestamp, reverse=True)
|
741
|
+
except Exception as e:
|
742
|
+
logger.error(f"Failed to get audit logs from file: {e}")
|
743
|
+
return []
|
744
|
+
|
745
|
+
def _get_audit_logs_memory(self, config_id: Optional[str], action: Optional[str], user_id: Optional[str], limit: int) -> List[ConfigAuditLog]:
|
746
|
+
"""Get audit logs from memory"""
|
747
|
+
filtered_logs = []
|
748
|
+
|
749
|
+
for log in self.audit_logs:
|
750
|
+
# Apply filters
|
751
|
+
if config_id and log.config_id != config_id:
|
752
|
+
continue
|
753
|
+
if action and log.action != action:
|
754
|
+
continue
|
755
|
+
if user_id and log.user_id != user_id:
|
756
|
+
continue
|
757
|
+
|
758
|
+
filtered_logs.append(log)
|
759
|
+
|
760
|
+
if len(filtered_logs) >= limit:
|
761
|
+
break
|
762
|
+
|
763
|
+
return sorted(filtered_logs, key=lambda x: x.timestamp, reverse=True)
|
764
|
+
|
765
|
+
# Placeholder implementations for remaining methods
|
766
|
+
def _delete_provider_config_file(self, provider_name: str, environment: str, deleted_by: Optional[str]) -> bool:
|
767
|
+
try:
|
768
|
+
config_file = self.config_dir / "providers" / f"{provider_name}_{environment}.json"
|
769
|
+
if config_file.exists():
|
770
|
+
config_file.unlink()
|
771
|
+
self.log_config_change(
|
772
|
+
config_id=f"provider_{provider_name}_{environment}",
|
773
|
+
action="delete",
|
774
|
+
user_id=deleted_by
|
775
|
+
)
|
776
|
+
return True
|
777
|
+
except Exception as e:
|
778
|
+
logger.error(f"Failed to delete provider config from file: {e}")
|
779
|
+
return False
|
780
|
+
|
781
|
+
def _delete_provider_config_memory(self, provider_name: str, environment: str, deleted_by: Optional[str]) -> bool:
|
782
|
+
key = f"{provider_name}_{environment}"
|
783
|
+
return self.providers.pop(key, None) is not None
|
784
|
+
|
785
|
+
def _delete_provider_config_env(self, provider_name: str, environment: str, deleted_by: Optional[str]) -> bool:
|
786
|
+
# Cannot delete environment variables programmatically
|
787
|
+
return False
|
788
|
+
|
789
|
+
def _update_environment_config_file(self, environment: str, config_data: Dict[str, Any], updated_by: Optional[str]) -> bool:
|
790
|
+
try:
|
791
|
+
config_file = self.config_dir / "environments" / f"{environment}.json"
|
792
|
+
|
793
|
+
env_config = EnvironmentConfig(
|
794
|
+
environment=environment,
|
795
|
+
config_data=config_data,
|
796
|
+
updated_by=updated_by,
|
797
|
+
updated_at=datetime.now(timezone.utc)
|
798
|
+
)
|
799
|
+
|
800
|
+
config_dict = asdict(env_config)
|
801
|
+
# Convert datetime objects to ISO strings
|
802
|
+
for key in ['created_at', 'updated_at']:
|
803
|
+
if config_dict[key] and isinstance(config_dict[key], datetime):
|
804
|
+
config_dict[key] = config_dict[key].isoformat()
|
805
|
+
|
806
|
+
with open(config_file, 'w') as f:
|
807
|
+
json.dump(config_dict, f, indent=2, ensure_ascii=False)
|
808
|
+
|
809
|
+
return True
|
810
|
+
except Exception as e:
|
811
|
+
logger.error(f"Failed to update environment config in file: {e}")
|
812
|
+
return False
|
813
|
+
|
814
|
+
def _list_environment_configs_file(self) -> List[EnvironmentConfig]:
|
815
|
+
try:
|
816
|
+
configs = []
|
817
|
+
environments_dir = self.config_dir / "environments"
|
818
|
+
|
819
|
+
for config_file in environments_dir.glob("*.json"):
|
820
|
+
with open(config_file, 'r') as f:
|
821
|
+
config_data = json.load(f)
|
822
|
+
|
823
|
+
# Convert datetime fields
|
824
|
+
for key in ['created_at', 'updated_at']:
|
825
|
+
if config_data[key]:
|
826
|
+
config_data[key] = datetime.fromisoformat(config_data[key])
|
827
|
+
|
828
|
+
configs.append(EnvironmentConfig(**config_data))
|
829
|
+
|
830
|
+
return configs
|
831
|
+
except Exception as e:
|
832
|
+
logger.error(f"Failed to list environment configs from file: {e}")
|
833
|
+
return []
|
834
|
+
|
835
|
+
# Placeholder implementations for Supabase and other methods
|
836
|
+
def _get_provider_config_supabase(self, provider_name: str, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
837
|
+
return None # Implementation needed
|
838
|
+
|
839
|
+
def _update_provider_config_supabase(self, provider_name: str, config_data: Dict[str, Any], environment: str, is_active: bool, updated_by: Optional[str]) -> bool:
|
840
|
+
return False # Implementation needed
|
841
|
+
|
842
|
+
def _list_provider_configs_supabase(self, environment: str, include_inactive: bool) -> List[ProviderConfig]:
|
843
|
+
return [] # Implementation needed
|
844
|
+
|
845
|
+
def _update_provider_config_env(self, provider_name: str, config_data: Dict[str, Any], environment: str, is_active: bool, updated_by: Optional[str]) -> bool:
|
846
|
+
return False # Environment variables are read-only
|
847
|
+
|
848
|
+
def _list_provider_configs_env(self, environment: str, include_inactive: bool) -> List[ProviderConfig]:
|
849
|
+
return [] # Implementation needed
|
850
|
+
|
851
|
+
def _list_provider_configs_memory(self, environment: str, include_inactive: bool) -> List[ProviderConfig]:
|
852
|
+
configs = []
|
853
|
+
for key, config in self.providers.items():
|
854
|
+
if environment not in key:
|
855
|
+
continue
|
856
|
+
if not include_inactive and not config.is_active:
|
857
|
+
continue
|
858
|
+
configs.append(config)
|
859
|
+
return configs
|
860
|
+
|
861
|
+
def _get_environment_config_supabase(self, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
862
|
+
return None # Implementation needed
|
863
|
+
|
864
|
+
def _get_environment_config_env(self, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
865
|
+
return None # Implementation needed
|
866
|
+
|
867
|
+
def _get_environment_config_memory(self, environment: str, mask_secrets: bool) -> Optional[Dict[str, Any]]:
|
868
|
+
env_config = self.environments.get(environment)
|
869
|
+
if not env_config:
|
870
|
+
return None
|
871
|
+
|
872
|
+
config_data = env_config.config_data.copy()
|
873
|
+
if mask_secrets:
|
874
|
+
config_data = self._mask_sensitive_data(config_data)
|
875
|
+
return config_data
|
876
|
+
|
877
|
+
def _update_environment_config_supabase(self, environment: str, config_data: Dict[str, Any], updated_by: Optional[str]) -> bool:
|
878
|
+
return False # Implementation needed
|
879
|
+
|
880
|
+
def _update_environment_config_env(self, environment: str, config_data: Dict[str, Any], updated_by: Optional[str]) -> bool:
|
881
|
+
return False # Environment variables are read-only
|
882
|
+
|
883
|
+
def _update_environment_config_memory(self, environment: str, config_data: Dict[str, Any], updated_by: Optional[str]) -> bool:
|
884
|
+
env_config = EnvironmentConfig(
|
885
|
+
environment=environment,
|
886
|
+
config_data=config_data,
|
887
|
+
updated_by=updated_by,
|
888
|
+
updated_at=datetime.now(timezone.utc)
|
889
|
+
)
|
890
|
+
self.environments[environment] = env_config
|
891
|
+
return True
|
892
|
+
|
893
|
+
def _list_environment_configs_supabase(self) -> List[EnvironmentConfig]:
|
894
|
+
return [] # Implementation needed
|
895
|
+
|
896
|
+
def _list_environment_configs_env(self) -> List[EnvironmentConfig]:
|
897
|
+
return [] # Implementation needed
|
898
|
+
|
899
|
+
def _list_environment_configs_memory(self) -> List[EnvironmentConfig]:
|
900
|
+
return list(self.environments.values())
|
901
|
+
|
902
|
+
def _get_config_supabase(self, config_key: str, config_type: str, environment: str, default_value: Any) -> Any:
|
903
|
+
return default_value # Implementation needed
|
904
|
+
|
905
|
+
def _set_config_supabase(self, config_key: str, config_value: Any, config_type: str, environment: str, updated_by: Optional[str], description: Optional[str]) -> bool:
|
906
|
+
return False # Implementation needed
|
907
|
+
|
908
|
+
def _set_config_env(self, config_key: str, config_value: Any, config_type: str, environment: str, updated_by: Optional[str], description: Optional[str]) -> bool:
|
909
|
+
return False # Environment variables are read-only
|
910
|
+
|
911
|
+
def _delete_provider_config_supabase(self, provider_name: str, environment: str, deleted_by: Optional[str]) -> bool:
|
912
|
+
return False # Implementation needed
|