fabric/modules/pip.py

366 lines
10 KiB
Python

import sys
import os
import logging
from fabric.api import env, task
from fabric.operations import run
from fabric.context_managers import hide, settings
from fabric.utils import abort
from .utils import virtualenv_source, virtualenv
from .utils import printerr
from .utils import booleanize, handle_flag_message
from .utils import prompt_continue
from .utils import upload_template
from .initialize import get_config
ERROR_BAD_BRANCH_PARAM = -3
ERROR_BAD_PARAM = -2
@task
def setup_virtualenv(python3=True):
"""
Sets up a virtual environment using virtualenvwrapper.
This method creates a new virtual environment with the specified Python
version. It uses the 'mkvirtualenv' command from virtualenvwrapper and can
optionally use Python 3.
:param python3: Specifies whether to use Python 3 for the virtual
environment, defaults to True.
:type python3: bool
Note:
- The virtual environment's name is taken from
`configuration.virtualenv.name`.
- If `env.debug` is True, the method logs additional debug information.
"""
configuration = env.config
if env.debug:
logging.basicConfig(
format='\n%(levelname)s: deploy.setup_virtualenv %(message)s',
level=logging.DEBUG)
# mkvirtualenv_cmd = "mkvirtualenv --no-site-packages "
mkvirtualenv_cmd = "mkvirtualenv "
if python3:
mkvirtualenv_cmd += "--python=python3 "
mkvirtualenv_cmd += f"{configuration.virtualenv.name}"
if env.debug:
logging.debug("python3 install: %s", python3)
logging.debug("virtualenv.workon : %s",
configuration.virtualenv.workon)
logging.debug("virtualenv.activate : %s",
configuration.virtualenv.activate)
logging.debug("virtualenv.name : %s",
configuration.virtualenv.name)
logging.debug("virtualenv.paths.bin : %s",
configuration.virtualenv.paths.bin)
logging.debug("virtualenv.paths.root : %s",
configuration.virtualenv.paths.root)
logging.debug("mkvirtualenv_cmd: %s", mkvirtualenv_cmd)
logging.debug(
"with virtualenv_source(): "
"run(\"\n\t%s\n\t\")", mkvirtualenv_cmd)
else:
# run("source virtualenvwrapper.sh; mkvirtualenv "
# "--no-site-packages {virtualenv_name}".format(
# virtualenv_name=configuration.virtualenv.name))
with virtualenv_source():
run(mkvirtualenv_cmd)
@task
def bootstrap_pip():
"""
Bootstraps pip in the current environment.
This method is a wrapper that calls the 'setup' method, intended for
setting up or bootstrapping pip in the current virtual environment.
Note:
- This method currently calls 'setup', which handles the installation
of packages via pip based on the requirements file.
"""
# upgrade()
setup()
@task
def update_pip():
"""
Updates pip to the latest version.
This method updates pip, the Python package installer, to its latest
version within the current virtual environment.
Note:
- The method assumes it's running inside a virtual environment.
- If `env.debug` is True, it logs the upgrade command being executed.
"""
# configuration = env.config
if env.debug:
logging.basicConfig(
format='\n%(levelname)s: deploy.pip %(message)s',
level=logging.DEBUG)
pipinstall_cmd = "pip install --upgrade pip"
if env.debug:
logging.debug("with virtualenv(): run(\"\n\t%s\n\t\")",
pipinstall_cmd)
else:
with virtualenv():
run(pipinstall_cmd)
@task
def setup():
"""
Installs all required packages via pip.
This method installs packages specified in a requirements file using pip.
The requirements file path is retrieved from
`configuration.virtualenv.requirements.filepath`.
Note:
- Assumes execution within a virtual environment.
- If `env.debug` is enabled, debug information is logged.
"""
configuration = env.config
if env.debug:
logging.basicConfig(
format='\n%(levelname)s: deploy.pip %(message)s',
level=logging.DEBUG)
pipinstall_cmd = (
f"pip install -r "
f"{configuration.virtualenv.requirements.filepath}"
)
if env.debug:
logging.debug("with virtualenv(): run(\"\n\t%s\n\t\")",
pipinstall_cmd)
else:
with virtualenv():
run(pipinstall_cmd)
@task
def install(package_name=None):
"""
Installs a package via pip within a virtual environment.
This method installs a specified package using pip. If the package_name
is '--all', it installs all packages listed in the requirements file.
:param package_name: Name of the package to install or '--all' to install
all packages from requirements file, defaults to None.
:type package_name: str, optional
:raises ValueError: If no package_name is provided.
"""
configuration = env.config
if not package_name:
abort("You must specify a package name to be installed")
if package_name == "--all":
pipinstall_cmd = (
f"pip install -r "
f"{configuration.virtualenv.requirements.filepath}"
)
else:
pipinstall_cmd = f"pip install {package_name}"
if env.debug:
print(f"pipinstall_cmd: {pipinstall_cmd}")
else:
with virtualenv():
run(pipinstall_cmd)
@task
def uninstall(package_name, silent=False):
"""
Uninstalls a specified package from the virtual environment.
This method activates the virtual environment and uses pip to uninstall the
specified package. It can optionally suppress command line output.
:param package_name: Name of the package to uninstall.
:type package_name: str
:param silent: Suppress command line output if True, defaults to False.
:type silent: bool
:return: None
"""
with virtualenv():
with settings(
hide('warnings', 'running', 'stdout', 'stderr'),
warn_only=True):
run(f"pip uninstall -y {package_name}", quiet=silent)
@task
def freeze(param='help'):
"""
True - update package list and freeze output
"""
configuration = env.config
msg_help = """
pip.freeze takes one of three values:
\thelp - this help message
\tTrue - update the pip package list the freeze output
\tFalse (default) - print the freeze output to the console
"""
if handle_flag_message(param, msg_help, 'help'):
sys.exit()
else:
try:
param = booleanize(param)
except TypeError:
printerr(
f"the parameter value you gave, '{param}',"
f" is not a valid parameter\n{msg_help}",
ERROR_BAD_PARAM
)
if param:
cmd_pipfreeze = (
f"pip freeze > "
f"{configuration.virtualenv.requirements.filepath}"
)
else:
cmd_pipfreeze = "pip freeze"
with virtualenv():
run(cmd_pipfreeze)
@task
def copyto(branch=None):
"""
copy requirements from the current branch to the specified branch
this only changes the requirements on the local branches. It does not
upload remotely. Use deploy.sync to perform remote updates.
Keyword Arguments:
branch -- the branch to copy to
Usage:
fab copyto --branch=staging
"""
if branch is None:
printerr(
"Missing required parameter: 'branch'\n"
"Usage: fab pip.copyto:<branch>\n"
"Valid options: development, staging, production",
ERROR_BAD_BRANCH_PARAM
)
return
configuration = env.config
branch_list = ['staging', 'production', 'development']
if branch not in branch_list:
printerr(
f"Branch parameter '{branch}' must be one of {branch_list} "
"values.", ERROR_BAD_BRANCH_PARAM)
elif branch == 'development':
printerr(
"This method does not allow copying to development branch",
ERROR_BAD_BRANCH_PARAM)
elif configuration.project.branch == branch:
printerr(
"You have set the source branch to the current branch."
"This will simply copy over \n\tthe requirements file for "
"this branch with itself", ERROR_BAD_BRANCH_PARAM)
branch_config = get_config(branch)
current_local_path = os.path.join(
configuration.virtualenv.requirements.local,
configuration.virtualenv.requirements.filename)
branch_local_path = os.path.join(
configuration.virtualenv.requirements.local,
branch_config.virtualenv.requirements.filename)
print(f"current_local_path: {current_local_path}")
print("branch_local_path: {branch_local_path}")
message = (
f"Copying file from current branch '{configuration.project.branch}'"
f"to destination branch '{branch}'. Continue? Y/n "
)
prompt_continue(message=message)
upload_template(
filename=configuration.virtualenv.requirements.filename,
destination=branch_local_path,
context=None,
use_jinja=False,
use_sudo=False,
backup=True,
template_dir=configuration.virtualenv.requirements.local)
def check_package_installed(package_name, silent=False):
"""
Checks if a specified package is installed in the virtual environment.
Activates the virtual environment and uses 'pip list' to check if the
specified package is installed. Can optionally suppress command line
output.
:param package_name: Name of the package to check.
:type package_name: str
:param silent: Suppress command line output if True, defaults to False.
:type silent: bool
:return: True if installed, False otherwise.
:rtype: bool
"""
with virtualenv():
with settings(
hide('warnings', 'running', 'stdout', 'stderr'),
warn_only=True):
output = run(f"pip list | grep {package_name}", quiet=silent)
return package_name in output