Index: core.py =================================================================== --- core.py (revision 59974) +++ core.py (working copy) @@ -20,6 +20,7 @@ # Mainly import these so setup scripts can "from distutils.core import" them. from distutils.dist import Distribution from distutils.cmd import Command +from distutils.pypirc import PyPIRCCommand from distutils.extension import Extension # This is a barebones help message generated displayed when the user Index: pypirc.py =================================================================== --- pypirc.py (revision 0) +++ pypirc.py (revision 0) @@ -0,0 +1,91 @@ +"""distutils.pypirc + +Provides the PyPIRcCommand class, the base class for the command classes +that uses .pypirc in the distutils.command package. +""" +import os +from ConfigParser import ConfigParser + +from distutils.core import Command + +class PyPIRCCommand(Command): + + DEFAULT_REPOSITORY = 'http://www.python.org/pypi' + DEFAULT_REALM = 'pypi' + repository = '' + realm = '' + + def _store_pypirc(self, username, password): + """stores the choice""" + rc = os.path.join(os.environ['HOME'], '.pypirc') + f = open(rc, 'w') + try: + f.write(('[pypirc]\n servers = default\n ' + '[default]\nusername:%s\npassword:%s\n') % \ + (username, password)) + finally: + f.close() + try: + os.chmod(rc, 0600) + except: # XXX ???? + pass + + def _read_pypirc(self): + """Reads the .pypirc file.""" + if os.environ.has_key('HOME'): + rc = os.path.join(os.environ['HOME'], '.pypirc') + if os.path.exists(rc): + print 'Using PyPI login from %s' % rc + repository = self.repository or self.DEFAULT_REPOSITORY + realm = self.realm or self.DEFAULT_REALM + + config = ConfigParser() + config.read(rc) + sections = config.sections() + + if 'distutils' in sections: + # let's get the list of servers + index_servers = config.get('distutils', 'index-servers') + _servers = [server.strip() for server in + index_servers.split('\n') + if server.strip() != ''] + if _servers == []: + # nothing set, let's try to get the defaut pypi + if 'pypi' in sections: + _servers = ['pypi'] + else: + # the file is not properly defined, returning + # an empty dict + return {} + for server in _servers: + current = {'server': server} + current['username'] = config.get(server, 'username') + current['password'] = config.get(server, 'password') + + # optional params + for key, default in (('repository', + self.DEFAULT_REPOSITORY), + ('realm', self.DEFAULT_REALM)): + if config.has_option(server, key): + current[key] = config.get(server, key) + else: + current[key] = default + if (current['server'] == repository or + current['repository'] == repository): + return current + elif 'server-login' in sections: + # old format + server = 'server-login' + if config.has_option(server, 'repository'): + repository = config.get(server, 'repository') + else: + repository = self.DEFAULT_REPOSITORY + return {'username': config.get(server, 'username'), + 'password': config.get(server, 'password'), + 'repository': repository, + 'server': server, + 'realm': self.DEFAULT_REALM} + + return {} + + Property changes on: pypirc.py ___________________________________________________________________ Name: svn:eol-style + native Index: tests/test_upload.py =================================================================== --- tests/test_upload.py (revision 0) +++ tests/test_upload.py (revision 0) @@ -0,0 +1,34 @@ +"""Tests for distutils.command.upload.""" +import sys +import os +import unittest + +from distutils.command.upload import upload +from distutils.core import Distribution + +from distutils.tests import support +from distutils.tests.test_pypirc import PYPIRC, PyPIRCCommandTestCase + +class uploadTestCase(PyPIRCCommandTestCase): + + def test_finalize_options(self): + + # new format + f = open(self.rc, 'w') + f.write(PYPIRC) + f.close() + + dist = Distribution() + cmd = upload(dist) + cmd.finalize_options() + for attr, waited in (('username', 'me'), ('password', 'secret'), + ('realm', 'pypi'), + ('repository', 'http://www.python.org/pypi')): + self.assertEquals(getattr(cmd, attr), waited) + + +def test_suite(): + return unittest.makeSuite(uploadTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") Property changes on: tests/test_upload.py ___________________________________________________________________ Name: svn:eol-style + native Index: tests/test_register.py =================================================================== --- tests/test_register.py (revision 0) +++ tests/test_register.py (revision 0) @@ -0,0 +1,45 @@ +"""Tests for distutils.command.register.""" +import sys +import os +import unittest + +from distutils.command.register import register +from distutils.core import Distribution + +from distutils.tests import support +from distutils.tests.test_pypirc import PYPIRC, PyPIRCCommandTestCase + +import urllib2 + +data_sent = {} + +class Opener(object): + + def open(self, req): + data_sent[req.get_host()] = (req.headers, req.data) + +def build_opener(auth): + return Opener() + +urllib2.build_opener = build_opener + +class registerTestCase(PyPIRCCommandTestCase): + + def test_register(self): + # new format + f = open(self.rc, 'w') + f.write(PYPIRC) + f.close() + + dist = Distribution() + cmd = register(dist) + cmd.send_metadata() + length = data_sent['www.python.org'][0]['Content-length'] + self.assertEquals(length, '1392') + +def test_suite(): + return unittest.makeSuite(registerTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") + Property changes on: tests/test_register.py ___________________________________________________________________ Name: svn:eol-style + native Index: tests/test_pypirc.py =================================================================== --- tests/test_pypirc.py (revision 0) +++ tests/test_pypirc.py (revision 0) @@ -0,0 +1,101 @@ +"""Tests for distutils.pypirc.pypirc.""" +import sys +import os +import unittest + +from distutils.core import PyPIRCCommand +from distutils.core import Distribution + +from distutils.tests import support + +PYPIRC = """\ +[distutils] + +index-servers = + server1 + server2 + +[server1] +username:me +password:secret + +[server2] +username:meagain +password: secret +repository:http://another.pypi/ +""" + +PYPIRC_OLD = """\ +[server-login] +username:tarek +password:secret +""" + +class PyPIRCCommandTestCase(support.TempdirManager, unittest.TestCase): + + def setUp(self): + """Patches the environment.""" + if os.environ.has_key('HOME'): + self._old_home = os.environ['HOME'] + else: + self._old_home = None + curdir = os.path.dirname(__file__) + os.environ['HOME'] = curdir + self.rc = os.path.join(curdir, '.pypirc') + + def tearDown(self): + """Removes the patch.""" + if self._old_home is None: + del os.environ['HOME'] + else: + os.environ['HOME'] = self._old_home + if os.path.exists(self.rc): + os.remove(self.rc) + + def test_server_registration(self): + # This test makes sure PyPIRCCommand knows how to: + # 1. handle several sections in .pypirc + # 2. handle the old format + + # new format + f = open(self.rc, 'w') + f.write(PYPIRC) + f.close() + + dist = Distribution() + class command(PyPIRCCommand): + def __init__(self, dist): + PyPIRCCommand.__init__(self, dist) + def initialize_options(self): + pass + finalize_options = initialize_options + + cmd = command(dist) + config = cmd._read_pypirc() + + config = config.items() + config.sort() + waited = [('password', 'secret'), ('realm', 'pypi'), + ('repository', 'http://www.python.org/pypi'), + ('server', 'server1'), ('username', 'me')] + self.assertEquals(config, waited) + + # old format + f = open(self.rc, 'w') + f.write(PYPIRC_OLD) + f.close() + + config = cmd._read_pypirc() + config = config.items() + config.sort() + waited = [('password', 'secret'), ('realm', 'pypi'), + ('repository', 'http://www.python.org/pypi'), + ('server', 'server-login'), ('username', 'tarek')] + self.assertEquals(config, waited) + + +def test_suite(): + return unittest.makeSuite(PyPIRCCommandTestCase) + +if __name__ == "__main__": + unittest.main(defaultTest="test_suite") Property changes on: tests/test_pypirc.py ___________________________________________________________________ Name: svn:eol-style + native Index: command/register.py =================================================================== --- command/register.py (revision 59974) +++ command/register.py (working copy) @@ -8,20 +8,18 @@ __revision__ = "$Id$" import sys, os, string, urllib2, getpass, urlparse -import StringIO, ConfigParser +import StringIO -from distutils.core import Command +from distutils.core import PyPIRCCommand from distutils.errors import * -class register(Command): +class register(PyPIRCCommand): description = ("register the distribution with the Python package index") - - DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi' - user_options = [ ('repository=', 'r', - "url of repository [default: %s]"%DEFAULT_REPOSITORY), + "url of repository [default: %s]" % \ + PyPIRCCommand.DEFAULT_REPOSITORY), ('list-classifiers', None, 'list the valid Trove classifiers'), ('show-response', None, @@ -90,45 +88,33 @@ (code, result) = self.post_to_server(self.build_post_data('verify')) print 'Server response (%s): %s'%(code, result) + def send_metadata(self): ''' Send the metadata to the package index server. Well, do the following: 1. figure who the user is, and then 2. send the data as a Basic auth'ed POST. - - First we try to read the username/password from $HOME/.pypirc, - which is a ConfigParser-formatted file with a section - [server-login] containing username and password entries (both - in clear text). Eg: - - [server-login] - username: fred - password: sekrit - - Otherwise, to figure who the user is, we offer the user three - choices: - - 1. use existing login, + XXX + 1. use existing login, 2. register as a new user, or 3. set the password to a random string and email the user. ''' choice = 'x' username = password = '' + self.repository = repository = self.DEFAULT_REPOSITORY + realm = self.DEFAULT_REALM # see if we can short-cut and get the username/password from the # config - config = None - if os.environ.has_key('HOME'): - rc = os.path.join(os.environ['HOME'], '.pypirc') - if os.path.exists(rc): - print 'Using PyPI login from %s'%rc - config = ConfigParser.ConfigParser() - config.read(rc) - username = config.get('server-login', 'username') - password = config.get('server-login', 'password') - choice = '1' + config = self._read_pypirc() + if config is not None: + username = config['username'] + password = config['password'] + self.repository = repository = config['repository'] + realm = config['realm'] + choice = '1' # get the user's login info choices = '1 2 3 4'.split() @@ -154,9 +140,8 @@ # set up the authentication auth = urllib2.HTTPPasswordMgr() - host = urlparse.urlparse(self.repository)[1] - auth.add_password('pypi', host, username, password) - + host = urlparse.urlparse(repository)[1] + auth.add_password(realm, host, username, password) # send the info to the server and report the result code, result = self.post_to_server(self.build_post_data('submit'), auth) @@ -164,7 +149,6 @@ # possibly save the login if os.environ.has_key('HOME') and config is None and code == 200: - rc = os.path.join(os.environ['HOME'], '.pypirc') print 'I can store your PyPI login so future submissions will be faster.' print '(the login will be stored in %s)'%rc choice = 'X' @@ -173,14 +157,8 @@ if not choice: choice = 'n' if choice.lower() == 'y': - f = open(rc, 'w') - f.write('[server-login]\nusername:%s\npassword:%s\n'%( - username, password)) - f.close() - try: - os.chmod(rc, 0600) - except: - pass + self._store_pypirc(username, password) + elif choice == '2': data = {':action': 'user'} data['name'] = data['password'] = data['email'] = '' Index: command/upload.py =================================================================== --- command/upload.py (revision 59974) +++ command/upload.py (working copy) @@ -3,7 +3,7 @@ Implements the Distutils 'upload' subcommand (upload package to PyPI).""" from distutils.errors import * -from distutils.core import Command +from distutils.core import PyPIRCCommand from distutils.spawn import spawn from distutils import log from hashlib import md5 @@ -16,15 +16,14 @@ import urlparse import cStringIO as StringIO -class upload(Command): +class upload(PyPIRCCommand): description = "upload binary package to PyPI" - DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi' - user_options = [ ('repository=', 'r', - "url of repository [default: %s]" % DEFAULT_REPOSITORY), + "url of repository [default: %s]" % \ + PyPIRCCommand.DEFAULT_REPOSITORY), ('show-response', None, 'display full response text from server'), ('sign', 's', @@ -37,6 +36,7 @@ self.username = '' self.password = '' self.repository = '' + self.realm = '' self.show_response = 0 self.sign = False self.identity = None @@ -46,23 +46,15 @@ raise DistutilsOptionError( "Must use --sign for --identity to have meaning" ) - if os.environ.has_key('HOME'): - rc = os.path.join(os.environ['HOME'], '.pypirc') - if os.path.exists(rc): - self.announce('Using PyPI login from %s' % rc) - config = ConfigParser.ConfigParser({ - 'username':'', - 'password':'', - 'repository':''}) - config.read(rc) - if not self.repository: - self.repository = config.get('server-login', 'repository') - if not self.username: - self.username = config.get('server-login', 'username') - if not self.password: - self.password = config.get('server-login', 'password') - if not self.repository: - self.repository = self.DEFAULT_REPOSITORY + config = self._read_pypirc() + if config is not None: + self.username = config['username'] + self.password = config['password'] + self.repository = config['repository'] + self.realm = config['realm'] + else: + if not self.repository: + self.repository = self.DEFAULT_REPOSITORY def run(self): if not self.distribution.dist_files: