Skip to content

Commit f63d1e3

Browse files
committed
Implement platform specific user config locations
1 parent 31b5cb5 commit f63d1e3

File tree

8 files changed

+136
-21
lines changed

8 files changed

+136
-21
lines changed

CHANGES.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
existing options have been made non functional but will still be accepted
1919
until their removal in pip v1.8. For more information please see
2020
https://pip.pypa.io/en/latest/reference/pip_install.html#caching
21-
21+
2222
* Added a virtualenv-specific configuration file. (:pull:`1364`)
2323

2424
* Added site-wide configuation files. (:pull:`1978`)
@@ -41,7 +41,7 @@
4141

4242
* Fixed :issue:`1618`. Pip no longer adds duplicate logging consumers, so it
4343
won't create duplicate output when being called multiple times. (:pull:`1723`)
44-
44+
4545
* Added general flag `--no-check-certificate` to tell pip NOT to verify
4646
the security of HTTPS connections. (:pull:`1741`:)
4747

@@ -67,13 +67,16 @@
6767

6868
* Fixed :issue:`1180`. Added support to respect proxies in ``pip search``. It
6969
also fixes :issue:`932` and :issue:`1104`. (:pull:`1902`)
70-
70+
7171
* Fixed :issue:`798` and :issue:`1060`. `pip install --download` works with vcs links.
7272
(:pull:`1926`)
7373

7474
* Fixed :issue:`1456`. Disabled warning about insecure index host when using localhost.
7575
Based off of Guy Rozendorn's work in :pull:`1718`. (:pull:`1967`)
7676

77+
* Allow the use of OS standard user configuration files instead of ones simply
78+
based around ``$HOME``. (:pull:`2021`)
79+
7780

7881
**1.5.7**
7982

docs/user_guide.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,15 @@ all users) configuration:
242242

243243
**Per-user**:
244244

245+
* On Unix the default configuration file is: :file:`$HOME/.config/pip/pip.conf`
246+
which respects the ``XDG_CONFIG_HOME`` environment variable.
247+
* On Mac OS X the configuration file is
248+
:file:`$HOME/Library/Application Support/pip/pip.conf`.
249+
* On Windows the configuration file is :file:`%APPDATA%\\pip\\pip.ini`.
250+
251+
There are also a legacy per-user configuration file which is also respected,
252+
these are located at:
253+
245254
* On Unix and Mac OS X the configuration file is: :file:`$HOME/.pip/pip.conf`
246255
* On Windows the configuration file is: :file:`%HOME%\\pip\\pip.ini`
247256

pip/baseparser.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
from pip._vendor.six import string_types
1111
from pip._vendor.six.moves import configparser
1212
from pip.locations import (
13-
default_config_file, default_config_basename, running_under_virtualenv,
13+
legacy_config_file, config_basename, running_under_virtualenv,
1414
site_config_files
1515
)
16-
from pip.utils import get_terminal_size
16+
from pip.utils import appdirs, get_terminal_size
1717

1818

1919
class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
@@ -154,13 +154,21 @@ def get_config_files(self):
154154
if config_file and os.path.exists(config_file):
155155
files.append(config_file)
156156
else:
157-
files.append(default_config_file)
157+
# This is the legacy config file, we consider it to be a lower
158+
# priority than the new file location.
159+
files.append(legacy_config_file)
160+
161+
# This is the new config file, we consider it to be a higher
162+
# priority than the legacy file.
163+
files.append(
164+
os.path.join(appdirs.user_config_dir("pip"), config_basename)
165+
)
158166

159167
# finally virtualenv configuration first trumping others
160168
if running_under_virtualenv():
161169
venv_config_file = os.path.join(
162170
sys.prefix,
163-
default_config_basename,
171+
config_basename,
164172
)
165173
if os.path.exists(venv_config_file):
166174
files.append(venv_config_file)

