""" Flare – ConfigProvider (date type support) """ from __future__ import annotations import json, os from pathlib import Path from typing import Any, Dict, List, Optional import commentjson from pydantic import BaseModel, Field, HttpUrl, ValidationError from utils import log from encryption_utils import decrypt class GlobalConfig(BaseModel): work_mode: str = Field("hfcloud", pattern=r"^(hfcloud|cloud|on-premise)$") cloud_token: Optional[str] = None spark_endpoint: HttpUrl users: List["UserConfig"] = [] def get_plain_token(self) -> Optional[str]: return decrypt(self.cloud_token) if self.cloud_token else None # ---------------- Global ----------------- class UserConfig(BaseModel): username: str password_hash: str salt: str # ---------------- Retry / Proxy ---------- class RetryConfig(BaseModel): retry_count: int = Field(3, alias="max_attempts") backoff_seconds: int = 2 strategy: str = Field("static", pattern=r"^(static|exponential)$") class ProxyConfig(BaseModel): enabled: bool = True url: HttpUrl # ---------------- API & Auth ------------- class APIAuthConfig(BaseModel): enabled: bool = False token_endpoint: Optional[HttpUrl] = None response_token_path: str = "access_token" token_request_body: Dict[str, Any] = Field({}, alias="body_template") token_refresh_endpoint: Optional[HttpUrl] = None token_refresh_body: Dict[str, Any] = {} class Config: extra = "allow" populate_by_name = True class APIConfig(BaseModel): name: str url: HttpUrl method: str = Field("GET", pattern=r"^(GET|POST|PUT|PATCH|DELETE)$") headers: Dict[str, Any] = {} body_template: Dict[str, Any] = {} timeout_seconds: int = 10 retry: RetryConfig = RetryConfig() proxy: Optional[str | ProxyConfig] = None auth: Optional[APIAuthConfig] = None response_prompt: Optional[str] = None class Config: extra = "allow" populate_by_name = True # ---------------- Intent / Param --------- class ParameterConfig(BaseModel): name: str caption: Optional[str] = "" type: str = Field(..., pattern=r"^(int|float|bool|str|string|date)$") # Added 'date' required: bool = True variable_name: str extraction_prompt: Optional[str] = None validation_regex: Optional[str] = None invalid_prompt: Optional[str] = None type_error_prompt: Optional[str] = None def canonical_type(self) -> str: if self.type == "string": return "str" elif self.type == "date": return "str" # Store dates as strings in ISO format return self.type class IntentConfig(BaseModel): name: str caption: Optional[str] = "" locale: str = "tr-TR" dependencies: List[str] = [] examples: List[str] = [] detection_prompt: Optional[str] = None parameters: List[ParameterConfig] = [] action: str fallback_timeout_prompt: Optional[str] = None fallback_error_prompt: Optional[str] = None class Config: extra = "allow" # ---------------- Version / Project ------ class LLMConfig(BaseModel): repo_id: str generation_config: Dict[str, Any] = {} use_fine_tune: bool = False fine_tune_zip: str = "" class VersionConfig(BaseModel): id: int = Field(..., alias="version_number") caption: Optional[str] = "" published: bool = False general_prompt: str llm: "LLMConfig" intents: List["IntentConfig"] class Config: extra = "allow" populate_by_name = True class ProjectConfig(BaseModel): id: Optional[int] = None name: str caption: Optional[str] = "" enabled: bool = True last_version_number: Optional[int] = None versions: List[VersionConfig] class Config: extra = "allow" # ---------------- Service Config --------- class ServiceConfig(BaseModel): global_config: GlobalConfig = Field(..., alias="config") projects: List[ProjectConfig] apis: List[APIConfig] # runtime helpers (skip validation) _api_by_name: Dict[str, APIConfig] = {} def build_index(self): self._api_by_name = {a.name: a for a in self.apis} def get_api(self, name: str) -> APIConfig | None: return self._api_by_name.get(name) # ---------------- Provider Singleton ----- class ConfigProvider: _instance: Optional[ServiceConfig] = None _CONFIG_PATH = Path(__file__).parent / "service_config.jsonc" @classmethod def get(cls) -> ServiceConfig: if cls._instance is None: cls._instance = cls._load() cls._instance.build_index() return cls._instance # -------- Internal helpers ------------ @classmethod def _load(cls) -> ServiceConfig: log(f"📥 Loading service config from {cls._CONFIG_PATH.name} …") raw = cls._CONFIG_PATH.read_text(encoding="utf-8") # Use commentjson instead of custom parser try: data = commentjson.loads(raw) cfg = ServiceConfig.model_validate(data) log("✅ Service config loaded.") return cfg except (commentjson.JSONLibraryException, ValidationError) as exc: log(f"❌ Config validation error: {exc}") raise