Last active
June 14, 2023 18:40
-
-
Save dakalamin/cc0144e5bee9f756b8281ccc1a074deb to your computer and use it in GitHub Desktop.
Script that ensures all packages from requirements.txt are installed. If not - finds/creates a venv, installs the packages and relaunches the script with same arguments from this venv. Non-venv pip is guaranteed not to be modified in any way.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import subprocess | |
import sys | |
from pathlib import Path | |
import pkg_resources | |
PLATFORM_IS_WIN = sys.platform == 'win32' | |
EXE_SUFFIX = '.exe' if PLATFORM_IS_WIN else '' | |
SCRIPT_PATH = Path(__file__).resolve() | |
PYTHON_PATH = Path(sys.base_prefix).joinpath('python').with_suffix(EXE_SUFFIX) | |
REQUIREMENTS_FILENAME = 'requirements.txt' | |
REQUIREMENTS_PATH = SCRIPT_PATH.with_name(REQUIREMENTS_FILENAME) | |
VENV_NAME = '.venv' | |
VENV_PATH = SCRIPT_PATH.with_name(VENV_NAME) | |
VENV_BIN_PATH = VENV_PATH.joinpath('Scripts' if PLATFORM_IS_WIN else 'bin') | |
VENV_PYTHON_PATH = VENV_BIN_PATH.joinpath('python').with_suffix(EXE_SUFFIX) | |
VENV_PIP_PATH = VENV_BIN_PATH.joinpath('pip').with_suffix(EXE_SUFFIX) | |
# extra precautionary measure in order to prevent any modifications of non-venv pip | |
VENV_PIP_ARG = '--require-virtualenv' | |
def ensure_requirements() -> None: | |
if _all_required_packages_found(): | |
return | |
if _launched_from_venv(): | |
_install_from_requirements_file() | |
return | |
if not _venv_exists(): | |
_create_venv() | |
_install_from_requirements_file() | |
_relaunch_script() | |
sys.exit() | |
def _all_required_packages_found() -> bool: | |
with open(REQUIREMENTS_PATH, 'r') as requirements_file: | |
requirements = pkg_resources.parse_requirements(requirements_file) | |
# map converts to list so every unsatisfied requirement is printed out | |
requirements_satisfied = list(map(_requirement_satisfied, requirements)) | |
return all(requirements_satisfied) | |
def _requirement_satisfied(req: pkg_resources.Requirement) -> bool: | |
try: | |
pkg_resources.require(str(req)) | |
return True | |
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict) as error: | |
print(error) | |
return False | |
def _launched_from_venv() -> bool: | |
return sys.prefix != sys.base_prefix | |
def _install_from_requirements_file() -> None: | |
print("Installing all required packages...") | |
args = [VENV_PIP_PATH, VENV_PIP_ARG, 'install', '-r', REQUIREMENTS_PATH] | |
subprocess.run(args) | |
def _venv_exists() -> bool: | |
paths_to_check = [ | |
VENV_PYTHON_PATH, | |
VENV_PIP_PATH | |
] | |
return all(map(Path.exists, paths_to_check)) | |
def _relaunch_script() -> None: | |
print(f"Relaunching script from {VENV_NAME} virtual env...") | |
subprocess.run([VENV_PYTHON_PATH, *sys.argv]) | |
def _create_venv() -> None: | |
print(f"Creating {VENV_NAME} virtual env...") | |
subprocess.run([PYTHON_PATH, '-m', 'venv', VENV_PATH]) | |
print(f"Upgrading pip in {VENV_NAME} virtual env...") | |
args = [VENV_PYTHON_PATH, '-m', 'pip', VENV_PIP_ARG, 'install', '--upgrade', 'pip'] | |
subprocess.run(args) | |
if __name__ == '__main__': | |
ensure_requirements() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment