Skip to content

Commit

Permalink
Implement platform specific user config locations
Browse files Browse the repository at this point in the history
  • Loading branch information
dstufft committed Sep 10, 2014
1 parent 31b5cb5 commit f63d1e3
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 21 deletions.
9 changes: 6 additions & 3 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
existing options have been made non functional but will still be accepted
until their removal in pip v1.8. For more information please see
https://pip.pypa.io/en/latest/reference/pip_install.html#caching

* Added a virtualenv-specific configuration file. (:pull:`1364`)

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

* Fixed :issue:`1618`. Pip no longer adds duplicate logging consumers, so it
won't create duplicate output when being called multiple times. (:pull:`1723`)

* Added general flag `--no-check-certificate` to tell pip NOT to verify
the security of HTTPS connections. (:pull:`1741`:)

Expand All @@ -67,13 +67,16 @@

* Fixed :issue:`1180`. Added support to respect proxies in ``pip search``. It
also fixes :issue:`932` and :issue:`1104`. (:pull:`1902`)

* Fixed :issue:`798` and :issue:`1060`. `pip install --download` works with vcs links.
(:pull:`1926`)

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

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


**1.5.7**

Expand Down
9 changes: 9 additions & 0 deletions docs/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,15 @@ all users) configuration:

**Per-user**:

* On Unix the default configuration file is: :file:`$HOME/.config/pip/pip.conf`
which respects the ``XDG_CONFIG_HOME`` environment variable.
* On Mac OS X the configuration file is
:file:`$HOME/Library/Application Support/pip/pip.conf`.
* On Windows the configuration file is :file:`%APPDATA%\\pip\\pip.ini`.

There are also a legacy per-user configuration file which is also respected,
these are located at:

* On Unix and Mac OS X the configuration file is: :file:`$HOME/.pip/pip.conf`
* On Windows the configuration file is: :file:`%HOME%\\pip\\pip.ini`

Expand Down
16 changes: 12 additions & 4 deletions pip/baseparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
from pip._vendor.six import string_types
from pip._vendor.six.moves import configparser
from pip.locations import (
default_config_file, default_config_basename, running_under_virtualenv,
legacy_config_file, config_basename, running_under_virtualenv,
site_config_files
)
from pip.utils import get_terminal_size
from pip.utils import appdirs, get_terminal_size


class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
Expand Down Expand Up @@ -154,13 +154,21 @@ def get_config_files(self):
if config_file and os.path.exists(config_file):
files.append(config_file)
else:
files.append(default_config_file)
# This is the legacy config file, we consider it to be a lower
# priority than the new file location.
files.append(legacy_config_file)

# This is the new config file, we consider it to be a higher
# priority than the legacy file.
files.append(
os.path.join(appdirs.user_config_dir("pip"), config_basename)
)

