Skip to content

Commit 52ca026

Browse files
committed
Use CacheControl instead of custom cache code
* Deprecates the --download-cache option & removes the download cache code. * Removes the in memory page cache on the index * Uses CacheControl to cache all cacheable HTTP requests to the filesystem. * Properly handles CacheControl headers for unconditional caching. * Will use ETag and Last-Modified headers to attempt to do a conditional HTTP request to speed up cache misses and turn them into cache hits. * Removes some concurrency unsafe code in the download cache accesses. * Uses a Cache-Control request header to limit the maximum length of time a cache is valid for. * Adds pip.appdirs to handle platform specific application directories such as cache, config, data, etc.
1 parent 077fa14 commit 52ca026

16 files changed

+412
-224
lines changed

CHANGES.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@
3737
* Fixed :issue:`1775`. `pip wheel` wasn't building wheels for dependencies of
3838
editable requirements.
3939

40+
* **DEPRECATION** ``pip install --download-cache`` and
41+
``pip wheel --download-cache`` command line flags have been deprecated and
42+
the functionality removed. Since pip now automatically configures and uses
43+
it's internal HTTP cache which supplants the ``--download-cache`` the
44+
existing options have been made non functional but will still be accepted
45+
until their removal in pip v1.8. For more information please see
46+
https://pip.pypa.io/en/latest/reference/pip_install.html#caching
47+
4048

4149
**1.5.5 (2014-05-03)**
4250

docs/reference/pip_install.rst

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,36 @@ Starting with v1.3, pip provides SSL certificate verification over https, for th
249249
of providing secure, certified downloads from PyPI.
250250

251251

252+
.. _`Caching`:
253+
254+
Caching
255+
+++++++
256+
257+
Starting with v1.6, pip provides an on by default cache which functions
258+
similarly to that of a web browser. While the cache is on by default and is
259+
designed do the right thing by default you can disable the cache and always
260+
access PyPI by utilizing the ``--no-cache-dir`` option.
261+
262+
When making any HTTP request pip will first check it's local cache to determine
263+
if it has a suitable response stored for that request which has not expired. If
264+
it does then it simply returns that response and doesn't make the request.
265+
266+
If it has a response stored, but it has expired, then it will attempt to make a
267+
conditional request to refresh the cache which will either return an empty
268+
response telling pip to simply use the cached item (and refresh the expiration
269+
timer) or it will return a whole new response which pip can then store in the
270+
cache.
271+
272+
When storing items in the cache pip will respect the ``CacheControl`` header
273+
if it exists, or it will fall back to the ``Expires`` header if that exists.
274+
This allows pip to function as a browser would, and allows the index server
275+
to communicate to pip how long it is reasonable to cache any particular item.
276+
277+
While this cache attempts to minimize network activity, it does not prevent
278+
network access all together. If you want a fast/local install solution that
279+
circumvents accessing PyPI, see :ref:`Fast & Local Installs`.
280+
281+
252282
Hash Verification
253283
+++++++++++++++++
254284

@@ -264,26 +294,6 @@ It is not intended to provide security against tampering. For that,
264294
see :ref:`SSL Certificate Verification`
265295

266296

267-
Download Cache
268-
++++++++++++++
269-
270-
pip offers a :ref:`--download-cache <install_--download-cache>` option for
271-
installs to prevent redundant downloads of archives from PyPI.
272-
273-
The point of this cache is *not* to circumvent the index crawling process, but
274-
to *just* prevent redundant downloads.
275-
276-
Items are stored in this cache based on the url the archive was found at, not
277-
simply the archive name.
278-
279-
If you want a fast/local install solution that circumvents crawling PyPI, see
280-
the :ref:`Fast & Local Installs`.
281-
282-
Like all options, :ref:`--download-cache <install_--download-cache>`, can also
283-
be set as an environment variable, or placed into the pip config file. See the
284-
:ref:`Configuration` section.
285-
286-
287297
.. _`editable-installs`:
288298

289299
"Editable" Installs

