| from __future__ import annotations |
|
|
| import json |
| import os |
| from dataclasses import dataclass |
| from functools import lru_cache |
| from pathlib import Path |
| from typing import Any, Dict, List, Optional |
|
|
| CONFIG_FILE = Path(__file__).with_name("app-config.json") |
| DEFAULT_LABELS: List[str] = [ |
| "Heart", |
| "Oblong", |
| "Oval", |
| "Round", |
| "Square", |
| ] |
| DEFAULT_ALLOWED_ORIGINS: List[str] = [ |
| "http://localhost:3000", |
| "http://127.0.0.1:3000", |
| ] |
| DEFAULT_PORT = 7860 |
|
|
|
|
| def _load_config_file() -> Dict[str, Any]: |
| if not CONFIG_FILE.exists(): |
| return {} |
|
|
| try: |
| return json.loads(CONFIG_FILE.read_text()) |
| except json.JSONDecodeError as exc: |
| raise ValueError(f"Konfigurasi JSON tidak valid: {CONFIG_FILE}") from exc |
|
|
|
|
| def _parse_labels(raw: Any) -> List[str]: |
| if raw is None: |
| return DEFAULT_LABELS.copy() |
|
|
| if isinstance(raw, str): |
| parts = [label.strip() for label in raw.split(",") if label.strip()] |
| return parts or DEFAULT_LABELS.copy() |
|
|
| if isinstance(raw, list): |
| filtered = [str(label).strip() for label in raw if str(label).strip()] |
| return filtered or DEFAULT_LABELS.copy() |
|
|
| return DEFAULT_LABELS.copy() |
|
|
|
|
| def _parse_origins(raw: Any) -> List[str]: |
| if raw is None: |
| return DEFAULT_ALLOWED_ORIGINS.copy() |
|
|
| if isinstance(raw, str): |
| parts = [origin.strip() for origin in raw.split(",") if origin.strip()] |
| return parts or DEFAULT_ALLOWED_ORIGINS.copy() |
|
|
| if isinstance(raw, list): |
| filtered = [str(origin).strip() for origin in raw if str(origin).strip()] |
| return filtered or DEFAULT_ALLOWED_ORIGINS.copy() |
|
|
| return DEFAULT_ALLOWED_ORIGINS.copy() |
|
|
|
|
| def _parse_bool(raw: Any, fallback: bool = False) -> bool: |
| if raw is None: |
| return fallback |
|
|
| if isinstance(raw, bool): |
| return raw |
|
|
| return str(raw).strip().lower() in {"1", "true", "yes", "on"} |
|
|
|
|
| def _parse_int(raw: Any, fallback: int) -> int: |
| if raw is None: |
| return fallback |
|
|
| try: |
| value = int(raw) |
| except (TypeError, ValueError): |
| return fallback |
|
|
| return value if value > 0 else fallback |
|
|
|
|
| def _resolve_model_path(file_value: Optional[str]) -> str: |
| env_value = os.environ.get("MODEL_PATH") |
| candidate = env_value or file_value or "model/best_model.keras" |
| path = Path(candidate) |
| if not path.is_absolute(): |
| path = (CONFIG_FILE.parent / path).resolve() |
| return str(path) |
|
|
|
|
| @dataclass(frozen=True) |
| class Settings: |
| model_path: str |
| labels: List[str] |
| port: int |
| cors_allowed_origins: List[str] |
| gradio_auth_token: str | None |
| gradio_username: str | None |
| gradio_password: str | None |
| share: bool |
|
|
|
|
| _CONFIG_CACHE = _load_config_file() |
|
|
|
|
| @lru_cache(maxsize=1) |
| def get_settings() -> Settings: |
| labels_raw = os.environ.get("MODEL_LABELS") |
| origins_raw = os.environ.get("CORS_ALLOWED_ORIGINS") |
| share_raw: Any = os.environ.get("GRADIO_SHARE") |
| port_raw: Any = os.environ.get("PORT") |
|
|
| return Settings( |
| model_path=_resolve_model_path(_CONFIG_CACHE.get("model_path")), |
| labels=_parse_labels(labels_raw if labels_raw is not None else _CONFIG_CACHE.get("labels")), |
| port=_parse_int(port_raw if port_raw is not None else _CONFIG_CACHE.get("port"), DEFAULT_PORT), |
| cors_allowed_origins=_parse_origins( |
| origins_raw if origins_raw is not None else _CONFIG_CACHE.get("cors_allowed_origins") |
| ), |
| gradio_auth_token=os.environ.get("GRADIO_AUTH_TOKEN") or _CONFIG_CACHE.get("gradio_auth_token"), |
| gradio_username=os.environ.get("GRADIO_USERNAME") or _CONFIG_CACHE.get("gradio_username"), |
| gradio_password=os.environ.get("GRADIO_PASSWORD") or _CONFIG_CACHE.get("gradio_password"), |
| share=_parse_bool(share_raw if share_raw is not None else _CONFIG_CACHE.get("share"), False), |
| ) |
|
|
|
|
| settings = get_settings() |
|
|