From b4abc6c7dede78cf15176eeffdfcfd3c7658f587 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Wed, 16 Feb 2022 22:33:20 -0800 Subject: [PATCH] add get-pipx script --- CHANGELOG.md | 2 + README.md | 49 ++-- docs/installation.md | 101 ++++++++- get-pipx.py | 225 ++++++++++++++++++- get_pipx.py | 1 + testdata/tests_packages/primary_packages.txt | 2 + testdata/tests_packages/unix-python3.9.txt | 200 +++++++++-------- tests/test_get_pipx.py | 42 ++++ 8 files changed, 494 insertions(+), 128 deletions(-) create mode 120000 get_pipx.py create mode 100644 tests/test_get_pipx.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b0d0591d7..687570d6dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## dev +- Add get-pipx.py script + ## 1.1.0 - Fix encoding issue on Windows when pip fails to install a package diff --git a/README.md b/README.md index 47bc1795af..458198dea3 100644 --- a/README.md +++ b/README.md @@ -22,43 +22,58 @@ **Source Code**: https://github.com/pypa/pipx -_For comparison to other tools including pipsi, see [Comparison to Other Tools](https://pypa.github.io/pipx/comparisons/)._ +_For comparison to other tools, see [Comparison to Other Tools](https://pypa.github.io/pipx/comparisons/)._ ## Install pipx -### On macOS +There are several ways to install pipx. Below are the most common. For other installation methods, see [installation](https://pypa.github.io/pipx/installation/). + +### Linux, macOS + +The `get-pipx.py` installation script installs pipx with pipx itself. + +After it's finished, you can upgrade pipx with `pipx upgrade pipx`. To uninstall, you can run `pipx uninstall pipx`. ``` -brew install pipx -pipx ensurepath +curl https://raw.githubusercontent.com/pypa/pipx/main/get-pipx.py | python3 ``` -Upgrade pipx with `brew update && brew upgrade pipx`. +If python3 is not found on your PATH or there is a syntax error/typo, `curl` will fail with the error message: "(23) Failed writing body." -### On Linux, install via pip (requires pip 19.0 or later) +To pass arguments to the `get-pipx.py` script: ``` -python3 -m pip install --user pipx -python3 -m pipx ensurepath +curl https://raw.githubusercontent.com/pypa/pipx/main/get-pipx.py | python3 - ARGS ``` -Upgrade pipx with `python3 -m pip install --user -U pipx`. +For example, you can see options with + +``` +curl https://raw.githubusercontent.com/pypa/pipx/main/get-pipx.py | python3 - --help +``` -### On Windows, install via pip (requires pip 19.0 or later) +The default binary location is ~/.local/bin. This can be overridden with the environment variable PIPX_BIN_DIR. + +pipx's default virtual environment location is ~/.local/pipx. This can be overridden with the environment variable PIPX_HOME. + +### Windows + +Install via pip (requires pip 19.0 or later) ``` # If you installed python using the app-store, replace `python` with `python3` in the next line. python -m pip install --user pipx ``` -It is possible (even most likely) the above finishes with a WARNING looking similar to this: +It is possible the above finishes with a warning such as: ``` WARNING: The script pipx.exe is installed in `\AppData\Roaming\Python\Python3x\Scripts` which is not on PATH ``` If so, go to the mentioned folder, allowing you to run the pipx executable directly. -Enter the following line (even if you did not get the warning): + +Enter the following line : ``` pipx ensurepath @@ -67,8 +82,6 @@ pipx ensurepath This will add both the above mentioned path and the `%USERPROFILE%\.local\bin` folder to your search path. Restart your terminal session and verify `pipx` does run. -Upgrade pipx with `python3 -m pip install --user -U pipx`. - ### Shell completions Shell completions are available by following the instructions printed with this command: @@ -77,8 +90,6 @@ Shell completions are available by following the instructions printed with this pipx completions ``` -For more details, see the [installation instructions](https://pypa.github.io/pipx/installation/). - ## Overview: What is `pipx`? pipx is a tool to help you install and run end-user applications written in Python. It's roughly similar to macOS's `brew`, JavaScript's [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b), and Linux's `apt`. @@ -124,14 +135,14 @@ This automatically creates a virtual environment, installs the package, and adds Example: ``` ->> pipx install pycowsay +> pipx install pycowsay installed package pycowsay 2.0.3, Python 3.7.3 These apps are now globally available - pycowsay done! ✨ 🌟 ✨ ->> pipx list +> pipx list venvs are in /home/user/.local/pipx/venvs apps are exposed on your $PATH at /home/user/.local/bin package pycowsay 2.0.3, Python 3.7.3 @@ -139,7 +150,7 @@ apps are exposed on your $PATH at /home/user/.local/bin # Now you can run pycowsay from anywhere ->> pycowsay mooo +> pycowsay mooo ____ < mooo > ==== diff --git a/docs/installation.md b/docs/installation.md index 3c87f1bd0c..ddba6ecac6 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,26 +2,101 @@ python 3.7+ is required to install pipx. pipx can run binaries from packages with Python 3.3+. Don't have Python 3.7 or later? See [Python 3 Installation & Setup Guide](https://realpython.com/installing-python/). -You also need to have `pip` installed on your machine for `python3`. Installing it varies from system to system. Consult [pip's installation instructions](https://pip.pypa.io/en/stable/installing/). Installing on Linux works best with a [Linux Package Manager](https://packaging.python.org/guides/installing-using-linux-tools/#installing-pip-setuptools-wheel-with-linux-package-managers). +You also need to have `pip` and `venv` installed on your machine for `python3`. Installing pip varies from system to system. Consult [pip's installation instructions](https://pip.pypa.io/en/stable/installing/). Installing on Linux works best with a [Linux Package Manager](https://packaging.python.org/guides/installing-using-linux-tools/#installing-pip-setuptools-wheel-with-linux-package-managers). -pipx works on macOS, linux, and Windows. +pipx works on macOS, Linux, and Windows. ## Install pipx -On macOS: +### pipx installer for Linux, macOS, Windows (recommended) + +This method installs pipxin a temporary environment, then installs pipx _with pipx_. That's right, pipx is a command line tool, so installing it with itself isolates its dependencies just like any other python tool. + +After it's finished, you can upgrade pipx with `pipx upgrade pipx`. To uninstall, you can run `pipx uninstall pipx`. + +``` +curl https://raw.githubusercontent.com/pypa/pipx/main/get-pipx.py | python3 +``` + +You can replace `python3` with whichever python binary you want to install pipx with. + +If python3 is not found on your PATH or there is a syntax error/typo, `curl` will fail with the error message: "(23) Failed writing body." + +To pass arguments to the `get-pipx.py` script: + +``` +curl https://raw.githubusercontent.com/pypa/pipx/main/get-pipx.py | python3 - ARGS +``` + +For example, you can see options with + +``` +> curl https://raw.githubusercontent.com/pypa/pipx/main/get-pipx.py | python3 - --help +usage: get-pipx.py [-h] [--spec SPEC] [--no-modify-path] [--force] + [--python PYTHON] [--verbose] + +Installer script to install pipx. After installing, pipx will be available for +use. pipx will be by itself, so you can run `pipx upgrade pipx` or `pipx +uninstall pipx`. Environment variables PIPX_BIN_DIR and PIPX_HOME can be used. + +optional arguments: + -h, --help show this help message and exit + --spec SPEC The package specification of pipx to install. This value + is passed to "pip install ". For example, to + install from the github repository use + `git+https://github.com/pypa/pipx.git`. Default: pipx + --no-modify-path Don't configure the PATH environment variable + --force reinstall pipx even if existing installation was found + --python PYTHON The Python binary to associate pipx with. Must be v3.7+. + --verbose Display more detailed output +``` + +### macOS ``` brew install pipx pipx ensurepath ``` -Otherwise, install via pip (requires pip 19.0 or later): +### Linux, macOS + +Install via pip (requires pip 19.0 or later): ``` python3 -m pip install --user pipx python3 -m pipx ensurepath ``` +Linux distros also include pipx in their package managers. For example, you may be able to install with apt: + +``` +sudo apt install pipx +``` + +### Windows + +Install via pip (requires pip 19.0 or later): + +``` +python -m pip install --user pipx +``` + +If you installed python using the app-store, replace `python` with `python3` . + +If you get the following warning + +``` +WARNING: The script pipx.exe is installed in `\AppData\Roaming\Python\Python3x\Scripts` which is not on PATH +``` + +go to the mentioned folder, allowing you to run the pipx executable directly. Enter the following line (even if you did not get the warning): + +``` +pipx ensurepath +``` + +This will add both the above mentioned path and the %USERPROFILE%.local\bin folder to your search path. Restart your terminal session and verify pipx runs. + ### Installation Options The default binary location for pipx-installed apps is `~/.local/bin`. This can be overridden with the environment variable `PIPX_BIN_DIR`. @@ -37,18 +112,32 @@ sudo PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install PACKAGE ## Upgrade pipx -On macOS: +Upgrade commands vary based on how pipx was installed on your system. + +With `get-pipx.py` (an installation of pipx managed by itself): + +``` +pipx upgrade pipx +``` + +With brew: ``` brew update && brew upgrade pipx ``` -Otherwise, upgrade via pip: +With pip: ``` python3 -m pip install --user -U pipx ``` +With apt: + +``` +sudo apt upgrade pipx +``` + ### Note: Upgrading pipx from a pre-0.15.0.0 version to 0.15.0.0 or later After upgrading to pipx 0.15.0.0 or above from a pre-0.15.0.0 version, you must re-install all packages to take advantage of the new persistent pipx metadata files introduced in the 0.15.0.0 release. These metadata files store pip specification values, injected packages, any custom pip arguments, and more in each main package's venv. diff --git a/get-pipx.py b/get-pipx.py index c91c9f1f1e..41282ec55f 100644 --- a/get-pipx.py +++ b/get-pipx.py @@ -1,20 +1,227 @@ #!/usr/bin/env python3 + +import argparse +import logging +import os +import shutil +import subprocess import sys +import tempfile +from pathlib import Path +from typing import List, Sequence, Union + +logger = logging.getLogger(__name__) +if sys.version_info.major != 3 or sys.version_info.minor < 7: + exit("Error: python3.7+ is required") + + +def bold(s: str) -> str: + return f"\033[1m{s}\033[0m" + + +class PipxInstallationError(RuntimeError): + pass + + +WINDOWS = os.name == "nt" + +DEFAULT_PIPX_HOME = Path.home() / ".local/pipx/venvs" +DEFAULT_PIPX_BIN_DIR = Path.home() / ".local/bin" + + +def run(cmd: Sequence[Union[str, Path]], check=True) -> int: + cmd_str = " ".join(str(c) for c in cmd) + logger.debug(f"running {cmd_str}") + + # windows cannot take Path objects, only strings + cmd_str_list = [str(c) for c in cmd] + returncode = subprocess.run(cmd_str_list).returncode + if check and returncode: + raise RuntimeError(f"{cmd_str!r} failed") + return returncode + + +class Venv: + def __init__(self, path: Path, *, verbose=False, python=sys.executable): + self.root = path + self._python = python + self.bin_path = path / "bin" if not WINDOWS else path / "Scripts" + self.python_path = self.bin_path / ("python" if not WINDOWS else "python.exe") + self.verbose = verbose + + def create_venv(self): + run([self._python, "-m", "venv", self.root]) + self.upgrade_package("pip") + + def install_package(self, application): + self.run_pip(["install", application]) + + def upgrade_package(self, package): + self.run_pip(["install", "--upgrade", package]) + + def run_pip(self, cmd: List[str]): + cmd = [str(self.python_path), "-m", "pip"] + cmd + if not self.verbose: + cmd.append("-q") + run(cmd) + + def run_python(self, cmd: List[str]): + cmd = [str(self.python_path)] + cmd + run(cmd) -def fail(msg): - sys.stderr.write(msg + "\n") - sys.stderr.flush() - sys.exit(1) +def parse_args(): + parser = argparse.ArgumentParser( + description="Installer script to install pipx. After installing, pipx will be available for use. " + + "pipx will be by itself, so you can run `pipx upgrade pipx` or `pipx uninstall pipx`. " + + "Environment variables PIPX_BIN_DIR and PIPX_HOME can be used." + ) + parser.add_argument( + "--spec", + default="pipx", + help=( + "The package specification of pipx to install. This value is passed " + 'to "pip install ". For example, to install from the github repository ' + "use `git+https://github.com/pypa/pipx.git`. " + "Default: %(default)s" + ), + ) + parser.add_argument( + "--no-modify-path", + action="store_true", + help="Don't configure the PATH environment variable", + ) + parser.add_argument( + "--force", + action="store_true", + help=("reinstall pipx even if existing installation was found"), + ) + parser.add_argument( + "--python", + default=sys.executable, + help=("The Python binary to associate pipx with. Must be v3.7+."), + ) + parser.add_argument( + "--verbose", action="store_true", help=("Display more detailed output") + ) + parser.add_argument( + "--yes", action="store_true", help=("Automatically answer yes to quesions") + ) + return parser.parse_args() + + +def install_pipx( + pipx_spec: str, python_bin: str, verbose: bool, no_modify_path: bool +) -> None: + with tempfile.TemporaryDirectory() as venv_dir: + venv = Venv(Path(venv_dir), python=python_bin, verbose=verbose) + venv.create_venv() + + install_pipx_cmd = ["-m", "pip", "install", pipx_spec] + if not verbose: + install_pipx_cmd.append("--quiet") + logger.debug("Installing pipx in temp venv") + venv.run_python(install_pipx_cmd) + # now run it to install itself + logger.debug("Installing pipx to system") + venv.run_python( + ["-m", "pipx", "install", pipx_spec, "--force", "--python", python_bin] + ) + if no_modify_path: + logger.debug("Not modifing user's path") + else: + logger.debug("Ensuring pipx is on user's path") + venv.run_python(["-m", "pipx", "ensurepath", "--force"]) def main(): - fail( - "This installation method has been deprecated. " - "See https://github.com/pypa/pipx for current installation " - "instructions." + args = parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG, format="%(message)s") + else: + logging.basicConfig(level=logging.INFO, format="%(message)s") + + pipx_local_venvs = Path(os.environ.get("PIPX_HOME", DEFAULT_PIPX_HOME)).resolve() + local_bin_dir = Path(os.environ.get("PIPX_BIN_DIR", DEFAULT_PIPX_BIN_DIR)).resolve() + + pipx_local_venvs.mkdir(parents=True, exist_ok=True) + local_bin_dir.mkdir(parents=True, exist_ok=True) + + pipx_venv = pipx_local_venvs / "pipx" + logger.info(bold("Welcome to the pipx installer!")) + logger.info("") + if (pipx_venv).exists(): + if args.force: + logger.warning( + f"Removing existing pipx installation at {str(pipx_venv)!r} since --force was passed" + ) + + shutil.rmtree(pipx_venv) + else: + logger.error( + "You already have pipx installed. To reinstall, pass the `--force` flag." + ) + logger.error( + "If you don't want to reinstall, go ahead and use pipx by typing `pipx`." + ) + logger.error(f"If you want to uninstall, run {bold('pipx uninstall pipx')}") + return + + logger.info("This will download and globally install pipx for you.") + logger.info( + "It does this by installing pipx to a temporary location, then uses the" ) + logger.info("temporarily-installed pipx to permanently install pipx.") + logger.info("") + logger.info("pipx will be installed to:") + logger.info("") + logger.info(f" {local_bin_dir}") + logger.info("") + logger.info("This can be modified with the PIPX_BIN_DIR environment variable.") + logger.info("") + logger.info("Virtual environments for installed packages will be created at:") + logger.info("") + logger.info(f" {pipx_local_venvs}") + logger.info("") + logger.info("This can be modified with the PIPX_HOME environment variable.") + logger.info("") + logger.info(f"You can uninstall anytime with {bold('pipx uninstall pipx')}") + logger.info("") + logger.info("The following options have been specified:") + logger.info("") + logger.info( + f" modify PATH variable in shell: {bold('no')if args.no_modify_path else bold('yes')}" + ) + logger.info(f" python: {bold(args.python)}") + logger.info(f" pipx package specification: {bold(args.spec)}") + logger.info("") + + if not sys.stdout.isatty() and not args.yes: + raise PipxInstallationError( + "To install from a script, the `--yes` flag must be passed" + ) + if args.yes: + yes = True + else: + value = input("Continue? [y/N] ") + yes = value.lower() == "y" + + logger.info("") + + if yes: + install_pipx(args.spec, args.python, args.verbose, args.no_modify_path) + else: + logger.info("Exiting pipx installer without installing anything") if __name__ == "__main__": - main() + try: + main() + except KeyboardInterrupt: + logger.info("") + logger.info("") + logger.info("Exiting pipx installation") + exit(1) + except PipxInstallationError as e: + logger.error("An error was encountered:") + exit(e) diff --git a/get_pipx.py b/get_pipx.py new file mode 120000 index 0000000000..25ab8dfd95 --- /dev/null +++ b/get_pipx.py @@ -0,0 +1 @@ +get-pipx.py \ No newline at end of file diff --git a/testdata/tests_packages/primary_packages.txt b/testdata/tests_packages/primary_packages.txt index 858e1e0749..8d3092efd3 100644 --- a/testdata/tests_packages/primary_packages.txt +++ b/testdata/tests_packages/primary_packages.txt @@ -4,12 +4,14 @@ # Space-separated values on same row. # spec no-deps +. # install latest version of ourself to test get-pipx.py bootstrap script Cython # in 'setup_requires' of jupyter dep pywinpty on Win ansible==2.9.13 awscli==1.18.168 black==18.9.b0 black==20.8b1 cloudtoken==0.1.707 +hatchling>=0.15.0 ipython==7.16.1 isort==5.6.4 jaraco-clipboard==2.0.1 diff --git a/testdata/tests_packages/unix-python3.9.txt b/testdata/tests_packages/unix-python3.9.txt index 8a48653ea9..68e82bfebd 100644 --- a/testdata/tests_packages/unix-python3.9.txt +++ b/testdata/tests_packages/unix-python3.9.txt @@ -1,157 +1,169 @@ -Cython==0.29.23 -Flask==2.0.1 -Jinja2==3.0.1 -MarkupSafe==2.0.1 +Cython==0.29.28 +Flask==2.0.3 +Jinja2==3.0.3 +MarkupSafe==2.1.0 PyYAML==5.3.1 -PyYAML==5.4.1 -Pygments==2.9.0 -QtPy==1.9.0 +PyYAML==6.0 +Pygments==2.11.2 +QtPy==2.0.1 SecretStorage==3.3.1 -Send2Trash==1.5.0 +Send2Trash==1.8.0 Weblate==4.3.1 -Werkzeug==2.0.1 +Werkzeug==2.0.3 ansible==2.9.13 appdirs==1.4.4 argcomplete==1.12.3 -argon2_cffi==20.1.0 -astroid==2.5.6 -async_generator==1.10 -attrs==21.2.0 +argcomplete==2.0.0 +argon2_cffi==21.3.0 +argon2_cffi_bindings==21.2.0 +astroid==2.9.3 +asttokens==2.0.5 +attrs==21.4.0 autocommand==2.2.1 awscli==1.18.168 backcall==0.2.0 -beautifulsoup4==4.9.3 +beautifulsoup4==4.10.0 black==18.9b0 black==20.8b1 -bleach==3.3.0 -boto3==1.17.81 +black==22.1.0 +bleach==4.1.0 +boto3==1.21.2 botocore==1.19.8 -botocore==1.20.81 -certifi==2020.12.5 -cffi==1.14.5 -chardet==4.0.0 -click==8.0.1 +botocore==1.24.2 +certifi==2021.10.8 +cffi==1.15.0 +charset_normalizer==2.0.12 +click==8.0.3 cloudtoken==0.1.707 cloudtoken_plugin.json_exporter==0.1.764 cloudtoken_plugin.saml==0.1.764 cloudtoken_plugin.shell_exporter==0.1.764 colorama==0.4.3 colorlog==4.8.0 -cryptography==3.4.7 -decorator==5.0.9 +cryptography==36.0.1 +debugpy==1.5.1 +decorator==5.1.1 defusedxml==0.7.1 -distlib==0.3.1 +distlib==0.3.4 docutils==0.15.2 -entrypoints==0.3 -filelock==3.0.12 -idna==2.10 -importlib_metadata==4.2.0 -inflect==5.3.0 +entrypoints==0.4 +executing==0.8.2 +filelock==3.6.0 +idna==3.3 +importlib_metadata==4.11.1 +inflect==5.4.0 iniconfig==1.1.1 -ipykernel==5.5.5 +ipykernel==6.9.1 ipython==7.16.1 -ipython==7.23.1 +ipython==8.0.1 ipython_genutils==0.2.0 -ipywidgets==7.6.3 +ipywidgets==7.6.5 isort==4.3.21 +isort==5.10.1 isort==5.6.4 -isort==5.8.0 -itsdangerous==2.0.1 +itsdangerous==2.1.0 jaraco.classes==3.2.1 jaraco.clipboard==2.0.1 -jaraco.collections==3.3.0 +jaraco.collections==3.5.1 +jaraco.context==4.1.1 jaraco.financial==2.0 -jaraco.functools==3.3.0 -jaraco.itertools==6.0.1 +jaraco.functools==3.5.0 +jaraco.itertools==6.1.1 jaraco.logging==3.1.0 -jaraco.text==3.5.0 +jaraco.text==3.7.0 jaraco.ui==2.3.0 -jedi==0.18.0 -jeepney==0.6.0 +jedi==0.18.1 +jeepney==0.7.1 jmespath==0.10.0 -jsonschema==3.2.0 +jsonschema==4.4.0 jupyter==1.0.0 -jupyter_client==6.1.12 +jupyter_client==7.1.2 jupyter_console==6.4.0 -jupyter_core==4.7.1 +jupyter_core==4.9.2 jupyterlab_pygments==0.1.2 -jupyterlab_widgets==1.0.0 +jupyterlab_widgets==1.0.2 kaggle==1.5.12 -keyring==23.0.1 -keyrings.alt==4.0.2 -lazy_object_proxy==1.6.0 -lxml==4.6.3 -matplotlib_inline==0.1.2 +keyring==23.5.0 +keyrings.alt==4.1.0 +lazy_object_proxy==1.7.1 +lxml==4.8.0 +matplotlib_inline==0.1.3 mccabe==0.6.1 mistune==0.8.4 -more_itertools==8.8.0 +more_itertools==8.12.0 mypy_extensions==0.4.3 -nbclient==0.5.3 -nbconvert==6.0.7 +nbclient==0.5.11 +nbconvert==6.4.2 nbformat==5.1.3 -nest_asyncio==1.5.1 -notebook==6.4.0 +nest_asyncio==1.5.4 +notebook==6.4.8 nox==2020.8.22 -ofxparse==0.20 -packaging==20.9 -pandocfilters==1.4.3 -parso==0.8.2 +ofxparse==0.21 +packaging==21.3 +pandocfilters==1.5.0 +parso==0.8.3 path.py==12.5.0 -path==15.1.2 -pathspec==0.8.1 +path==16.3.0 +pathspec==0.9.0 pbr==5.6.0 pexpect==4.8.0 pickleshare==0.7.5 -pip==21.1.2 -pluggy==0.13.1 -prometheus_client==0.10.1 -prompt_toolkit==3.0.18 +pip==22.0.3 +pipx==1.0.0 +platformdirs==2.5.0 +pluggy==1.0.0 +prometheus_client==0.13.1 +prompt_toolkit==3.0.28 ptyprocess==0.7.0 -py==1.10.0 +pure_eval==0.2.2 +py==1.11.0 pyasn1==0.4.8 pycowsay==0.0.0.1 -pycparser==2.20 +pycparser==2.21 pygdbmi==0.10.0.0 +pylint==2.12.2 pylint==2.3.1 -pylint==2.8.2 -pyparsing==2.4.7 +pyparsing==3.0.7 pyperclip==1.8.2 -pyrsistent==0.17.3 -pytest==6.2.4 -python_dateutil==2.8.1 -python_slugify==5.0.2 -pytz==2021.1 -pyzmq==22.1.0 -qtconsole==5.1.0 -regex==2021.4.4 -requests==2.25.1 +pyrsistent==0.18.1 +pytest==7.0.1 +python_dateutil==2.8.2 +python_slugify==6.0.1 +pytz==2021.3 +pyzmq==22.3.0 +qtconsole==5.2.2 +regex==2022.1.18 +requests==2.27.1 rsa==4.5 s3transfer==0.3.7 -s3transfer==0.4.2 +s3transfer==0.5.1 schedule==1.1.0 -setuptools==57.0.0 -setuptools_scm==6.0.1 +setuptools==60.9.3 +setuptools_scm==6.4.2 shell-functools==0.3.0 six==1.16.0 -soupsieve==2.2.1 -tempora==4.0.2 -terminado==0.10.0 +soupsieve==2.3.1 +stack_data==0.2.0 +tempora==5.0.1 +terminado==0.13.1 testpath==0.5.0 text_unidecode==1.3 toml==0.10.2 +tomli==2.0.1 tornado==6.1 -tox==3.23.1 +tox==3.24.5 tox_ini_fmt==0.5.0 -tqdm==4.61.0 -traitlets==5.0.5 -typed_ast==1.4.3 -typing_extensions==3.10.0.0 +tqdm==4.62.3 +traitlets==5.1.1 +typed_ast==1.5.2 +typing_extensions==4.1.1 urllib3==1.25.11 -urllib3==1.26.5 -virtualenv==20.4.7 +urllib3==1.26.8 +userpath==1.8.0 +virtualenv==20.13.1 wcwidth==0.2.5 webencodings==0.5.1 -wheel==0.36.2 -widgetsnbextension==3.5.1 -wrapt==1.12.1 -zipp==3.4.1 +wheel==0.37.1 +widgetsnbextension==3.5.2 +wrapt==1.13.3 +zipp==3.7.0 diff --git a/tests/test_get_pipx.py b/tests/test_get_pipx.py new file mode 100644 index 0000000000..7938906dcb --- /dev/null +++ b/tests/test_get_pipx.py @@ -0,0 +1,42 @@ +import os +import sys +from unittest import mock + +import pytest # type: ignore + +from helpers import run_pipx_cli +from pipx import constants + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from get_pipx import PipxInstallationError # noqa: E402 +from get_pipx import main as get_pipx_main # noqa: E402 + + +def test_get_pipx(pipx_temp_env, capfd, monkeypatch, caplog): + assert not run_pipx_cli(["list"]) + cap = capfd.readouterr() + assert "nothing has been installed with pipx" in cap.err + assert "These apps are now globally available" not in cap.out + + monkeypatch.setenv("PIPX_HOME", str(constants.PIPX_HOME)) + monkeypatch.setenv("BIN_DIR", str(constants.LOCAL_BIN_DIR)) + with mock.patch.object( + sys, + "argv", + ["get_pipx"] + ["--verbose", "--no-modify-path", "--yes", "--spec=."], + ): + get_pipx_main() + cap = capfd.readouterr() + assert "These apps are now globally available" in cap.out + assert "- pipx" in cap.out + + +def test_get_pipx_requires_yes_from_script(pipx_temp_env, capfd, monkeypatch, caplog): + assert not run_pipx_cli(["list"]) + monkeypatch.setenv("PIPX_HOME", str(constants.PIPX_HOME)) + monkeypatch.setenv("BIN_DIR", str(constants.LOCAL_BIN_DIR)) + with mock.patch.object( + sys, "argv", ["get_pipx"] + ["--verbose", "--no-modify-path", "--spec=."] + ), pytest.raises(PipxInstallationError) as err: + get_pipx_main() + assert "To install from a script" in err