Repository: pypa/pip
Pull request: 3905 pip version check cache dir
Fixes #3905 
Changed rules on where pip version lock file selfcheck.json is stored.

Previously, the lock file was either stored at ``USER_CACHE``/selfcheck.json,
or for vitual enviorments it is stored at ``sys.prefix``\pip-selfcheck.json

Now regardless of whether the user is in a virtual environment, the lock file
is stored either in selfcheck.json in the path specified in cache-dir in
pip.conf, or if none is specified it is stored at``USER_CACHE``/selfcheck.json

Problem statement:
pip version check file doesn't honor cache-dir
- Pip version: 7.1.0
- Python version: 2.7.6
- Operating System: Fedora 22

My pip config looks like:

```
[dev@machine ~] $ cat ~/.config/pip/pip.conf 
[global]
cache-dir=/ssd/dev/.pip/cache
```

Whenever I try to install any package, it caches the wheels in the `cache-dir` as specified above. However, the pip-version check is performed and the file `selfcheck.json` and it's lock file is placed in the directory: `$HOME/.cache/pip/` , which should have been inside the `cache-dir` as specified above.

I get around this problem by specifying the following in the pip config (but it's not a very good solution):

```
disable-pip-version-check=true
```

Hints from issue discussion:
I'm at PyCon sprints, researching this before making changes.


I was able to recreate this issue using the latest pip (10.0.1)

Looking in the code, I found that in `pip._internal.utils.outdated` there are two classes that write out the _selfcheck.json_ file.

For users not in virtual environmnets
	`pip._internal.utils.outdated.GlobalSelfCheckState` puts _selfcheck.json_ in the directory specified in USER_CACHE_DIR
For users in virtual environments
	`pip._internal.utils.outdated.VirtualenvSelfCheckState` puts _pip-selfcheck.json_ in the directory specified in sys.prefix

I am not certain if we would want to change this for both scenarios or just for users not in virtual environments.

From looking at the code, it looks like the change would be as follows:

`pip._internal.utils.outdated.pip_version_check` has access to to the configured cache directory in `options.cache_dir`
 To fix the problem, this value could be passed through as an argument into  `pip._internal.utils.outdated.load_selfcheck_state` and from there into `pip._internal.utils.outdated.GlobalSelfCheckState.__init__`
 And possibly into `pip._internal.utils.outdated.VirtualenvSelfCheckState.__init_`


Hey @tomfbiz! Are you on IRC?
Quick look suggests we can kill the virtual env file completely, and just use the per user cache file for all environments, and then plumb the cache-dir file through appropriately. 

Does that answer your question?
Noting here that we (mostly @dstufft, @tomfbiz :P) had a discussion on IRC and we're likely simply removing the virtualenv specific file.

Commit message: fix lint errors

Patch to review:
diff --git a/news/3905.bugfix b/news/3905.bugfix
new file mode 100644
index 00000000000..0c0c822192a
--- /dev/null
+++ b/news/3905.bugfix
@@ -0,0 +1,8 @@
+Changed rules on where pip version lock file selfcheck.json is stored.
+
+Previously, the lock file was either stored at ``USER_CACHE``/selfcheck.json,
+or for vitual enviorments it is stored at ``sys.prefix``\pip-selfcheck.json
+
+Now regardless of whether the user is in a virtual environment, the lock file
+is stored either in selfcheck.json in the path specified in cache-dir in
+pip.conf, or if none is specified it is stored at``USER_CACHE``/selfcheck.json 
diff --git a/src/pip/_internal/utils/outdated.py b/src/pip/_internal/utils/outdated.py
index f8f64669d51..1a1f70eec90 100644
--- a/src/pip/_internal/utils/outdated.py
+++ b/src/pip/_internal/utils/outdated.py
@@ -21,34 +21,9 @@
 logger = logging.getLogger(__name__)
 
 
-class VirtualenvSelfCheckState(object):
-    def __init__(self):
-        self.statefile_path = os.path.join(sys.prefix, "pip-selfcheck.json")
-
-        # Load the existing state
-        try:
-            with open(self.statefile_path) as statefile:
-                self.state = json.load(statefile)
-        except (IOError, ValueError):
-            self.state = {}
-
-    def save(self, pypi_version, current_time):
-        # Attempt to write out our version check file
-        with open(self.statefile_path, "w") as statefile:
-            json.dump(
-                {
-                    "last_check": current_time.strftime(SELFCHECK_DATE_FMT),
-                    "pypi_version": pypi_version,
-                },
-                statefile,
-                sort_keys=True,
-                separators=(",", ":")
-            )
-
-
 class GlobalSelfCheckState(object):
-    def __init__(self):
-        self.statefile_path = os.path.join(USER_CACHE_DIR, "selfcheck.json")
+    def __init__(self, cache_dir):
+        self.statefile_path = os.path.join(cache_dir, "selfcheck.json")
 
         # Load the existing state
         try:
@@ -84,13 +59,6 @@ def save(self, pypi_version, current_time):
                           separators=(",", ":"))
 
 
