"""
formelsammlung.venv_utils
~~~~~~~~~~~~~~~~~~~~~~~~~
Utility function for working with virtual environments.
:copyright: 2020 (c) Christian Riedel
:license: GPLv3, see LICENSE file for more details
""" # noqa: D205, D208, D400
import contextlib
import os
import shutil
import sys
from pathlib import Path
from typing import Optional, Tuple, Union
OS_BIN = "Scripts" if sys.platform == "win32" else "bin"
[docs]def get_venv_path() -> Path:
"""Get path to the venv from where the python executable runs.
:raises FileNotFoundError: when no calling venv can be detected.
:return: Return venv path
"""
if hasattr(sys, "real_prefix"):
return Path(sys.real_prefix) # type: ignore[no-any-return,attr-defined] # pylint: disable=E1101
if sys.base_prefix != sys.prefix:
return Path(sys.prefix)
raise FileNotFoundError("No calling venv could be detected.")
[docs]def get_venv_bin_dir(venv_path: Union[str, Path]) -> Path:
"""Return path to bin/Scripts dir of given venv.
:param venv_path: Path to venv
:raises FileNotFoundError: when no bin/Scripts dir can be found for given venv.
:return: Path to bin/Scripts dir
"""
bin_dir = Path(venv_path) / OS_BIN
if bin_dir.is_dir():
return bin_dir
raise FileNotFoundError(f"Given venv has no '{OS_BIN}' directory.")
[docs]def get_venv_tmp_dir(
venv_path: Union[str, Path],
search_tmp_dirs: Optional[Tuple[str]] = None,
create_if_missing: bool = False,
create_dir_name: Optional[str] = None,
) -> Path:
"""Return path to tmp/temp dir of given venv.
:param venv_path: Path to venv
:param search_tmp_dirs: List of temp dir names to look for;
defaults to ("tmp", "temp", ".tmp", ".temp")
:param create_if_missing: Create a temp dir in the given venv if non exists;
defaults to ``False``
:param create_dir_name: Name of the venv to create; defaults to ``tmp``
:raises FileNotFoundError: when no tmp/temp dir can be found for given venv.
:return: Path to tmp/temp dir
"""
tmp_dirs = search_tmp_dirs if search_tmp_dirs else ("tmp", "temp", ".tmp", ".temp")
for tmp_dir in tmp_dirs:
tmp_path = Path(venv_path) / tmp_dir
if tmp_path.is_dir():
return tmp_path
if create_if_missing:
tmp_path = Path(venv_path) / (create_dir_name if create_dir_name else "tmp")
tmp_path.mkdir(exist_ok=True)
return tmp_path
raise FileNotFoundError(f"Given venv has non of theses directories: {tmp_dirs}.")
[docs]def get_venv_site_packages_dir(venv_path: Union[str, Path]) -> Path:
"""Return path to site-packages dir of given venv.
:param venv_path: Path to venv
:raises FileNotFoundError: when no site-packages dir can be found for given venv.
:return: Path to site-packages dir
"""
paths = list(Path(venv_path).glob("**/site-packages"))
if paths:
return paths[0]
raise FileNotFoundError("Given venv has no 'site-packages' directory.")
[docs]def where_installed(program: str) -> Tuple[int, Optional[str], Optional[str]]:
"""Find installation locations for given program.
Return exit code and locations based on found installation places.
Search in current venv and globally.
Exit codes:
- 0 = nowhere
- 1 = venv
- 2 = global
- 3 = both
:param program: Program to search
:return: Exit code, venv executable path, glob executable path
"""
exit_code = 0
exe = shutil.which(program)
if not exe:
return exit_code, None, None
venv_path = None
with contextlib.suppress(FileNotFoundError):
venv_path = get_venv_path()
bin_dir = "\\Scripts" if sys.platform == "win32" else "/bin"
path_wo_venv = os.environ["PATH"].replace(f"{venv_path}{bin_dir}", "")
glob_exe = shutil.which(program, path=path_wo_venv)
if glob_exe is None:
exit_code += 1
elif exe == glob_exe:
exit_code += 2
exe = None
else:
exit_code += 3
return exit_code, exe, glob_exe