# finally virtualenv configuration first trumping others
if running_under_virtualenv():
venv_config_file = os.path.join(
sys.prefix,
default_config_basename,
config_basename,
)
if os.path.exists(venv_config_file):
files.append(venv_config_file)
Expand Down
26 changes: 15 additions & 11 deletions pip/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,20 +179,24 @@ def _get_build_prefix():
if not os.path.exists(bin_py):
bin_py = os.path.join(sys.prefix, 'bin')
bin_user = os.path.join(user_site, 'bin')
default_storage_dir = os.path.join(user_dir, 'pip')
default_config_basename = 'pip.ini'
default_config_file = os.path.join(
default_storage_dir,
default_config_basename,

config_basename = 'pip.ini'

legacy_storage_dir = os.path.join(user_dir, 'pip')
legacy_config_file = os.path.join(
legacy_storage_dir,
config_basename,
)
else:
bin_py = os.path.join(sys.prefix, 'bin')
bin_user = os.path.join(user_site, 'bin')
default_storage_dir = os.path.join(user_dir, '.pip')
default_config_basename = 'pip.conf'
default_config_file = os.path.join(
default_storage_dir,
default_config_basename,

config_basename = 'pip.conf'

legacy_storage_dir = os.path.join(user_dir, '.pip')
legacy_config_file = os.path.join(
legacy_storage_dir,
config_basename,
)

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

site_config_files = [
os.path.join(path, default_config_basename)
os.path.join(path, config_basename)
for path in appdirs.site_config_dirs('pip')
]

Expand Down
31 changes: 31 additions & 0 deletions pip/utils/appdirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,37 @@ def user_log_dir(appname):
return path


def user_config_dir(appname, roaming=True):
"""Return full path to the user-specific config dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"roaming" (boolean, default True) can be set False to not use the
Windows roaming appdata directory. That means that for users on a
Windows network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user data directories are:
Mac OS X: same as user_data_dir
Unix: ~/.config/<AppName>
Win *: same as user_data_dir
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
That means, by deafult "~/.config/<AppName>".
"""
if WINDOWS:
path = user_data_dir(appname, roaming=roaming)
elif sys.platform == "darwin":
path = user_data_dir(appname)
else:
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
path = os.path.join(path, appname)

return path


# for the discussion regarding site_config_dirs locations
# see <https://github.com/pypa/pip/issues/1733>
def site_config_dirs(appname):
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_install_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ def test_options_from_venv_config(script, virtualenv):
Test if ConfigOptionParser reads a virtualenv-local config file
"""
from pip.locations import default_config_basename
from pip.locations import config_basename
conf = "[global]\nno-index = true"
ini = virtualenv.location / default_config_basename
ini = virtualenv.location / config_basename
with open(ini, 'w') as f:
f.write(conf)
result = script.pip('install', '-vvv', 'INITools', expect_error=True)
Expand Down
60 changes: 60 additions & 0 deletions tests/unit/test_appdirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,66 @@ def test_user_data_dir_linux_override(self, monkeypatch):
assert appdirs.user_data_dir("pip") == "/home/test/.other-share/pip"


class TestUserConfigDir:

def test_user_config_dir_win_no_roaming(self, monkeypatch):
@pretend.call_recorder
def _get_win_folder(base):
return "C:\\Users\\test\\AppData\\Local"

monkeypatch.setattr(
appdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
monkeypatch.setattr(appdirs, "WINDOWS", True)

assert (
appdirs.user_config_dir("pip", roaming=False).replace("/", "\\")
== "C:\\Users\\test\\AppData\\Local\\pip"
)
assert _get_win_folder.calls == [pretend.call("CSIDL_LOCAL_APPDATA")]

def test_user_config_dir_win_yes_roaming(self, monkeypatch):
@pretend.call_recorder
def _get_win_folder(base):
return "C:\\Users\\test\\AppData\\Roaming"

monkeypatch.setattr(
appdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
monkeypatch.setattr(appdirs, "WINDOWS", True)

assert (appdirs.user_config_dir("pip").replace("/", "\\")
== "C:\\Users\\test\\AppData\\Roaming\\pip")
assert _get_win_folder.calls == [pretend.call("CSIDL_APPDATA")]

def test_user_config_dir_osx(self, monkeypatch):
monkeypatch.setenv("HOME", "/home/test")
monkeypatch.setattr(sys, "platform", "darwin")

assert (appdirs.user_config_dir("pip")
== "/home/test/Library/Application Support/pip")

def test_user_config_dir_linux(self, monkeypatch):
monkeypatch.delenv("XDG_CONFIG_HOME")
monkeypatch.setenv("HOME", "/home/test")
monkeypatch.setattr(sys, "platform", "linux2")

assert appdirs.user_config_dir("pip") == "/home/test/.config/pip"

def test_user_config_dir_linux_override(self, monkeypatch):
monkeypatch.setenv("XDG_CONFIG_HOME", "/home/test/.other-config")
monkeypatch.setenv("HOME", "/home/test")
monkeypatch.setattr(sys, "platform", "linux2")

assert appdirs.user_config_dir("pip") == "/home/test/.other-config/pip"


class TestUserLogDir:

def test_user_log_dir_win(self, monkeypatch):
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,4 @@ def test_venv_config_file_found(self, monkeypatch):
)
monkeypatch.setattr(os.path, 'exists', lambda filename: True)
cp = pip.baseparser.ConfigOptionParser()
assert len(cp.get_config_files()) == 3
assert len(cp.get_config_files()) == 4

0 comments on commit f63d1e3

Please sign in to comment.