-def load_selfcheck_statefile():
-    if running_under_virtualenv():
-        return VirtualenvSelfCheckState()
-    else:
-        return GlobalSelfCheckState()
-
-
 def pip_version_check(session, options):
     """Check for an update for pip.
 
@@ -106,7 +74,7 @@ def pip_version_check(session, options):
     pypi_version = None
 
     try:
-        state = load_selfcheck_statefile()
+        state = GlobalSelfCheckState(cache_dir=options.cache_dir)
 
         current_time = datetime.datetime.utcnow()
         # Determine if we need to refresh the state
diff --git a/tests/unit/test_unit_outdated.py b/tests/unit/test_unit_outdated.py
index 531ff02d27d..cd765ef11c6 100644
--- a/tests/unit/test_unit_outdated.py
+++ b/tests/unit/test_unit_outdated.py
@@ -3,6 +3,7 @@
 import sys
 from contextlib import contextmanager
 
+
 import freezegun
 import pretend
 import pytest
@@ -37,6 +38,7 @@ def _options():
     return pretend.stub(
         find_links=False, extra_index_urls=[], index_url='default_url',
         pre=False, trusted_hosts=False, process_dependency_links=False,
+        cache_dir='',
     )
 
 
@@ -72,16 +74,16 @@ def test_pip_version_check(monkeypatch, stored_time, installed_ver, new_ver,
         save=pretend.call_recorder(lambda v, t: None),
     )
     monkeypatch.setattr(
-        outdated, 'load_selfcheck_statefile', lambda: fake_state
+        outdated, 'GlobalSelfCheckState', lambda **kw: fake_state
     )
 
     with freezegun.freeze_time(
-            "1970-01-09 10:00:00",
-            ignore=[
-                "six.moves",
-                "pip._vendor.six.moves",
-                "pip._vendor.requests.packages.urllib3.packages.six.moves",
-            ]):
+        "1970-01-09 10:00:00",
+        ignore=[
+            "six.moves",
+            "pip._vendor.six.moves",
+            "pip._vendor.requests.packages.urllib3.packages.six.moves",
+        ]):
         latest_pypi_version = outdated.pip_version_check(None, _options())
 
     # See we return None if not installed_version
@@ -105,40 +107,6 @@ def test_pip_version_check(monkeypatch, stored_time, installed_ver, new_ver,
         assert len(outdated.logger.warning.calls) == 0
 
 
-def test_virtualenv_state(monkeypatch):
-    CONTENT = '{"last_check": "1970-01-02T11:00:00Z", "pypi_version": "1.0"}'
-    fake_file = pretend.stub(
-        read=pretend.call_recorder(lambda: CONTENT),
-        write=pretend.call_recorder(lambda s: None),
-    )
-
-    @pretend.call_recorder
-    @contextmanager
-    def fake_open(filename, mode='r'):
-        yield fake_file
-
-    monkeypatch.setattr(outdated, 'open', fake_open, raising=False)
-
-    monkeypatch.setattr(outdated, 'running_under_virtualenv',
-                        pretend.call_recorder(lambda: True))
-
-    monkeypatch.setattr(sys, 'prefix', 'virtually_env')
-
-    state = outdated.load_selfcheck_statefile()
-    state.save('2.0', datetime.datetime.utcnow())
-
-    assert len(outdated.running_under_virtualenv.calls) == 1
-
-    expected_path = os.path.join('virtually_env', 'pip-selfcheck.json')
-    assert fake_open.calls == [
-        pretend.call(expected_path),
-        pretend.call(expected_path, 'w'),
-    ]
-
-    # json.dumps will call this a number of times
-    assert len(fake_file.write.calls)
-
-
 def test_global_state(monkeypatch, tmpdir):
     CONTENT = '''{"pip_prefix": {"last_check": "1970-01-02T11:00:00Z",
         "pypi_version": "1.0"}}'''
@@ -164,18 +132,14 @@ def fake_lock(filename):
     monkeypatch.setattr(lockfile, 'LockFile', fake_lock)
     monkeypatch.setattr(os.path, "exists", lambda p: True)
 
-    monkeypatch.setattr(outdated, 'running_under_virtualenv',
-                        pretend.call_recorder(lambda: False))
 
     cache_dir = tmpdir / 'cache_dir'
     monkeypatch.setattr(outdated, 'USER_CACHE_DIR', cache_dir)
     monkeypatch.setattr(sys, 'prefix', tmpdir / 'pip_prefix')
 
-    state = outdated.load_selfcheck_statefile()
+    state = outdated.GlobalSelfCheckState(cache_dir=cache_dir)
     state.save('2.0', datetime.datetime.utcnow())
 
-    assert len(outdated.running_under_virtualenv.calls) == 1
-
     expected_path = cache_dir / 'selfcheck.json'
     assert fake_lock.calls == [pretend.call(expected_path)]
 


Code review comments:
diff_hunk: @@ -21,34 +21,9 @@
 logger = logging.getLogger(__name__)
 
 
-class VirtualenvSelfCheckState(object):
-    def __init__(self):
-        self.statefile_path = os.path.join(sys.prefix, "pip-selfcheck.json")
-
-        # Load the existing state
-        try:
-            with open(self.statefile_path) as statefile:
-                self.state = json.load(statefile)
-        except (IOError, ValueError):
-            self.state = {}
-
-    def save(self, pypi_version, current_time):
-        # Attempt to write out our version check file
-        with open(self.statefile_path, "w") as statefile:
-            json.dump(
-                {
-                    "last_check": current_time.strftime(SELFCHECK_DATE_FMT),
-                    "pypi_version": pypi_version,
-                },
-                statefile,
-                sort_keys=True,
-                separators=(",", ":")
-            )
-
-
 class GlobalSelfCheckState(object):
path: src/pip/_internal/utils/outdated.py
text: @user1:
I'd probably rename this to just ``SelfCheckState`` or similar, since it's no longer "global" vs "virtualenv".