from __future__ import annotations import importlib.util import os from pathlib import Path from types import ModuleType def _user_config_path() -> Path | None: explicit = os.environ.get("COPIENATOR_CONFIG", "").strip() if explicit: return Path(explicit).expanduser().resolve() local = Path.cwd() / "config.py" return local.resolve() if local.is_file() else None def _load_user_config(path: Path) -> ModuleType: if not path.is_file(): raise FileNotFoundError(f"Copienator configuration not found: {path}") spec = importlib.util.spec_from_file_location("_copienator_user_config", path) if spec is None or spec.loader is None: raise ImportError(f"Could not load Copienator configuration: {path}") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _settings(path: Path | None) -> dict[str, object]: import default_config values = { name: getattr(default_config, name) for name in dir(default_config) if not name.startswith("_") } if path is not None: # Preserve compatibility defaults for older personal configurations. values.update(THINKING_LEVEL_FOR_LABEL="medium", THINKING_LEVEL_PRO="high") module = _load_user_config(path) values.update({ name: getattr(module, name) for name in dir(module) if not name.startswith("_") }) # Historically the name recognizer inherited the label model when omitted. if not hasattr(module, "MODEL_FOR_NAME_ID"): values["MODEL_FOR_NAME_ID"] = values["MODEL_FOR_LABEL_ID"] values["PAGE_SPLITTER_KB"] = { "reverse_pages": "i", "add_secondary_splitter": "", "delete_secondary_splitter": "", **values["PAGE_SPLITTER_KB"], } return values CONFIG_PATH = _user_config_path() _global_settings = _settings(CONFIG_PATH) globals().update(_global_settings) LOCAL_CONFIG_PATH: Path | None = None _evaluation_overrides: set[str] = set() def use_evaluation(root: str | Path | None) -> None: """Apply selected evaluation settings, resetting any previous overrides.""" global LOCAL_CONFIG_PATH, _evaluation_overrides path = Path(root).expanduser().resolve() / "config.py" if root is not None else None values = dict(_global_settings) overrides = {} if path is not None and path.is_file() and path != CONFIG_PATH: module = _load_user_config(path) overrides = { name: getattr(module, name) for name in dir(module) if name.isupper() and name in _global_settings } values.update(overrides) else: path = None for name in ("ANNOTATION_MAX_HEIGHT_PX", "ANNOTATION_MAX_PIXELS"): if type(values[name]) is not int or values[name] <= 0: raise ValueError(f"{name} must be a positive integer (pixels)") values["PAGE_SPLITTER_KB"] = { "reverse_pages": "i", "add_secondary_splitter": "", "delete_secondary_splitter": "", **values["PAGE_SPLITTER_KB"], } for name in _evaluation_overrides | overrides.keys(): globals()[name] = values[name] _evaluation_overrides = set(overrides) LOCAL_CONFIG_PATH = path