Spaces:
Building
Building
""" | |
Flare – Configuration Loader (JSONC + Pydantic v2) | |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
• Global, Project, Version, Intent, API tanımları | |
• JSONC yorum temizleme (string literal’lere dokunmaz) | |
• Alias’lar (version_number, max_attempts vb.) | |
• apis[] artık liste → runtime’da dict’e map’lenir | |
""" | |
from __future__ import annotations | |
import json | |
from pathlib import Path | |
from typing import Any, Dict, List, Optional | |
from pydantic import BaseModel, Field, HttpUrl, ValidationError | |
from utils import log | |
# ---------------- Global ----------------- | |
class UserConfig(BaseModel): | |
username: str | |
password_hash: str | |
salt: str | |
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] = [] | |
# ---------------- 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" | |
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" | |
# ---------------- Intent / Param --------- | |
class ParameterConfig(BaseModel): | |
name: str | |
caption: Optional[str] = "" | |
type: str = Field(..., pattern=r"^(int|float|bool|str|string)$") | |
required: bool = True | |
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: | |
return "str" if self.type == "string" else 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" | |
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() | |
return cls._instance | |
# -------- Internal helpers ------------ | |
def _load(cls) -> ServiceConfig: | |
log(f"📥 Loading service config from {cls._CONFIG_PATH.name} …") | |
raw = cls._CONFIG_PATH.read_text(encoding="utf-8") | |
json_str = cls._strip_jsonc(raw) | |
try: | |
data = json.loads(json_str) | |
cfg = ServiceConfig.model_validate(data) | |
log("✅ Service config loaded.") | |
return cfg | |
except (json.JSONDecodeError, ValidationError) as exc: | |
log(f"❌ Config validation error: {exc}") | |
raise | |
def _strip_jsonc(text: str) -> str: | |
"""Remove // and /* */ comments (string-aware).""" | |
OUT, STR, ESC, SLASH, BLOCK = 0, 1, 2, 3, 4 | |
state, res, i = OUT, [], 0 | |
while i < len(text): | |
ch = text[i] | |
if state == OUT: | |
if ch == '"': | |
state, res = STR, res + [ch] | |
elif ch == '/': | |
nxt = text[i + 1] if i + 1 < len(text) else "" | |
if nxt == '/': | |
state, i = SLASH, i + 1 | |
elif nxt == '*': | |
state, i = BLOCK, i + 1 | |
else: | |
res.append(ch) | |
else: | |
res.append(ch) | |
elif state == STR: | |
res.append(ch) | |
state = ESC if ch == '\\' else (OUT if ch == '"' else STR) | |
elif state == ESC: | |
res.append(ch) | |
state = STR | |
elif state == SLASH: | |
if ch == '\n': | |
res.append(ch) | |
state = OUT | |
elif state == BLOCK: | |
if ch == '*' and i + 1 < len(text) and text[i + 1] == '/': | |
i, state = i + 1, OUT | |
i += 1 | |
return ''.join(res) | |