Source code for bonafide.utils.base_mixin

"""Mixin class with common base functionality for ``BaseFeaturizer`` and ``BaseSinglePoint``."""

from __future__ import annotations

import logging
import os
import shutil
import uuid
from datetime import datetime
from typing import TYPE_CHECKING, Dict, List, Optional, Union

from bonafide.utils.helper_functions import get_function_or_method_name

if TYPE_CHECKING:
    import numpy as np
    from numpy.typing import NDArray


[docs] class _BaseMixin: """Set up a temporary working directory before the feature or single-point energy calculation and save the output files after the calculation is done. Attributes ---------- _keep_output_files : bool If ``True``, all output files created during the feature calculations are kept. If ``False``, they are removed when the calculation is done. conformer_name : str The name of the conformer for which the feature is requested. work_dir_name : Optional[str] The name of the working directory where temporary files are stored during feature calculation. """ _keep_output_files: bool conformer_name: str work_dir_name: str # Common attributes for child classes charge: Optional[int] coordinates: Optional[NDArray[np.float64]] electronic_struc_n: Optional[str] electronic_struc_n_plus1: Optional[str] electronic_struc_n_minus1: Optional[str] elements: NDArray[np.str_] global_feature_cache: List[Dict[str, Optional[Union[str, bool, int, float]]]] multiplicity: Optional[int]
[docs] def _setup_work_dir(self) -> None: """Set up the temporary working directory for a feature or single-point energy calculation. The temporary working directory is set up inside the output files directory. If the user did not request an output files directory, ``_output_directory`` is set to the current working directory (in which the working directory is then created). Returns ------- None """ _loc = f"{self.__class__.__name__}.{get_function_or_method_name()}" # Define working directory name self.work_dir_name = ( f"_w__{self.conformer_name}__" f"{datetime.now().strftime('%Y%m%d%H%M%S%f')[2:]}-{uuid.uuid4().hex[:6]}" ) # Check if the directory already exists if os.path.exists(self.work_dir_name): _errmsg = ( f"Temporary working directory path at " f"'{os.path.abspath(self.work_dir_name)}' already exists." ) _namespace = self.conformer_name[::-1].split("__", 1)[-1][::-1] logging.error(f"'{_namespace}' | {_loc}()\n{_errmsg}") raise FileExistsError(f"{_loc}(): {_errmsg}") # Create directory and change to it os.mkdir(self.work_dir_name) os.chdir(self.work_dir_name)
[docs] def _save_output_files(self) -> None: """Save the potentially generated output files during a feature or single-point energy calculation and delete the temporary working directory. The child classes (feature factories) are responsible for deciding which files to preserve. If ``_keep_output_files`` is ``False``, no output files are saved. Returns ------- None """ _loc = f"{self.__class__.__name__}.{get_function_or_method_name()}" # Leave working directory os.chdir("..") # Save output files if requested by the user if self._keep_output_files is True: for item in os.listdir(self.work_dir_name): try: source = os.path.join(self.work_dir_name, item) dest = os.path.join(os.getcwd(), item) if os.path.isfile(source): shutil.copy2(source, dest) if os.path.isdir(source): shutil.copytree(source, dest) except Exception as e: _errmsg = f"Could not copy '{source}' to '{dest}': {e}." _namespace = self.conformer_name[::-1].split("__", 1)[-1][::-1] logging.error(f"'{_namespace}' | {_loc}()\n{_errmsg}") raise IOError(f"{_loc}(): {_errmsg}") # Delete working directory shutil.rmtree(self.work_dir_name)