Skip to content

Commit

Permalink
Allow users to pass a string of extra arguments to pip (#5283)
Browse files Browse the repository at this point in the history
* Allow users to pass a string of extra arguments to pip install
  • Loading branch information
matteius authored Sep 4, 2022
1 parent d344648 commit 7b9b1ae
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 2 deletions.
16 changes: 15 additions & 1 deletion docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 pip feature for using
`system certificate stores <https://pip.pypa.io/en/latest/topics/https-certificates/#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``.
Example usage::

pipenv sync --extra-pip-args="--use-feature=truststore --proxy=127.0.0.1"


☤ Specifying Basically Anything
-------------------------------

Expand Down
2 changes: 2 additions & 0 deletions news/5283.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
It is now possible to supply additional arguments to ``pip`` install by supplying ``--extra-pip-args="<arg1> <arg2>"``
See the updated documentation ``Supplying additional arguments to pip`` for more details.
3 changes: 3 additions & 0 deletions pipenv/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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()
Expand Down
20 changes: 20 additions & 0 deletions pipenv/cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def __init__(self):
self.deploy = False
self.packages = []
self.editables = []
self.extra_pip_args = []


class LockOptions:
Expand Down Expand Up @@ -286,6 +287,24 @@ def callback(ctx, param, value):
)(f)


def extra_pip_args(f):
def callback(ctx, param, value):
state = ctx.ensure_object(State)
if value:
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)
Expand Down Expand Up @@ -570,6 +589,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


Expand Down
20 changes: 20 additions & 0 deletions pipenv/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,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,
Expand Down Expand Up @@ -774,6 +775,7 @@ def batch_install(
trusted_hosts=trusted_hosts,
use_pep517=use_pep517,
use_constraint=False, # no need to use constraints, it's written in lockfile
extra_pip_args=extra_pip_args,
)

for c in cmds:
Expand All @@ -792,6 +794,7 @@ def do_install_dependencies(
concurrent=True,
requirements_dir=None,
pypi_mirror=None,
extra_pip_args=None,
):
"""
Executes the installation functionality.
Expand Down Expand Up @@ -841,6 +844,7 @@ def do_install_dependencies(
"allow_global": allow_global,
"pypi_mirror": pypi_mirror,
"sequential_deps": editable_or_vcs_deps,
"extra_pip_args": extra_pip_args,
}

batch_install(
Expand Down Expand Up @@ -1228,6 +1232,7 @@ def do_init(
keep_outdated=False,
requirements_dir=None,
pypi_mirror=None,
extra_pip_args=None,
):
"""Executes the init functionality."""

Expand Down Expand Up @@ -1327,6 +1332,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.
Expand All @@ -1352,6 +1358,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"],
Expand All @@ -1373,6 +1380,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))


Expand Down Expand Up @@ -1445,6 +1454,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:
Expand Down Expand Up @@ -1521,6 +1531,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:
Expand Down Expand Up @@ -1574,6 +1585,7 @@ def pip_install_deps(
trusted_hosts=None,
use_pep517=True,
use_constraint=False,
extra_pip_args: Optional[List] = None,
):
if not trusted_hosts:
trusted_hosts = []
Expand Down Expand Up @@ -1662,6 +1674,7 @@ def pip_install_deps(
selective_upgrade=selective_upgrade,
no_use_pep517=not use_pep517,
no_deps=no_deps,
extra_pip_args=extra_pip_args,
)
sources = get_source_list(
project,
Expand Down Expand Up @@ -2058,6 +2071,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-"
Expand Down Expand Up @@ -2213,6 +2227,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
Expand All @@ -2233,6 +2248,7 @@ def do_install(
deploy=deploy,
pypi_mirror=pypi_mirror,
skip_lock=skip_lock,
extra_pip_args=extra_pip_args,
)
for pkg_line in pkg_list:
click.secho(
Expand Down Expand Up @@ -2279,6 +2295,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(
Expand Down Expand Up @@ -2381,6 +2398,7 @@ def do_install(
deploy=deploy,
pypi_mirror=pypi_mirror,
skip_lock=skip_lock,
extra_pip_args=extra_pip_args,
)
sys.exit(0)

Expand Down Expand Up @@ -3056,6 +3074,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:
Expand Down Expand Up @@ -3090,6 +3109,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"))
Expand Down
15 changes: 14 additions & 1 deletion tests/integration/test_install_basic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import mock

from pathlib import Path
from tempfile import TemporaryDirectory
Expand Down Expand Up @@ -534,7 +535,6 @@ def test_install_dev_use_default_constraints(PipenvInstance):
assert c.returncode != 0


@pytest.mark.dev
@pytest.mark.basic
@pytest.mark.install
@pytest.mark.needs_internet
Expand All @@ -547,6 +547,19 @@ def test_install_does_not_exclude_packaging(PipenvInstance):
assert c.returncode == 0


@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 dataclasses-json --extra-pip-args=""--use-feature=truststore --proxy=test""")
assert c.returncode == 1
assert "truststore feature" in c.stderr


@pytest.mark.basic
@pytest.mark.install
@pytest.mark.needs_internet
def test_install_tarball_is_actually_installed(PipenvInstance):
""" Test case for Issue 5326"""
with PipenvInstance(chdir=True) as p:
Expand Down

0 comments on commit 7b9b1ae

Please sign in to comment.