from typing import Any, Dict, Optional, List import argparse import logging import logging.config import os from pathlib import Path import sys import uvicorn import yaml from fastapi import FastAPI # Basisverzeichnis: .../Entworfener_Code/app BASE_DIR: Path = Path(__file__).resolve().parent CODE_DIR: Path = BASE_DIR / "code" # .../app/code CONFIG_DIR: Path = BASE_DIR / "config" # .../app/config SRC_DIR: Path = BASE_DIR / "src" # .../app/src # Sicherstellen, dass .../app/code und .../app/src importierbar sind if str(CODE_DIR) not in sys.path: sys.path.insert(0, str(CODE_DIR)) if str(SRC_DIR) not in sys.path: sys.path.insert(0, str(SRC_DIR)) # Jetzt Import aus neuer Struktur (app/code/app/main.py) from app.main import create_app # noqa: E402 from src.config_loader import load_runtime_config # noqa: E402 from src.logging_setup import init_logging # noqa: E402 APP_CONFIG_ENV = "APP_CONFIG_PATH" def load_yaml(path: Path) -> Dict[str, Any]: """ Lädt eine YAML-Datei und gibt den Inhalt als Dictionary zurück. """ with path.open("r", encoding="utf-8") as f: return yaml.safe_load(f) or {} def setup_logging(cfg: Dict[str, Any]) -> None: """ Initialisiert das Logging anhand der in app/config/logging.yaml definierten Konfiguration. Stellt sicher, dass das Log-Verzeichnis existiert. """ logging_cfg = cfg.get("logging", {}) or {} paths_cfg = cfg.get("paths", {}) or {} log_dir = Path(paths_cfg.get("log_dir", "logs")) if not log_dir.is_absolute(): log_dir = BASE_DIR / log_dir log_dir.mkdir(parents=True, exist_ok=True) logging_config_file = logging_cfg.get("config_file", "config/logging.yaml") logging_path = Path(logging_config_file) if not logging_path.is_absolute(): logging_path = BASE_DIR / logging_path if logging_path.exists(): with logging_path.open("r", encoding="utf-8") as f: config_dict = yaml.safe_load(f) or {} logging.config.dictConfig(config_dict) else: logging.basicConfig( level=getattr(logging, (logging_cfg.get("level") or "INFO").upper(), logging.INFO), format="%(asctime)s | %(levelname)s | %(name)s | %(filename)s:%(lineno)d | %(message)s", ) logging.getLogger(__name__).debug("Logging initialisiert. Log-Verzeichnis: %s", str(log_dir)) def load_config_from_env() -> Dict[str, Any]: """ Lädt die Anwendungskonfiguration aus der durch APP_CONFIG_PATH angegebenen Datei. Fällt zurück auf app/config/config.yaml. """ env_path = os.environ.get(APP_CONFIG_ENV) if env_path: cfg_path = Path(env_path) else: cfg_path = CONFIG_DIR / "config.yaml" if not cfg_path.exists(): print(f"Konfigurationsdatei nicht gefunden: {cfg_path}", file=sys.stderr) return {} return load_yaml(cfg_path) def app_factory() -> FastAPI: """ Uvicorn-Factory für Reload-Betrieb (importierbare App-Fabrik). Liest Konfiguration, initialisiert Logging und erzeugt die FastAPI-App. """ cfg = load_config_from_env() # Laufzeit-Settings laden und Logging initialisieren (überschreibt ggf. YAML-Logging) settings = load_runtime_config(base_dir=BASE_DIR) init_logging(settings) app = create_app(cfg) return app def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace: """ CLI-Argumente parsen. """ parser = argparse.ArgumentParser(description="Startet den FastAPI-Server (app/code-Struktur).") parser.add_argument( "--config", type=str, default=str(CONFIG_DIR / "config.yaml"), help="Pfad zur Konfigurationsdatei (YAML).", ) return parser.parse_args(argv) def main(argv: Optional[List[str]] = None) -> None: """ Einstiegspunkt: lädt Konfiguration, initialisiert Logging, startet Uvicorn. """ args = parse_args(argv) # Resolve config path config_path = Path(args.config) if not config_path.is_absolute(): config_path = (BASE_DIR / args.config).resolve() if not config_path.exists(): print(f"Konfigurationsdatei nicht gefunden: {config_path}", file=sys.stderr) sys.exit(2) # Set env var für Factory os.environ[APP_CONFIG_ENV] = str(config_path) cfg = load_yaml(config_path) # Laufzeit-Settings laden und Logging initialisieren (überschreibt ggf. YAML-Logging) settings = load_runtime_config(base_dir=BASE_DIR) init_logging(settings) app_cfg = cfg.get("app", {}) or {} host = str(app_cfg.get("host", "0.0.0.0")) port = int(app_cfg.get("port", 8000)) reload_enabled = bool(app_cfg.get("reload", False)) logger = logging.getLogger("start") if reload_enabled: # Für Reload muss eine importierbare App-Factory übergeben werden logger.info("Starte Uvicorn mit Reload (Factory-Modus, app/code).") module_name = Path(__file__).stem # "start" uvicorn.run( f"{module_name}:app_factory", factory=True, host=host, port=port, reload=True, ) else: logger.info("Starte Uvicorn ohne Reload (app/code).") app = create_app(cfg) uvicorn.run( app, host=host, port=port, reload=False, ) if __name__ == "__main__": main()