diff options
| -rw-r--r-- | docs/easy_install.txt | 9 | ||||
| -rwxr-xr-x | setuptools/package_index.py | 64 |
2 files changed, 72 insertions, 1 deletions
diff --git a/docs/easy_install.txt b/docs/easy_install.txt index 42a5323d..a69ddd59 100644 --- a/docs/easy_install.txt +++ b/docs/easy_install.txt @@ -486,6 +486,15 @@ You can do this with both index page URLs and direct download URLs. As long as any HTML pages read by easy_install use *relative* links to point to the downloads, the same user ID and password will be used to do the downloading. +Using .pypirc Credentials +------------------------- + +In additional to supplying credentials in the URL, ``easy_install`` will also +honor credentials if present in the .pypirc file. Teams maintaining a private +repository of packages may already have defined access credentials for +uploading packages according to the distutils documentation. ``easy_install`` +will attempt to honor those if present. Refer to the distutils documentation +for Python 2.5 or later for details on the syntax. Controlling Build Options ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 4c9e40a7..26d1d4ad 100755 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -17,7 +17,8 @@ from distutils.errors import DistutilsError from setuptools.compat import (urllib2, httplib, StringIO, HTTPError, urlparse, urlunparse, unquote, splituser, url2pathname, name2codepoint, - unichr, urljoin, urlsplit, urlunsplit) + unichr, urljoin, urlsplit, urlunsplit, + ConfigParser) from setuptools.compat import filterfalse from fnmatch import translate from setuptools.py24compat import hashlib @@ -920,6 +921,60 @@ def _encode_auth(auth): # strip the trailing carriage return return encoded.rstrip() +class Credential(object): + """ + A username/password pair. Use like a namedtuple. + """ + def __init__(self, username, password): + self.username = username + self.password = password + + def __iter__(self): + yield self.username + yield self.password + + def __str__(self): + return '%(username)s:%(password)s' % vars(self) + +class PyPIConfig(ConfigParser.ConfigParser): + + def __init__(self): + """ + Load from ~/.pypirc + """ + defaults = dict.fromkeys(['username', 'password', 'repository'], '') + super(PyPIConfig, self).__init__(defaults) + + rc = os.path.join(os.path.expanduser('~'), '.pypirc') + if os.path.exists(rc): + self.read(rc) + + @property + def creds_by_repository(self): + sections_with_repositories = [ + section for section in self.sections() + if self.get(section, 'repository').strip() + ] + + return dict(map(self._get_repo_cred, sections_with_repositories)) + + def _get_repo_cred(self, section): + repo = self.get(section, 'repository').strip() + return repo, Credential( + self.get(section, 'username').strip(), + self.get(section, 'password').strip(), + ) + + def find_credential(self, url): + """ + If the URL indicated appears to be a repository defined in this + config, return the credential for that repository. + """ + for repository, cred in self.creds_by_repository.items(): + if url.startswith(repository): + return cred + + def open_with_auth(url, opener=urllib2.urlopen): """Open a urllib2 request, handling HTTP authentication""" @@ -935,6 +990,13 @@ def open_with_auth(url, opener=urllib2.urlopen): else: auth = None + if not auth: + cred = PyPIConfig().find_credential(url) + if cred: + auth = str(cred) + info = cred.username, url + log.info('Authenticating as %s for %s (from .pypirc)' % info) + if auth: auth = "Basic " + _encode_auth(auth) new_url = urlunparse((scheme,host,path,params,query,frag)) |
