Source code for bonafide.utils.dependencies
"""Utility module to check for required dependencies that are accessed through a Python
subprocess.
"""
import logging
import os
import shutil
import tempfile
from subprocess import run
from typing import List
from bonafide.utils.constants import XTB_VERSION_STRING
from bonafide.utils.helper_functions import get_function_or_method_name
[docs]
def _check_xtb_version() -> bool:
"""Check if the correct xtb version is installed.
Returns
-------
bool
``True`` if the correct xtb version is installed, ``False`` otherwise.
"""
try:
res = run(["xtb", "--version"], check=False, capture_output=True, text=True)
except Exception:
return False
if XTB_VERSION_STRING not in res.stdout.strip():
return False
return True
[docs]
def check_dependency_path(prg_name: str) -> str:
"""Check if a required program is installed and accessible in the system PATH.
Parameters
----------
prg_name : str
The name of the program to check for.
Returns
-------
str
The path to the program if it is found.
"""
_loc = get_function_or_method_name()
path = shutil.which(prg_name)
if path is None:
_errmsg = (
f"Required program '{prg_name}' is not installed or not found in PATH. "
"Install it and/or add it to your PATH environment variable."
)
raise ImportError(f"{_loc}(): {_errmsg}")
# Check xtb version (important because Fukui indices are not correctly printed in older
# versions)
if prg_name == "xtb":
if _check_xtb_version() is False:
_errmsg = "Installed xtb version is not supported. Install xtb version 6.7.1."
raise ImportError(f"{_loc}(): {_errmsg}")
return path
[docs]
def check_dependency_env(python_path: str, package_names: List[str], namespace: str) -> str:
"""Check if a required package is installed in a given Python environment.
It is first checked if the provided Python interpreter path is valid. Then, a temporary
Python script is created that checks if the required package is installed in the external
environment.
Parameters
----------
python_path : str
The path to the Python interpreter where the package is expected to be installed.
package_names : List[str]
A list of the package to check for.
namespace : str
The namespace of the currently handled molecule for logging purposes.
Returns
-------
str
The path to the Python interpreter if the package is found.
"""
_loc = get_function_or_method_name()
# Check if provided path is a valid Python interpreter
python_path = os.path.expanduser(python_path)
if not os.path.exists(python_path):
_errmsg = f"Provided Python interpreter path '{python_path}' is not valid."
logging.error(f"'{namespace}' | {_loc}()\n{_errmsg}")
raise ImportError(f"{_loc}(): {_errmsg}")
# Check if the required package is installed
for package_name in package_names:
# Check script
check_script = [
"try:",
" from importlib.metadata import distributions",
"except ImportError:",
" from importlib_metadata import distributions",
'packages = [dist.metadata["Name"] for dist in distributions()]',
f"if '{package_name}' not in packages:",
" print(False)",
"else:",
" print(True)",
]
check_script_str = "\n".join(check_script)
# Write the script to a temporary file
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as f:
f.write(check_script_str)
tmp = f.name
# Run script
res = run([python_path, tmp], check=False, capture_output=True, text=True)
# Check result
if res.stdout.strip() != "True":
_errmsg = (
f"Required package '{package_name}' is not installed in the "
f"environment of '{python_path}'."
)
logging.error(f"'{namespace}' | {_loc}()\n{_errmsg}")
raise ImportError(f"{_loc}(): {_errmsg}")
return python_path