flare / config_provider.py
ciyidogan's picture
Update config_provider.py
c40bd8f verified
raw
history blame
4.74 kB
"""
Flare – ConfigProvider (JSONC-safe)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
• Pydantic v2 (pattern=…)
• Yorum ayıklayıcı string literal’leri korur
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Dict, List, Optional
from pydantic import BaseModel, Field, HttpUrl, ValidationError
from utils import log
# -------------------- Model tanımları --------------------
class RetryConfig(BaseModel):
strategy: str = Field("static", pattern=r"^(static|exponential)$")
retry_count: int = 3
backoff_seconds: int = 2
class ParameterConfig(BaseModel):
name: str
type: str = Field(..., pattern=r"^(int|float|str|bool)$")
required: bool = True
invalid_prompt: Optional[str] = None
class IntentConfig(BaseModel):
name: str
action: str
parameters: List[ParameterConfig] = []
fallback_error_prompt: Optional[str] = None
class VersionConfig(BaseModel):
id: int
caption: str
general_prompt: str
is_published: bool = Field(False, alias="published")
intents: List[IntentConfig] = []
class ProjectConfig(BaseModel):
name: str
caption: str
versions: List[VersionConfig]
class APIAuthConfig(BaseModel):
token_endpoint: HttpUrl
refresh_endpoint: Optional[HttpUrl] = None
client_id: str
client_secret: str
class APIConfig(BaseModel):
name: str
url: HttpUrl
method: str = Field("GET", pattern=r"^(GET|POST|PUT|PATCH|DELETE)$")
timeout_seconds: int = 10
proxy: Optional[str] = None
auth: Optional[APIAuthConfig] = None
response_prompt: Optional[str] = None
retry: Optional[RetryConfig] = None
class ServiceConfig(BaseModel):
projects: List[ProjectConfig]
apis: Dict[str, APIConfig]
locale: str = "tr-TR"
retry: RetryConfig = RetryConfig()
# -------------------- Provider singleton ----------------
class ConfigProvider:
_config: Optional[ServiceConfig] = None
_CONFIG_PATH = Path(__file__).parent / "service_config.jsonc"
@classmethod
def get(cls) -> ServiceConfig:
if cls._config is None:
cls._config = cls._load_config()
return cls._config
# ---------------- internal ----------------
@classmethod
def _load_config(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 successfully.")
return cfg
except (json.JSONDecodeError, ValidationError) as exc:
log(f"❌ Config validation error: {exc}")
raise
# -------- robust JSONC stripper ----------
@staticmethod
def _strip_jsonc(text: str) -> str:
"""Remove // line comments and /* block comments */ without touching string literals."""
OUT, IN_STR, ESC, IN_SLASH, IN_BLOCK = 0, 1, 2, 3, 4
state = OUT
res = []
i = 0
while i < len(text):
ch = text[i]
if state == OUT:
if ch == '"':
state = IN_STR
res.append(ch)
elif ch == '/':
# Could be // or /* – peek next char
nxt = text[i + 1] if i + 1 < len(text) else ""
if nxt == '/':
state = IN_SLASH
i += 1 # skip nxt in loop
elif nxt == '*':
state = IN_BLOCK
i += 1
else:
res.append(ch)
else:
res.append(ch)
elif state == IN_STR:
res.append(ch)
if ch == '\\':
state = ESC # escape next
elif ch == '"':
state = OUT
elif state == ESC:
res.append(ch)
state = IN_STR
elif state == IN_SLASH:
if ch == '\n':
res.append(ch) # keep newline
state = OUT
elif state == IN_BLOCK:
if ch == '*' and i + 1 < len(text) and text[i + 1] == '/':
i += 1 # skip /
state = OUT
i += 1
return ''.join(res)
# --------------- exports ---------------
RetryConfig = RetryConfig
ParameterConfig = ParameterConfig
IntentConfig = IntentConfig
VersionConfig = VersionConfig
APIConfig = APIConfig
ProjectConfig = ProjectConfig
ServiceConfig = ServiceConfig