Source code for getscipapers_hoanganhduc.configuration

"""Centralized configuration and credential utilities for getscipapers.

This module keeps runtime settings in one place to reduce the amount of
cross-module global state. Functions that previously lived in
``getpapers.py`` now reside here so other modules can import and share a
single source of truth for paths and credentials.
"""

from __future__ import annotations

import json
import os
import platform
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Optional

DEFAULT_LIMIT = 5

EMAIL = ""
ELSEVIER_API_KEY = ""
WILEY_TDM_TOKEN = ""
IEEE_API_KEY = ""


def _default_config_file() -> Path:
    if platform.system() == "Windows":
        return Path(os.path.expanduser("~")) / "AppData" / "Local" / "getscipapers" / "getpapers" / "config.json"
    return Path(os.path.expanduser("~")) / ".config" / "getscipapers" / "getpapers" / "config.json"


GETPAPERS_CONFIG_FILE = _default_config_file()
UNPYWALL_CACHE_DIR = GETPAPERS_CONFIG_FILE.parent
UNPYWALL_CACHE_FILE = UNPYWALL_CACHE_DIR / "unpywall_cache"


[docs] @dataclass class Credentials: email: str = "" elsevier_api_key: str = "" wiley_tdm_token: str = "" ieee_api_key: str = ""
[docs] def normalized_email(self) -> str: return (self.email or "").strip()
[docs] def require_email(self) -> str: email = self.normalized_email() if not email: raise ValueError( "Missing required email for API requests. Set GETSCIPAPERS_EMAIL " "or provide a config file via --credentials (or run interactively " "without --non-interactive)." ) return email
[docs] def to_dict(self) -> Dict[str, str]: return { "email": self.email, "elsevier_api_key": self.elsevier_api_key, "wiley_tdm_token": self.wiley_tdm_token, "ieee_api_key": self.ieee_api_key, }
[docs] def ensure_directory_exists(path: Path) -> None: if path and not path.exists(): path.mkdir(parents=True, exist_ok=True)
[docs] def get_default_download_folder(create: bool = False) -> str: system = platform.system() if system == "Windows": base = os.environ.get("USERPROFILE", os.path.expanduser("~")) folder = Path(base) / "Downloads" / "getscipapers" / "getpapers" else: folder = Path(os.path.expanduser("~")) / "Downloads" / "getscipapers" / "getpapers" if create: ensure_directory_exists(folder) return str(folder)
DEFAULT_DOWNLOAD_FOLDER = get_default_download_folder() def _update_globals(creds: Credentials) -> None: global EMAIL, ELSEVIER_API_KEY, WILEY_TDM_TOKEN, IEEE_API_KEY EMAIL = creds.email ELSEVIER_API_KEY = creds.elsevier_api_key WILEY_TDM_TOKEN = creds.wiley_tdm_token IEEE_API_KEY = creds.ieee_api_key
[docs] def save_credentials( email: str | None = None, elsevier_api_key: str | None = None, wiley_tdm_token: str | None = None, ieee_api_key: str | None = None, config_file: Optional[str] = None, verbose: bool = False, ) -> bool: ICON_SUCCESS = "✅" ICON_ERROR = "❌" ICON_INFO = "ℹ️" if config_file is None: config_file = str(GETPAPERS_CONFIG_FILE) config_path = Path(config_file) config_dir = config_path.parent try: ensure_directory_exists(config_dir) if verbose: print(f"{ICON_SUCCESS} Ensured config directory exists: {config_dir}") except Exception as e: # pragma: no cover - defensive logging if verbose: print(f"{ICON_ERROR} Error creating config directory {config_dir}: {e}") return False existing_config: Dict[str, str] = {} if config_path.exists(): try: existing_config = json.loads(config_path.read_text(encoding="utf-8")) except Exception as e: # pragma: no cover - defensive logging if verbose: print(f"{ICON_INFO} Warning: Could not read existing config file {config_file}: {e}") if email is not None: existing_config["email"] = email if elsevier_api_key is not None: existing_config["elsevier_api_key"] = elsevier_api_key if wiley_tdm_token is not None: existing_config["wiley_tdm_token"] = wiley_tdm_token if ieee_api_key is not None: existing_config["ieee_api_key"] = ieee_api_key try: config_path.write_text(json.dumps(existing_config, indent=2), encoding="utf-8") if verbose: print(f"{ICON_SUCCESS} Saved credentials to {config_file}") return True except Exception as e: # pragma: no cover - defensive logging if verbose: print(f"{ICON_ERROR} Error saving config file {config_file}: {e}") return False
[docs] def load_credentials( config_file: Optional[str] = None, interactive: Optional[bool] = None, env_prefix: str = "GETSCIPAPERS_", verbose: bool = False, ) -> Credentials: ICON_SUCCESS = "✅" ICON_ERROR = "❌" ICON_INFO = "ℹ️" ICON_INPUT = "📝" ICON_TIMEOUT = "⏰" ICON_WARNING = "⚠️" if config_file is None: config_file = str(GETPAPERS_CONFIG_FILE) if interactive is None: interactive = sys.stdin.isatty() default_config = { "email": "", "elsevier_api_key": "", "wiley_tdm_token": "", "ieee_api_key": "", } existing_config = default_config.copy() config_path = Path(config_file) file_exists = config_path.exists() if file_exists: try: existing_config = json.loads(config_path.read_text(encoding="utf-8")) if verbose: print(f"{ICON_SUCCESS} Loaded existing config from {config_file}") except (json.JSONDecodeError, Exception) as e: if verbose: print(f"{ICON_ERROR} Error loading config file {config_file}: {e}") print(f"{ICON_ERROR} Configuration file {config_file} is corrupted. Will recreate.") file_exists = False existing_config = default_config.copy() env_config = { "email": os.getenv(f"{env_prefix}EMAIL", ""), "elsevier_api_key": os.getenv(f"{env_prefix}ELSEVIER_API_KEY", ""), "wiley_tdm_token": os.getenv(f"{env_prefix}WILEY_TDM_TOKEN", ""), "ieee_api_key": os.getenv(f"{env_prefix}IEEE_API_KEY", ""), } merged_config = existing_config.copy() for key, env_val in env_config.items(): if env_val: merged_config[key] = env_val missing_keys = [k for k, v in merged_config.items() if not v] if missing_keys and interactive: try: print("🔑 Some credentials are missing. Please enter them below (leave blank to keep existing value):") for key in missing_keys: prompt = f"{ICON_INPUT} Enter {key.replace('_', ' ').title()}: " user_input = input(prompt).strip() if user_input: merged_config[key] = user_input if merged_config != existing_config: if save_credentials(config_file=config_file, verbose=verbose, **merged_config): print(f"{ICON_SUCCESS} Credentials saved to {config_file}") elif verbose: print(f"{ICON_INFO} No changes made to credentials.") except KeyboardInterrupt: print(f"\n{ICON_WARNING} Credential input interrupted by user.") except Exception as e: # pragma: no cover - defensive logging print(f"{ICON_TIMEOUT} An error occurred while reading input: {e}") creds = Credentials(**merged_config) _update_globals(creds) return creds
[docs] def require_email(email: Optional[str] = None) -> str: if email is not None: normalized = email.strip() if normalized: return normalized creds = Credentials(EMAIL, ELSEVIER_API_KEY, WILEY_TDM_TOKEN, IEEE_API_KEY) return creds.require_email()
__all__ = [ "Credentials", "DEFAULT_LIMIT", "DEFAULT_DOWNLOAD_FOLDER", "EMAIL", "ELSEVIER_API_KEY", "WILEY_TDM_TOKEN", "IEEE_API_KEY", "GETPAPERS_CONFIG_FILE", "UNPYWALL_CACHE_DIR", "UNPYWALL_CACHE_FILE", "ensure_directory_exists", "get_default_download_folder", "load_credentials", "require_email", "save_credentials", ]