Skip to content

Commit

Permalink
Make PIP_NO_CACHE_DIR disable the cache also if given a "true" value.
Browse files Browse the repository at this point in the history
  • Loading branch information
cjerdonek committed Nov 8, 2018
1 parent 1228f64 commit b94d719
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
1 change: 1 addition & 0 deletions news/5385.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Setting ``PIP_NO_CACHE_DIR=yes`` no longer causes pip to crash.
2 changes: 2 additions & 0 deletions news/5735.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Make ``PIP_NO_CACHE_DIR`` disable the cache also for truthy values like
``"true"``, ``"yes"``, ``"1"``, etc.
29 changes: 28 additions & 1 deletion src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from __future__ import absolute_import

import warnings
from distutils.util import strtobool
from functools import partial
from optparse import SUPPRESS_HELP, Option, OptionGroup

Expand Down Expand Up @@ -520,11 +521,37 @@ def prefer_binary():
help="Store the cache data in <dir>."
)


def no_cache_dir_callback(option, opt, value, parser):
"""
Process a value provided for the --no-cache-dir option.
This is an optparse.Option callback for the --no-cache-dir option.
"""
# The value argument will be None if --no-cache-dir is passed via the
# command-line, since the option doesn't accept arguments. However,
# the value can be non-None if the option is triggered e.g. by an
# environment variable, like PIP_NO_CACHE_DIR=true.
if value is not None:
# Then parse the string value to get argument error-checking.
strtobool(value)

# Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
# converted to 0 (like "false" or "no") caused cache_dir to be disabled
# rather than enabled (logic would say the latter). Thus, we disable
# the cache directory not just on values that parse to True, but (for
# backwards compatibility reasons) also on values that parse to False.
# In other words, always set it to False if the option is provided in
# some (valid) form.
parser.values.cache_dir = False


no_cache = partial(
Option,
"--no-cache-dir",
dest="cache_dir",
action="store_false",
action="callback",
callback=no_cache_dir_callback,
help="Disable the cache.",
)

Expand Down
88 changes: 88 additions & 0 deletions tests/unit/test_options.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from contextlib import contextmanager

import pytest

Expand All @@ -7,6 +8,23 @@
from tests.lib.options_helpers import AddFakeCommandMixin


@contextmanager
def assert_raises_message(exc_class, expected):
"""
Assert that an exception with the given type and message is raised.
"""
with pytest.raises(exc_class) as excinfo:
yield

assert str(excinfo.value) == expected


def assert_is_default_cache_dir(value):
# This path looks different on different platforms, but the path always
# has the substring "pip".
assert 'pip' in value


class TestOptionPrecedence(AddFakeCommandMixin):
"""
Tests for confirming our option precedence:
Expand Down Expand Up @@ -81,6 +99,63 @@ def test_cli_override_environment(self):
options, args = main(['fake', '--timeout', '-2'])
assert options.timeout == -2

@pytest.mark.parametrize('pip_no_cache_dir', [
# Enabling --no-cache-dir means no cache directory.
'1',
'true',
'on',
'yes',
# For historical / backwards compatibility reasons, we also disable
# the cache directory if provided a value that translates to 0.
'0',
'false',
'off',
'no',
])
def test_cache_dir__PIP_NO_CACHE_DIR(self, pip_no_cache_dir):
"""
Test setting the PIP_NO_CACHE_DIR environment variable without
passing any command-line flags.
"""
os.environ['PIP_NO_CACHE_DIR'] = pip_no_cache_dir
options, args = main(['fake'])
assert options.cache_dir is False

@pytest.mark.parametrize('pip_no_cache_dir', ['yes', 'no'])
def test_cache_dir__PIP_NO_CACHE_DIR__with_cache_dir(
self, pip_no_cache_dir
):
"""
Test setting PIP_NO_CACHE_DIR while also passing an explicit
--cache-dir value.
"""
os.environ['PIP_NO_CACHE_DIR'] = pip_no_cache_dir
options, args = main(['--cache-dir', '/cache/dir', 'fake'])
# The command-line flag takes precedence.
assert options.cache_dir == '/cache/dir'

@pytest.mark.parametrize('pip_no_cache_dir', ['yes', 'no'])
def test_cache_dir__PIP_NO_CACHE_DIR__with_no_cache_dir(
self, pip_no_cache_dir
):
"""
Test setting PIP_NO_CACHE_DIR while also passing --no-cache-dir.
"""
os.environ['PIP_NO_CACHE_DIR'] = pip_no_cache_dir
options, args = main(['--no-cache-dir', 'fake'])
# The command-line flag should take precedence (which has the same
# value in this case).
assert options.cache_dir is False

def test_cache_dir__PIP_NO_CACHE_DIR_invalid__with_no_cache_dir(self):
"""
Test setting PIP_NO_CACHE_DIR to an invalid value while also passing
--no-cache-dir.
"""
os.environ['PIP_NO_CACHE_DIR'] = 'maybe'
with assert_raises_message(ValueError, "invalid truth value 'maybe'"):
main(['--no-cache-dir', 'fake'])


class TestOptionsInterspersed(AddFakeCommandMixin):

Expand All @@ -106,6 +181,19 @@ class TestGeneralOptions(AddFakeCommandMixin):
# the reason to specifically test general options is due to the
# extra processing they receive, and the number of bugs we've had

def test_cache_dir__default(self):
options, args = main(['fake'])
# With no options the default cache dir should be used.
assert_is_default_cache_dir(options.cache_dir)

def test_cache_dir__provided(self):
options, args = main(['--cache-dir', '/cache/dir', 'fake'])
assert options.cache_dir == '/cache/dir'

def test_no_cache_dir__provided(self):
options, args = main(['--no-cache-dir', 'fake'])
assert options.cache_dir is False

def test_require_virtualenv(self):
options1, args1 = main(['--require-virtualenv', 'fake'])
options2, args2 = main(['fake', '--require-virtualenv'])
Expand Down

0 comments on commit b94d719

Please sign in to comment.