import os from pathlib import Path from PyInstaller.utils.hooks import collect_all, copy_metadata # Spec CLI frozen — EXE de PRODUCTION (anonymisation fichier unique sans GUI). # Même moteur / mêmes datas que anonymisation_onefile.spec, mais : # - entrypoint = scripts/anonymize_cli.py (CLI production, pas launcher.py) # Contrat : Anonymisation-CLI.exe # Modèle CamemBERT-bio ONNX OBLIGATOIRE (fail-closed, code 3 si absent). # - console=True (CLI), pas de Splash # - name = Anonymisation-CLI -> ne remplace pas dist/Anonymisation.exe # (Le harnais perf D-19 reste scripts/anonymize_batch_cli.py, non buildé ici.) block_cipher = None project_dir = Path(globals().get("SPECPATH", os.getcwd())).resolve() def _data_entry(relative_path: str, target_dir: str | None = None): src = project_dir / relative_path if not src.exists(): return None return (str(src), target_dir or relative_path) binaries = [] datas = [] for relative_path, target_dir in [ ("config", "config"), ("data/bdpm", "data/bdpm"), ("data/finess", "data/finess"), ("data/insee", "data/insee"), ("models/camembert-bio-deid/onnx", "models/camembert-bio-deid/onnx"), ("detectors", "detectors"), ("scripts", "scripts"), ("assets", "assets"), ]: entry = _data_entry(relative_path, target_dir) if entry is not None: datas.append(entry) for relative_path in [ "data/stopwords_manuels.txt", "data/villes_blacklist.txt", "data/dpi_labels_blacklist.txt", "data/companion_blacklist.txt", ]: entry = _data_entry(relative_path, "data") if entry is not None: datas.append(entry) onnxtr_cache_dir = Path(os.environ.get("ONNXTR_CACHE_DIR", Path.home() / ".cache" / "onnxtr")) required_onnxtr_weights = [ "db_resnet50-69ba0015.onnx", "crnn_vgg16_bn-743599aa.onnx", ] missing_onnxtr_weights = [] for filename in required_onnxtr_weights: src = onnxtr_cache_dir / "models" / filename if src.exists(): datas.append((str(src), "models/onnxtr/models")) else: missing_onnxtr_weights.append(str(src)) if missing_onnxtr_weights: raise FileNotFoundError( "Poids OCR OnnxTR manquants pour le build frozen : " + ", ".join(missing_onnxtr_weights) + ". Précharger OnnxTR (lancer une OCR une fois) ou définir ONNXTR_CACHE_DIR avant PyInstaller." ) hiddenimports = [ "anonymizer_core_refactored_onnx", "admin_rules", "config_defaults", "profile_defaults", "gui_batch_paths", "manual_masking", "pdf_mask_designer", "format_converter", "ner_manager_onnx", "camembert_ner_manager", "eds_pseudo_manager", "gliner_manager", "vlm_manager", "build_info", # OCR OnnxTR (ONNX Runtime, remplace docTR — sans torch ni doctr) "onnxtr", "onnxtr.io", "onnxtr.models", "onnxtr.models.detection", "onnxtr.models.recognition", "onnxtr.utils", "onnxtr.utils.data", # Dépendances transitives OnnxTR (hiddenimports défensifs vs omission PyInstaller) "pyclipper", "scipy.cluster.hierarchy", "scipy.special", "cv2", "edsnlp", "edsnlp.pipes", "edsnlp.pipes.ner", "edsnlp.pipes.ner.pseudo", "spacy", "spacy.lang.fr", "gliner", "onnxruntime", "transformers", "tokenizers", "pdfplumber", "fitz", "PIL", "yaml", "loguru", "regex", "optimum", "optimum.onnxruntime", "optimum.pipelines", "optimum.modeling_base", "optimum.exporters.onnx", ] def _collect_optional_package(package_name: str): try: package_datas, package_binaries, package_hiddenimports = collect_all(package_name) datas.extend(package_datas) binaries.extend(package_binaries) hiddenimports.extend(package_hiddenimports) try: datas.extend(copy_metadata(package_name, recursive=True)) except Exception: pass except Exception: pass for _package_name in [ "edsnlp", "spacy", "thinc", "blis", "srsly", "catalogue", "confection", "cymem", "preshed", "murmurhash", "gliner", "loguru", ]: _collect_optional_package(_package_name) a = Analysis( [str(project_dir / "scripts" / "anonymize_cli.py")], pathex=[str(project_dir)], binaries=binaries, datas=datas, hiddenimports=hiddenimports, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name="Anonymisation-CLI", debug=False, strip=False, upx=False, console=True, )