pip/appdirs.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""
2+
This code was taken from https://github.com/ActiveState/appdirs and modified
3+
to suite our purposes.
4+
"""
5+
import os
6+
import sys
7+
8+
from pip._vendor import six
9+
10+
11+
def user_cache_dir(appname):
12+
r"""
13+
Return full path to the user-specific cache dir for this application.
14+
15+
"appname" is the name of application.
16+
17+
Typical user cache directories are:
18+
Mac OS X: ~/Library/Caches/<AppName>
19+
Unix: ~/.cache/<AppName> (XDG default)
20+
Windows: C:\Users\<username>\AppData\Local\<AppName>\Cache
21+
22+
On Windows the only suggestion in the MSDN docs is that local settings go
23+
in the `CSIDL_LOCAL_APPDATA` directory. This is identical to the
24+
non-roaming app data dir (the default returned by `user_data_dir`). Apps
25+
typically put cache data somewhere *under* the given dir here. Some
26+
examples:
27+
...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
28+
...\Acme\SuperApp\Cache\1.0
29+
30+
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
31+
"""
32+
if sys.platform == "win32":
33+
# Get the base path
34+
path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
35+
36+
# Add our app name and Cache directory to it
37+
path = os.path.join(path, appname, "Cache")
38+
elif sys.platform == "darwin":
39+
# Get the base path
40+
path = os.path.expanduser("~/Library/Caches")
41+
42+
# Add our app name to it
43+
path = os.path.join(path, appname)
44+
else:
45+
# Get the base path
46+
path = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
47+
48+
# Add our app name to it
49+
path = os.path.join(path, appname)
50+
51+
return path
52+
53+
54+
# -- Windows support functions --
55+
56+
def _get_win_folder_from_registry(csidl_name):
57+
"""
58+
This is a fallback technique at best. I'm not sure if using the
59+
registry for this guarantees us the correct answer for all CSIDL_*
60+
names.
61+
"""
62+
import _winreg
63+
64+
shell_folder_name = {
65+
"CSIDL_APPDATA": "AppData",
66+
"CSIDL_COMMON_APPDATA": "Common AppData",
67+
"CSIDL_LOCAL_APPDATA": "Local AppData",
68+
}[csidl_name]
69+
70+
key = _winreg.OpenKey(
71+
_winreg.HKEY_CURRENT_USER,
72+
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
73+
)
74+
directory, _type = _winreg.QueryValueEx(key, shell_folder_name)
75+
return directory
76+
77+
78+
def _get_win_folder_with_pywin32(csidl_name):
79+
from win32com.shell import shellcon, shell
80+
directory = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
81+
# Try to make this a unicode path because SHGetFolderPath does
82+
# not return unicode strings when there is unicode data in the
83+
# path.
84+
try:
85+
directory = six.text_type(directory)
86+
87+
# Downgrade to short path name if have highbit chars. See
88+
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
89+
has_high_char = False
90+
for c in directory:
91+
if ord(c) > 255:
92+
has_high_char = True
93+
break
94+
if has_high_char:
95+
try:
96+
import win32api
97+
directory = win32api.GetShortPathName(directory)
98+
except ImportError:
99+
pass
100+
except UnicodeError:
101+
pass
102+
return directory
103+
104+
105+
def _get_win_folder_with_ctypes(csidl_name):
106+
csidl_const = {
107+
"CSIDL_APPDATA": 26,
108+
"CSIDL_COMMON_APPDATA": 35,
109+
"CSIDL_LOCAL_APPDATA": 28,
110+
}[csidl_name]
111+
112+
buf = ctypes.create_unicode_buffer(1024)
113+
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
114+
115+
# Downgrade to short path name if have highbit chars. See
116+
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
117+
has_high_char = False
118+
for c in buf:
119+
if ord(c) > 255:
120+
has_high_char = True
121+
break
122+
if has_high_char:
123+
buf2 = ctypes.create_unicode_buffer(1024)
124+
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
125+
buf = buf2
126+
127+
return buf.value
128+
129+
if sys.platform == "win32":
130+
try:
131+
import win32com.shell # noqa
132+
_get_win_folder = _get_win_folder_with_pywin32
133+
except ImportError:
134+
try:
135+
import ctypes
136+
_get_win_folder = _get_win_folder_with_ctypes
137+
except ImportError:
138+
_get_win_folder = _get_win_folder_from_registry

pip/basecommand.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND,
2020
PREVIOUS_BUILD_DIR_ERROR,
2121
)
22-
from pip.util import get_prog
22+
from pip.util import get_prog, normalize_path
2323

2424

2525
__all__ = ['Command']
@@ -54,7 +54,10 @@ def __init__(self):
5454
self.parser.add_option_group(gen_opts)
5555

5656
def _build_session(self, options):
57-
session = PipSession(retries=options.retries)
57+
session = PipSession(
58+
cache=normalize_path(os.path.join(options.cache_dir, "http")),
59+
retries=options.retries,
60+
)
5861

5962
# Handle custom ca-bundles from the user
6063
if options.cert:

pip/cmdoptions.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
"""
1010
import copy
1111
from optparse import OptionGroup, SUPPRESS_HELP, Option
12-
from pip.locations import build_prefix, default_log_file, src_prefix
12+
from pip.locations import (
13+
USER_CACHE_DIR, build_prefix, default_log_file, src_prefix,
14+
)
1315

