"""Résolution du fichier de configuration externe éditable (dictionnaires.yml). En frozen (PyInstaller), la config doit vivre À CÔTÉ de l'exécutable pour que l'établissement puisse l'éditer sans recompiler ; on copie la version embarquée au premier lancement si elle est absente. En développement, on pointe directement la config du dépôt. Aligné sur le pattern V5 (``Pseudonymisation_Gui_V5._resolve_config``), best-effort (jamais de crash). """ from __future__ import annotations import logging import shutil import sys from pathlib import Path from typing import Optional log = logging.getLogger(__name__) _CONFIG_RELATIVE = Path("config") / "dictionnaires.yml" def _frozen() -> bool: return bool(getattr(sys, "frozen", False)) def _bundled_config() -> Path: """Config embarquée : ``_MEIPASS`` en frozen, racine du dépôt en dev.""" if _frozen(): base = Path(getattr(sys, "_MEIPASS")) else: base = Path(__file__).resolve().parent.parent return base / _CONFIG_RELATIVE def resolve_user_config_path() -> Optional[Path]: """Chemin du ``dictionnaires.yml`` éditable par l'utilisateur. - dev : la config du dépôt (éditable en place) ; on ne crée jamais le fichier (contrairement à la V5) : si absent, on renvoie ``None`` (le moteur retombe sur sa config par défaut) ; - frozen : ``/config/dictionnaires.yml`` ; copie la version embarquée au premier lancement si absente, sans jamais écraser une config existante (perso établissement). Renvoie ``None`` si rien n'est résoluble (le moteur retombe alors sur sa config runtime par défaut). """ if not _frozen(): bundled = _bundled_config() return bundled if bundled.exists() else None user_cfg = Path(sys.executable).resolve().parent / _CONFIG_RELATIVE if user_cfg.exists(): return user_cfg try: user_cfg.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(_bundled_config(), user_cfg) return user_cfg except Exception as exc: log.warning("copie de la configuration externe échouée (%s) : %s", user_cfg, exc) bundled = _bundled_config() return bundled if bundled.exists() else None