Spaces:
Paused
Paused
| """ | |
| 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 utils import log | |
| from pydantic import BaseModel, Field, HttpUrl, ValidationError | |
| 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]: | |
| if self.cloud_token: | |
| # Lazy import to avoid circular dependency | |
| from encryption_utils import decrypt | |
| return decrypt(self.cloud_token) | |
| return None | |
| def is_cloud_mode(self) -> bool: | |
| """Check if running in cloud mode (hfcloud or cloud)""" | |
| return self.work_mode in ("hfcloud", "cloud") | |
| def is_on_premise(self) -> bool: | |
| """Check if running in on-premise mode""" | |
| return self.work_mode == "on-premise" | |
| # ---------------- 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" | |
| def get(cls) -> ServiceConfig: | |
| if cls._instance is None: | |
| cls._instance = cls._load() | |
| cls._instance.build_index() | |
| cls._check_environment_setup() | |
| return cls._instance | |
| def _load(cls) -> ServiceConfig: | |
| """Load configuration from service_config.jsonc""" | |
| try: | |
| log(f"π Loading config from: {cls._CONFIG_PATH}") | |
| if not cls._CONFIG_PATH.exists(): | |
| raise FileNotFoundError(f"Config file not found: {cls._CONFIG_PATH}") | |
| with open(cls._CONFIG_PATH, 'r', encoding='utf-8') as f: | |
| config_data = commentjson.load(f) | |
| # Create ServiceConfig instance | |
| service_config = ServiceConfig(**config_data) | |
| log("β Configuration loaded successfully") | |
| return service_config | |
| except FileNotFoundError as e: | |
| log(f"β Config file not found: {e}") | |
| raise | |
| except json.JSONDecodeError as e: | |
| log(f"β Invalid JSON in config file: {e}") | |
| raise | |
| except ValidationError as e: | |
| log(f"β Config validation error: {e}") | |
| raise | |
| except Exception as e: | |
| log(f"β Unexpected error loading config: {e}") | |
| raise | |
| def _check_environment_setup(cls): | |
| """Check if environment is properly configured based on work_mode""" | |
| config = cls._instance.global_config | |
| if config.is_cloud_mode(): | |
| # Cloud mode - check for HuggingFace Secrets | |
| missing_secrets = [] | |
| if not os.getenv("JWT_SECRET"): | |
| missing_secrets.append("JWT_SECRET") | |
| if not os.getenv("FLARE_TOKEN_KEY"): | |
| missing_secrets.append("FLARE_TOKEN_KEY") | |
| if not os.getenv("SPARK_TOKEN"): | |
| missing_secrets.append("SPARK_TOKEN") | |
| if missing_secrets: | |
| log(f"β οΈ Running in {config.work_mode} mode. Missing secrets: {', '.join(missing_secrets)}") | |
| log("π Add these in HuggingFace Space Settings β Repository secrets") | |
| else: | |
| log(f"β Running in {config.work_mode} mode with all required secrets") | |
| elif config.is_on_premise(): | |
| # On-premise mode - check for .env file | |
| env_path = Path(".env") | |
| if env_path.exists(): | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| log("β Running in on-premise mode with .env file") | |
| else: | |
| log("β οΈ Running in on-premise mode but .env file not found") | |
| log("π Copy .env.example to .env and configure it") |