from fabric.api import env, task from fabric.operations import run from fabric.context_managers import hide, settings from fabric.utils import abort import sys import os import logging from .utils import virtualenv_source, virtualenv from .utils import printerr 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 += "{virtualenv_name}".format( virtualenv_name=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{mkvirtualenv_cmd}\n\t\")".format( mkvirtualenv_cmd=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 = "pip install -r {requirements}".format( requirements=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 = "pip install -r {requirements_file}".format( requirements_file=configuration.virtualenv.requirements.filepath) else: pipinstall_cmd = "pip install {package}".format( package=package_name) if env.debug: print("pipinstall_cmd: %s" % 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 """ from .utils import booleanize, handle_flag_message if handle_flag_message(param, msg_help, 'help'): sys.exit() else: try: param = booleanize(param) except TypeError: printerr( "the parameter value you gave, '{param}' , is not a " "valid parameter\n{msg_help}".format( param=param, msg_help=msg_help), ERROR_BAD_PARAM ) if param: cmd_pipfreeze = "pip freeze > {requirements}".format( requirements=configuration.virtualenv.requirements.filepath) else: cmd_pipfreeze = "pip freeze" with virtualenv(): run(cmd_pipfreeze) @task def copyto(branch): """ copy requirements from the current branch to the specified branch this only changes the requirements on the local branches. It does not upload remotely. This is because I want to use deploy.sync to do all remote updates Keyword Arguments: branch -- the branch to copy to """ configuration = env.config branch_list = ['staging', 'production', 'development'] if branch not in branch_list: printerr( "Branch parameter '{branch}' must be one of {branchlist} " "values.".format(branch=branch, branchlist=branch_list), 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) from .initialize import get_config 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("current_local_path: %s" % current_local_path) print("branch_local_path: %s" % branch_local_path) message = "Copying file from current branch '{branch_src}' to " \ "destination branch '{branch_dst}'. Continue? Y/n ".format( branch_src=configuration.project.branch, branch_dst=branch) from .utils import prompt_continue prompt_continue(message=message) from .utils import upload_template 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