Skip to content

Commit

Permalink
Implement site-wide configuration settings
Browse files Browse the repository at this point in the history
* add site_config_dirs() to appdirs to determine locations across OSes
* add system_config_files to locations.py
* add system_config_files to get_config_files() and re-order files entries to correct precedence
* document changes to configuration files in user guide

Closes pypa#309
  • Loading branch information
r1chardj0n3s committed Aug 22, 2014
1 parent 1d81183 commit aedca3c
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Expand Up @@ -19,6 +19,8 @@

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

* Added site-wide configuation files. (:pull:`1978`)

* `wsgiref` and `argparse` (for >py26) are now excluded from `pip list` and `pip
freeze` (:pull:`1606`, :pull:`1369`)

Expand Down
39 changes: 36 additions & 3 deletions docs/user_guide.rst
Expand Up @@ -237,12 +237,45 @@ pip allows you to set all command line option defaults in a standard ini
style config file.

The names and locations of the configuration files vary slightly across
platforms.
platforms. You may have per-user, per-virtualenv or site-wide (shared amongst
all users) configuration:

**Per-user**:

* 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`
* On Windows the configuration file is: :file:`%HOME%\\pip\\pip.ini`

You can set a custom path location for this config file using the environment
variable ``PIP_CONFIG_FILE``.

**Inside a virtualenv**:

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

**Site-wide**:

* On Unix the file may be located in in :file:`/etc/pip.conf`. Alternatively
it may be in a "pip" subdirectory of any of the paths set in the
environment variable ``XDG_CONFIG_DIRS`` (if it exists), for example
:file:`/etc/xdg/pip/pip.conf`.
* On Mac OS X the file is: :file:`/Library/Application Support/pip/pip.conf`
* On Windows XP the file is:
:file:`C:\\Documents and Settings\\All Users\\Application Data\\PyPA\\pip\\pip.conf`
* On Windows 7 and later the file is hidden, but writeable at
:file:`C:\\ProgramData\\PyPA\\pip\\pip.conf`
* Site-wide configuration is not supported on Windows Vista

If multiple configuration files are found by pip then they are combined in
the following order:

1. Firstly the site-wide file is read, then
2. The per-user file is read, and finally
3. The virtualenv-specific file is read.

You can set a custom path location for the config file using the environment variable ``PIP_CONFIG_FILE``.
Each file read overrides any values read from previous files, so if the
global timeout is specified in both the site-wide file and the per-user file
then the latter value is the one that will be used.

The names of the settings are derived from the long command line option, e.g.
if you want to use a different package index (``--index-url``) and set the
Expand Down
40 changes: 40 additions & 0 deletions pip/appdirs.py
Expand Up @@ -51,6 +51,46 @@ def user_cache_dir(appname):
return path


# for the discussion regarding site_config_dirs locations
# see <https://github.com/pypa/pip/issues/1733>
def site_config_dirs(appname):
"""Return a list of potential user-shared config dirs for this application.
"appname" is the name of application.
Typical user config directories are:
Mac OS X: /Library/Application Support/<AppName>/
Unix: /etc or $XDG_CONFIG_DIRS[i]/<AppName>/ for each value in
$XDG_CONFIG_DIRS
Win XP: C:\Documents and Settings\All Users\Application ...
...Data\<AppName>\
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory
on Vista.)
Win 7: Hidden, but writeable on Win 7:
C:\ProgramData\<AppName>\
"""
if sys.platform == "win32":
path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
pathlist = [os.path.join(path, appname)]
elif sys.platform == 'darwin':
pathlist = [os.path.join('/Library/Application Support', appname)]
else:
# try looking in $XDG_CONFIG_DIRS
xdg_config_dirs = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
if xdg_config_dirs:
pathlist = [
os.sep.join([os.path.expanduser(x), appname])
for x in xdg_config_dirs.split(os.pathsep)
]
else:
pathlist = []

# always look in /etc directly as well
pathlist.append('/etc')

return pathlist


# -- Windows support functions --

def _get_win_folder_from_registry(csidl_name):
Expand Down
16 changes: 14 additions & 2 deletions pip/baseparser.py
Expand Up @@ -10,6 +10,7 @@
from pip._vendor.six.moves import configparser
from pip.locations import (
default_config_file, default_config_basename, running_under_virtualenv,
site_config_files
)
from pip.util import get_terminal_size

Expand Down Expand Up @@ -138,20 +139,31 @@ def __init__(self, *args, **kwargs):
optparse.OptionParser.__init__(self, *args, **kwargs)

def get_config_files(self):
# the files returned by this method will be parsed in order with the
# first files listed being overridden by later files in standard
# ConfigParser fashion
config_file = os.environ.get('PIP_CONFIG_FILE', False)
if config_file == os.devnull:
return []

# at the base we have any site-wide configuration
files = list(site_config_files)

# per-user configuration next
if config_file and os.path.exists(config_file):
files = [config_file]
files.append(config_file)
else:
files = [default_config_file]
files.append(default_config_file)

# finally virtualenv configuration first trumping others
if running_under_virtualenv():
venv_config_file = os.path.join(
sys.prefix,
default_config_basename,
)
if os.path.exists(venv_config_file):
files.append(venv_config_file)

return files

def check_default(self, option, key, val):
Expand Down
5 changes: 5 additions & 0 deletions pip/locations.py
Expand Up @@ -202,6 +202,11 @@ def _get_build_prefix():
bin_py = '/usr/local/bin'
default_log_file = os.path.join(user_dir, 'Library/Logs/pip.log')

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


def distutils_scheme(dist_name, user=False, home=None, root=None):
"""
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/test_appdirs.py
Expand Up @@ -43,3 +43,50 @@ def test_user_cache_dir_linux_override(self, monkeypatch):
monkeypatch.setattr(sys, "platform", "linux2")

assert appdirs.user_cache_dir("pip") == "/home/test/.other-cache/pip"

def test_site_config_dirs_win(self, monkeypatch):
@pretend.call_recorder
def _get_win_folder(base):
return "C:\\ProgramData"

monkeypatch.setattr(
appdirs,
"_get_win_folder",
_get_win_folder,
raising=False,
)
monkeypatch.setattr(sys, "platform", "win32")

result = [
e.replace("/", "\\")
for e in appdirs.site_config_dirs("pip")
]
assert result == ["C:\\ProgramData\\pip"]
assert _get_win_folder.calls == [pretend.call("CSIDL_COMMON_APPDATA")]

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

assert appdirs.site_config_dirs("pip") == \
["/Library/Application Support/pip"]

def test_site_config_dirs_linux(self, monkeypatch):
monkeypatch.delenv("XDG_CONFIG_DIRS")
monkeypatch.setattr(sys, "platform", "linux2")

assert appdirs.site_config_dirs("pip") == [
'/etc/xdg/pip',
'/etc'
]

def test_site_config_dirs_linux_override(self, monkeypatch):
monkeypatch.setenv("XDG_CONFIG_DIRS", "/spam:/etc:/etc/xdg")
monkeypatch.setattr(sys, "platform", "linux2")

assert appdirs.site_config_dirs("pip") == [
'/spam/pip',
'/etc/pip',
'/etc/xdg/pip',
'/etc'
]
5 changes: 4 additions & 1 deletion tests/unit/test_options.py
Expand Up @@ -275,6 +275,9 @@ def test_venv_config_file_found(self, monkeypatch):
lambda self: None,
)

# strict limit on the site_config_files list
monkeypatch.setattr(pip.baseparser, 'site_config_files', ['/a/place'])

# If we are running in a virtualenv and all files appear to exist,
# we should see two config files.
monkeypatch.setattr(
Expand All @@ -284,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()) == 2
assert len(cp.get_config_files()) == 3

0 comments on commit aedca3c

Please sign in to comment.