1416

1517
def make_option_group(group, parser):
@@ -322,12 +324,26 @@ def make(self):
322324
'find-links locations.'),
323325
)
324326

327+
cache_dir = OptionMaker(
328+
"--cache-dir",
329+
dest="cache_dir",
330+
default=USER_CACHE_DIR,
331+
metavar="dir",
332+
help="Store the cache data in <dir>."
333+
)
334+
335+
no_cache = OptionMaker(
336+
"--no-cache-dir",
337+
dest="cache_dir",
338+
action="store_false",
339+
help="Disable the cache.",
340+
)
341+
325342
download_cache = OptionMaker(
326343
'--download-cache',
327344
dest='download_cache',
328-
metavar='dir',
329345
default=None,
330-
help='Cache downloaded packages in <dir>.')
346+
help=SUPPRESS_HELP)
331347

332348
no_deps = OptionMaker(
333349
'--no-deps', '--no-dependencies',
@@ -396,6 +412,8 @@ def make(self):
396412
cert,
397413
client_cert,
398414
no_check_certificate,
415+
cache_dir,
416+
no_cache,
399417
]
400418
}
401419

pip/commands/install.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,14 @@ def run(self, options, args):
248248
)
249249
index_urls += options.mirrors
250250

251+
if options.download_cache:
252+
logger.deprecated(
253+
"1.8",
254+
"--download-cache has been deprecated and will be removed in "
255+
" the future. Pip now automatically uses and configures its "
256+
"cache."
257+
)
258+
251259
session = self._build_session(options)
252260

253261
finder = self._build_package_finder(options, index_urls, session)
@@ -256,7 +264,6 @@ def run(self, options, args):
256264
build_dir=options.build_dir,
257265
src_dir=options.src_dir,
258266
download_dir=options.download_dir,
259-
download_cache=options.download_cache,
260267
upgrade=options.upgrade,
261268
as_egg=options.as_egg,
262269
ignore_installed=options.ignore_installed,

pip/commands/wheel.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ def run(self, options, args):
144144
)
145145
index_urls += options.mirrors
146146

147+
if options.download_cache:
148+
logger.deprecated(
149+
"1.8",
150+
"--download-cache has been deprecated and will be removed in "
151+
" the future. Pip now automatically uses and configures its "
152+
"cache."
153+
)
154+
147155
session = self._build_session(options)
148156

149157
finder = PackageFinder(
@@ -162,7 +170,6 @@ def run(self, options, args):
162170
build_dir=options.build_dir,
163171
src_dir=options.src_dir,
164172
download_dir=None,
165-
download_cache=options.download_cache,
166173
ignore_dependencies=options.ignore_dependencies,
167174
ignore_installed=True,
168175
session=session,

0 commit comments

Comments
 (0)