"""Échange de configuration par fichier JSON (workflow email V5, P1-3). - ``build_export_payload`` : produit le dict V5 (format consommé par ``scripts/merge_params.merge_params``) à partir des listes du profil courant ; - ``import_config_file`` : fusionne un JSON reçu dans le ``dictionnaires.yml`` utilisateur, sans écraser l'existant. Aucune dépendance à un widget : testable en pur Python. """ from __future__ import annotations from datetime import datetime, timezone from pathlib import Path from typing import Iterable EXPORT_VERSION = "1" def build_export_payload( whitelist: Iterable[str], blacklist: Iterable[str], version: str = EXPORT_VERSION ) -> dict: """Construit la charge utile d'export au format consommé par merge_params.""" return { "version": version, "date_export": datetime.now(timezone.utc).isoformat(), "whitelist_phrases": [str(t) for t in whitelist], "blacklist_force_mask_terms": [str(t) for t in blacklist], } def _yaml_lists(config_path: Path) -> tuple[set, set]: import yaml cfg = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} wl = set(cfg.get("whitelist_phrases", []) or []) bl = set((cfg.get("blacklist", {}) or {}).get("force_mask_terms", []) or []) return wl, bl def import_config_file(json_path, config_path) -> bool: """Fusionne ``json_path`` dans ``config_path`` (YAML). Retourne True si la config a changé, False si rien de nouveau. Fusion autonome (union des listes, jamais d'écrasement) — volontairement SANS dépendance à ``scripts/merge_params`` (non bundlé en frozen). Même sémantique : ``whitelist_phrases`` et ``blacklist.force_mask_terms``. """ import json import yaml json_path = Path(json_path) config_path = Path(config_path) before_wl, before_bl = _yaml_lists(config_path) data = json.loads(json_path.read_text(encoding="utf-8")) incoming_wl = {str(t).strip() for t in data.get("whitelist_phrases", []) if str(t).strip()} incoming_bl = {str(t).strip() for t in data.get("blacklist_force_mask_terms", []) if str(t).strip()} after_wl = before_wl | incoming_wl after_bl = before_bl | incoming_bl if after_wl == before_wl and after_bl == before_bl: return False cfg = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} cfg["whitelist_phrases"] = sorted(after_wl) cfg.setdefault("blacklist", {}) cfg["blacklist"]["force_mask_terms"] = sorted(after_bl) config_path.write_text( yaml.dump(cfg, allow_unicode=True, default_flow_style=False, sort_keys=False), encoding="utf-8", ) return True