pip/locations.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,24 @@ def _get_build_prefix():
179179
if not os.path.exists(bin_py):
180180
bin_py = os.path.join(sys.prefix, 'bin')
181181
bin_user = os.path.join(user_site, 'bin')
182-
default_storage_dir = os.path.join(user_dir, 'pip')
183-
default_config_basename = 'pip.ini'
184-
default_config_file = os.path.join(
185-
default_storage_dir,
186-
default_config_basename,
182+
183+
config_basename = 'pip.ini'
184+
185+
legacy_storage_dir = os.path.join(user_dir, 'pip')
186+
legacy_config_file = os.path.join(
187+
legacy_storage_dir,
188+
config_basename,
187189
)
188190
else:
189191
bin_py = os.path.join(sys.prefix, 'bin')
190192
bin_user = os.path.join(user_site, 'bin')
191-
default_storage_dir = os.path.join(user_dir, '.pip')
192-
default_config_basename = 'pip.conf'
193-
default_config_file = os.path.join(
194-
default_storage_dir,
195-
default_config_basename,
193+
194+
config_basename = 'pip.conf'
195+
196+
legacy_storage_dir = os.path.join(user_dir, '.pip')
197+
legacy_config_file = os.path.join(
198+
legacy_storage_dir,
199+
config_basename,
196200
)
197201

198202
# Forcing to use /usr/local/bin for standard Mac OS X framework installs
@@ -201,7 +205,7 @@ def _get_build_prefix():
201205
bin_py = '/usr/local/bin'
202206

203207
site_config_files = [
204-
os.path.join(path, default_config_basename)
208+
os.path.join(path, config_basename)
205209
for path in appdirs.site_config_dirs('pip')
206210
]
207211

pip/utils/appdirs.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,37 @@ def user_log_dir(appname):
130130
return path
131131

132132

133+
def user_config_dir(appname, roaming=True):
134+
"""Return full path to the user-specific config dir for this application.
135+
136+
"appname" is the name of application.
137+
If None, just the system directory is returned.
138+
"roaming" (boolean, default True) can be set False to not use the
139+
Windows roaming appdata directory. That means that for users on a
140+
Windows network setup for roaming profiles, this user data will be
141+
sync'd on login. See
142+
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
143+
for a discussion of issues.
144+
145+
Typical user data directories are:
146+
Mac OS X: same as user_data_dir
147+
Unix: ~/.config/<AppName>
148+
Win *: same as user_data_dir
149+
150+
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
151+
That means, by deafult "~/.config/<AppName>".
152+
"""
153+
if WINDOWS:
154+
path = user_data_dir(appname, roaming=roaming)
155+
elif sys.platform == "darwin":
156+
path = user_data_dir(appname)
157+
else:
158+
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
159+
path = os.path.join(path, appname)
160+
161+
return path
162+
163+
133164
# for the discussion regarding site_config_dirs locations
134165
# see <https://github.com/pypa/pip/issues/1733>
135166
def site_config_dirs(appname):

tests/functional/test_install_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,9 @@ def test_options_from_venv_config(script, virtualenv):
172172
Test if ConfigOptionParser reads a virtualenv-local config file
173173
174174
"""
175-
from pip.locations import default_config_basename
175+
from pip.locations import config_basename
176176
conf = "[global]\nno-index = true"
177-
ini = virtualenv.location / default_config_basename
177+
ini = virtualenv.location / config_basename
178178
with open(ini, 'w') as f:
179179
f.write(conf)
180180
result = script.pip('install', '-vvv', 'INITools', expect_error=True)

tests/unit/test_appdirs.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,66 @@ def test_user_data_dir_linux_override(self, monkeypatch):
153153
assert appdirs.user_data_dir("pip") == "/home/test/.other-share/pip"
154154

155155

156+
class TestUserConfigDir:
157+
158+
def test_user_config_dir_win_no_roaming(self, monkeypatch):
159+
@pretend.call_recorder
160+
def _get_win_folder(base):
161+
return "C:\\Users\\test\\AppData\\Local"
162+
163+
monkeypatch.setattr(
164+
appdirs,
165+
"_get_win_folder",
166+
_get_win_folder,
167+
raising=False,
168+
)
169+
monkeypatch.setattr(appdirs, "WINDOWS", True)
170+
171+
assert (
172+
appdirs.user_config_dir("pip", roaming=False).replace("/", "\\")
173+
== "C:\\Users\\test\\AppData\\Local\\pip"
174+
)
175+
assert _get_win_folder.calls == [pretend.call("CSIDL_LOCAL_APPDATA")]
176+
177+
def test_user_config_dir_win_yes_roaming(self, monkeypatch):
178+
@pretend.call_recorder
179+
def _get_win_folder(base):
180+
return "C:\\Users\\test\\AppData\\Roaming"
181+
182+
monkeypatch.setattr(
183+
appdirs,
184+
"_get_win_folder",
185+
_get_win_folder,
186+
raising=False,
187+
)
188+
monkeypatch.setattr(appdirs, "WINDOWS", True)
189+
190+
assert (appdirs.user_config_dir("pip").replace("/", "\\")
191+
== "C:\\Users\\test\\AppData\\Roaming\\pip")
192+
assert _get_win_folder.calls == [pretend.call("CSIDL_APPDATA")]
193+
194+
def test_user_config_dir_osx(self, monkeypatch):
195+
monkeypatch.setenv("HOME", "/home/test")
196+
monkeypatch.setattr(sys, "platform", "darwin")
197+
198+
assert (appdirs.user_config_dir("pip")
199+
== "/home/test/Library/Application Support/pip")
200+
201+
def test_user_config_dir_linux(self, monkeypatch):
202+
monkeypatch.delenv("XDG_CONFIG_HOME")
203+
monkeypatch.setenv("HOME", "/home/test")
204+
monkeypatch.setattr(sys, "platform", "linux2")
205+
206+
assert appdirs.user_config_dir("pip") == "/home/test/.config/pip"
207+
208+
def test_user_config_dir_linux_override(self, monkeypatch):
209+
monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config")
210+
monkeypatch.setenv("HOME", "/home/test")
211+
monkeypatch.setattr(sys, "platform", "linux2")
212+
213+
assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip"
214+
215+
156216
class TestUserLogDir:
157217

158218
def test_user_log_dir_win(self, monkeypatch):

tests/unit/test_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,4 @@ def test_venv_config_file_found(self, monkeypatch):
287287
)
288288
monkeypatch.setattr(os.path, 'exists', lambda filename: True)
289289
cp = pip.baseparser.ConfigOptionParser()
290-
assert len(cp.get_config_files()) == 3
290+
assert len(cp.get_config_files()) == 4

0 commit comments

Comments
 (0)