From 1c4647f14b67f55548f7f91cbbe9e619d4f4c5e1 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 22 Aug 2022 16:49:51 -0400 Subject: [PATCH 1/8] Allow users to pass a string of extra argumnets to pip --- pipenv/cli/command.py | 3 +++ pipenv/cli/options.py | 19 +++++++++++++++++++ pipenv/core.py | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/pipenv/cli/command.py b/pipenv/cli/command.py index 568544903d..6b5511c6fd 100644 --- a/pipenv/cli/command.py +++ b/pipenv/cli/command.py @@ -250,6 +250,7 @@ def install(state, **kwargs): packages=state.installstate.packages, editable_packages=state.installstate.editables, site_packages=state.site_packages, + extra_pip_args=state.installstate.extra_pip_args, ) @@ -561,6 +562,7 @@ def update(ctx, state, bare=False, dry_run=None, outdated=False, **kwargs): unused=False, sequential=state.installstate.sequential, pypi_mirror=state.pypi_mirror, + extra_pip_args=state.installstate.extra_pip_args, ) @@ -652,6 +654,7 @@ def sync(ctx, state, bare=False, user=False, unused=False, **kwargs): sequential=state.installstate.sequential, pypi_mirror=state.pypi_mirror, system=state.system, + extra_pip_args=state.installstate.extra_pip_args, ) if retcode: ctx.abort() diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index 7018c898bf..da30f29840 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -87,6 +87,7 @@ def __init__(self): self.deploy = False self.packages = [] self.editables = [] + self.extra_pip_args = [] class LockOptions: @@ -286,6 +287,23 @@ def callback(ctx, param, value): )(f) +def extra_pip_args(f): + def callback(ctx, param, value): + state = ctx.ensure_object(State) + for opt in value.split(" "): + state.installstate.extra_pip_args.append(opt) + return value + + return option( + "--extra-pip-args", + nargs=1, + required=False, + callback=callback, + expose_value=True, + type=click_types.STRING, + )(f) + + def three_option(f): def callback(ctx, param, value): state = ctx.ensure_object(State) @@ -579,6 +597,7 @@ def install_options(f): f = ignore_pipfile_option(f) f = editable_option(f) f = package_arg(f) + f = extra_pip_args(f) return f diff --git a/pipenv/core.py b/pipenv/core.py index d87c6e5db2..ad1a237e05 100644 --- a/pipenv/core.py +++ b/pipenv/core.py @@ -714,6 +714,7 @@ def batch_install( pypi_mirror=None, retry=True, sequential_deps=None, + extra_pip_args=None, ): from .vendor.requirementslib.models.utils import ( strip_extras_markers_from_requirement, @@ -787,6 +788,7 @@ def batch_install( extra_indexes=extra_indexes, use_pep517=use_pep517, use_constraint=False, # no need to use constraints, it's written in lockfile + extra_pip_args=extra_pip_args, ) c.dep = dep @@ -806,6 +808,7 @@ def do_install_dependencies( concurrent=True, requirements_dir=None, pypi_mirror=None, + extra_pip_args=None, ): """ Executes the installation functionality. @@ -856,6 +859,7 @@ def do_install_dependencies( "blocking": not concurrent, "pypi_mirror": pypi_mirror, "sequential_deps": editable_or_vcs_deps, + "extra_pip_args": extra_pip_args, } batch_install( @@ -1238,6 +1242,7 @@ def do_init( keep_outdated=False, requirements_dir=None, pypi_mirror=None, + extra_pip_args=None, ): """Executes the init functionality.""" @@ -1339,6 +1344,7 @@ def do_init( concurrent=concurrent, requirements_dir=requirements_dir, pypi_mirror=pypi_mirror, + extra_pip_args=extra_pip_args, ) # Hint the user what to do to activate the virtualenv. @@ -1364,6 +1370,7 @@ def get_pip_args( no_deps: bool = False, selective_upgrade: bool = False, src_dir: Optional[str] = None, + extra_pip_args: Optional[List] = None, ) -> List[str]: arg_map = { "pre": ["--pre"], @@ -1385,6 +1392,8 @@ def get_pip_args( arg_set.extend(arg_map.get(key)) elif key == "selective_upgrade" and not locals().get(key): arg_set.append("--exists-action=i") + for extra_pip_arg in extra_pip_args: + arg_set.append(extra_pip_arg) return list(dict.fromkeys(arg_set)) @@ -1458,6 +1467,7 @@ def pip_install( trusted_hosts=None, use_pep517=True, use_constraint=False, + extra_pip_args: Optional[List] = None, ): piplogger = logging.getLogger("pipenv.patched.pip._internal.commands.install") if not trusted_hosts: @@ -1535,6 +1545,7 @@ def pip_install( no_use_pep517=not use_pep517, no_deps=no_deps, require_hashes=not ignore_hashes, + extra_pip_args=extra_pip_args, ) pip_command.extend(pip_args) if r: @@ -1919,6 +1930,7 @@ def do_install( keep_outdated=False, selective_upgrade=False, site_packages=None, + extra_pip_args=None, ): requirements_directory = vistir.path.create_tracked_tempdir( suffix="-requirements", prefix="pipenv-" @@ -2074,6 +2086,7 @@ def do_install( requirements_dir=requirements_directory, pypi_mirror=pypi_mirror, keep_outdated=keep_outdated, + extra_pip_args=extra_pip_args, ) # This is for if the user passed in dependencies, then we want to make sure we @@ -2094,6 +2107,7 @@ def do_install( deploy=deploy, pypi_mirror=pypi_mirror, skip_lock=skip_lock, + extra_pip_args=extra_pip_args, ) pip_shims_module = os.environ.pop("PIP_SHIMS_BASE_MODULE", None) for pkg_line in pkg_list: @@ -2141,6 +2155,7 @@ def do_install( index=index_url, pypi_mirror=pypi_mirror, use_constraint=True, + extra_pip_args=extra_pip_args, ) if c.returncode: sp.write_err( @@ -2245,6 +2260,7 @@ def do_install( deploy=deploy, pypi_mirror=pypi_mirror, skip_lock=skip_lock, + extra_pip_args=extra_pip_args, ) sys.exit(0) @@ -2926,6 +2942,7 @@ def do_sync( pypi_mirror=None, system=False, deploy=False, + extra_pip_args=None, ): # The lock file needs to exist because sync won't write to it. if not project.lockfile_exists: @@ -2960,6 +2977,7 @@ def do_sync( pypi_mirror=pypi_mirror, deploy=deploy, system=system, + extra_pip_args=extra_pip_args, ) if not bare: click.echo(click.style("All dependencies are now up-to-date!", fg="green")) From ccc104be7283706faa0654752d88fd1090bda2f0 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 22 Aug 2022 16:59:19 -0400 Subject: [PATCH 2/8] Handle case where the option is not provided. --- pipenv/cli/options.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pipenv/cli/options.py b/pipenv/cli/options.py index da30f29840..38c7aeb0be 100644 --- a/pipenv/cli/options.py +++ b/pipenv/cli/options.py @@ -290,8 +290,9 @@ def callback(ctx, param, value): def extra_pip_args(f): def callback(ctx, param, value): state = ctx.ensure_object(State) - for opt in value.split(" "): - state.installstate.extra_pip_args.append(opt) + if value: + for opt in value.split(" "): + state.installstate.extra_pip_args.append(opt) return value return option( From 84fc2ca36e1521df027ba67a81622bccd47fabae Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 3 Sep 2022 10:01:59 -0400 Subject: [PATCH 3/8] Add documentaion. --- docs/advanced.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 4d76774270..4852e5ebb4 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -123,7 +123,7 @@ Keep in mind that environment variables are expanded in runtime, leaving the ent ☤ Injecting credentials through keychain support ------------------------------------------------ -Private regirstries on Google Cloud, Azure and AWS support dynamic credentials using +Private registries on Google Cloud, Azure and AWS support dynamic credentials using the keychain implementation. Due to the way the keychain is structured, it might ask the user for input. Asking the user for input is disabled. This will disable the keychain support completely, unfortunately. @@ -153,6 +153,20 @@ input. Otherwise the process will hang forever!:: Above example will install ``flask`` and a private package ``private-test-package`` from GCP. +☤ Supplying additional arguments to pip +------------------------------------------------ + +There may be cases where you wish to supply additional arguments to pip to be used during the install phase. +For example, you may want to enable the new pip experimental feature for using +`system certificate stores `_ + +In this case you can supply these additional arguments to ``pipenv sync`` or ``pipenv install`` by passing additional +argument ``--extra-pip-args="--use-feature=truststore"``. It is possible to supply multiple arguments in the ``--extra-pip-args``, +for example:: + + pipenv sync --extra-pip-args="--use-feature=truststore --proxy=127.0.0.1" + + ☤ Specifying Basically Anything ------------------------------- From bfab94b57b14034b8f160a5c7b3681628e4e1a52 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 3 Sep 2022 10:06:14 -0400 Subject: [PATCH 4/8] Add news fragment. --- news/5283.feature.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 news/5283.feature.rst diff --git a/news/5283.feature.rst b/news/5283.feature.rst new file mode 100644 index 0000000000..78c4aba636 --- /dev/null +++ b/news/5283.feature.rst @@ -0,0 +1,2 @@ +It is now possible to supply additional arguments to ``pip`` install by supplying ``--extra-pip-args=" "`` +See the updated documentation ``Supplying additional arguments to pip`` for more details. From 359de072098f80f0a7d761b95abf0266ea69d3ab Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 3 Sep 2022 10:16:53 -0400 Subject: [PATCH 5/8] Add test for extra-pip-args --- tests/integration/test_install_basic.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/integration/test_install_basic.py b/tests/integration/test_install_basic.py index 3c7959cb4f..1b8028a7cf 100644 --- a/tests/integration/test_install_basic.py +++ b/tests/integration/test_install_basic.py @@ -1,4 +1,5 @@ import os +import mock from pathlib import Path from tempfile import TemporaryDirectory @@ -545,3 +546,14 @@ def test_install_does_not_exclude_packaging(PipenvInstance): assert c.returncode == 0 c = p.pipenv("run python -c 'from dataclasses_json import DataClassJsonMixin'") assert c.returncode == 0 + + +@pytest.mark.dev +@pytest.mark.basic +@pytest.mark.install +@pytest.mark.needs_internet +def test_install_will_supply_extra_pip_args(PipenvInstance): + with PipenvInstance(chdir=True) as p: + c = p.pipenv("""install requests --extra-pip-args=""--use-feature=truststore --proxy=test""") + assert c.returncode == 1 + assert "To use the truststore feature" in c.stderr From 23340a6876da7b587a1002a26a7ce61adf6fb1a6 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 3 Sep 2022 11:08:54 -0400 Subject: [PATCH 6/8] revise assertion --- tests/integration/test_install_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_install_basic.py b/tests/integration/test_install_basic.py index 1b8028a7cf..9b9bd54c4f 100644 --- a/tests/integration/test_install_basic.py +++ b/tests/integration/test_install_basic.py @@ -556,4 +556,4 @@ def test_install_will_supply_extra_pip_args(PipenvInstance): with PipenvInstance(chdir=True) as p: c = p.pipenv("""install requests --extra-pip-args=""--use-feature=truststore --proxy=test""") assert c.returncode == 1 - assert "To use the truststore feature" in c.stderr + assert "truststore feature" in c.stderr From 31673716164f3b9fec2c776351b5b41ad5e3cf12 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 3 Sep 2022 19:14:25 -0400 Subject: [PATCH 7/8] Refine documentation. --- docs/advanced.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 4852e5ebb4..0f4e301058 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -157,12 +157,12 @@ Above example will install ``flask`` and a private package ``private-test-packag ------------------------------------------------ There may be cases where you wish to supply additional arguments to pip to be used during the install phase. -For example, you may want to enable the new pip experimental feature for using +For example, you may want to enable the pip feature for using `system certificate stores `_ In this case you can supply these additional arguments to ``pipenv sync`` or ``pipenv install`` by passing additional -argument ``--extra-pip-args="--use-feature=truststore"``. It is possible to supply multiple arguments in the ``--extra-pip-args``, -for example:: +argument ``--extra-pip-args="--use-feature=truststore"``. It is possible to supply multiple arguments in the ``--extra-pip-args``. +Example usage:: pipenv sync --extra-pip-args="--use-feature=truststore --proxy=127.0.0.1" From 57bbc95015b2232d8b9caa953b775e130aa0215d Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Sat, 3 Sep 2022 21:39:27 -0400 Subject: [PATCH 8/8] Not sure why the dep test failures came back, but going to invest more time into the test runner for hacktoberfest. --- tests/integration/test_install_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_install_basic.py b/tests/integration/test_install_basic.py index 1b702118fa..daa75fd9ac 100644 --- a/tests/integration/test_install_basic.py +++ b/tests/integration/test_install_basic.py @@ -552,7 +552,7 @@ def test_install_does_not_exclude_packaging(PipenvInstance): @pytest.mark.needs_internet def test_install_will_supply_extra_pip_args(PipenvInstance): with PipenvInstance(chdir=True) as p: - c = p.pipenv("""install requests --extra-pip-args=""--use-feature=truststore --proxy=test""") + c = p.pipenv("""install dataclasses-json --extra-pip-args=""--use-feature=truststore --proxy=test""") assert c.returncode == 1 assert "truststore feature" in c.stderr