From 13f7c3b6cad28ee57390c7650e1bf644f0b3943f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarek=20Ziad=C3=A9?= Date: Fri, 9 Jan 2009 00:15:45 +0000 Subject: Merged revisions 68415 via svnmerge from svn+ssh://pythondev@svn.python.org/python/trunk ........ r68415 | tarek.ziade | 2009-01-09 00:56:31 +0100 (Fri, 09 Jan 2009) | 1 line fixed #4394 make the storage of the password optional in .pypirc ........ --- Doc/distutils/packageindex.rst | 76 +++++++++++++++++------------- Doc/distutils/uploading.rst | 10 ++-- Doc/whatsnew/2.7.rst | 6 +++ Lib/distutils/command/register.py | 30 ++++++------ Lib/distutils/command/upload.py | 5 ++ Lib/distutils/config.py | 4 +- Lib/distutils/dist.py | 1 + Lib/distutils/tests/test_register.py | 91 ++++++++++++++++++++++++++---------- Lib/distutils/tests/test_upload.py | 29 ++++++++++++ 9 files changed, 175 insertions(+), 77 deletions(-) diff --git a/Doc/distutils/packageindex.rst b/Doc/distutils/packageindex.rst index 3715c82..c4cbf88 100644 --- a/Doc/distutils/packageindex.rst +++ b/Doc/distutils/packageindex.rst @@ -8,17 +8,17 @@ The Python Package Index (PyPI) holds meta-data describing distributions packaged with distutils. The distutils command :command:`register` is used to submit your distribution's meta-data to the index. It is invoked as follows:: - python setup.py register + python setup.py register Distutils will respond with the following prompt:: - running register - We need to know who you are, so please choose either: - 1. use your existing login, - 2. register as a new user, - 3. have the server generate a new password for you (and email it to you), or - 4. quit - Your selection [default 1]: + running register + We need to know who you are, so please choose either: + 1. use your existing login, + 2. register as a new user, + 3. have the server generate a new password for you (and email it to you), or + 4. quit + Your selection [default 1]: Note: if your username and password are saved locally, you will not see this menu. @@ -55,40 +55,50 @@ The .pypirc file The format of the :file:`.pypirc` file is as follows:: - [distutils] - index-servers = - pypi + [distutils] + index-servers = + pypi - [pypi] - repository: - username: - password: + [pypi] + repository: + username: + password: -*repository* can be omitted and defaults to ``http://www.python.org/pypi``. +The *distutils* section defines a *index-servers* variable that lists the +name of all sections describing a repository. -If you want to define another server a new section can be created:: +Each section describing a repository defines three variables: - [distutils] - index-servers = - pypi - other +- *repository*, that defines the url of the PyPI server. Defaults to + ``http://www.python.org/pypi``. +- *username*, which is the registered username on the PyPI server. +- *password*, that will be used to authenticate. If omitted the user + will be prompt to type it when needed. - [pypi] - repository: - username: - password: +If you want to define another server a new section can be created and +listed in the *index-servers* variable:: - [other] - repository: http://example.com/pypi - username: - password: + [distutils] + index-servers = + pypi + other -The command can then be called with the -r option:: + [pypi] + repository: + username: + password: - python setup.py register -r http://example.com/pypi + [other] + repository: http://example.com/pypi + username: + password: -Or even with the section name:: +:command:`register` can then be called with the -r option to point the +repository to work with:: - python setup.py register -r other + python setup.py register -r http://example.com/pypi +The name of the section that describes the repository may also be used +for conveniency:: + python setup.py register -r other diff --git a/Doc/distutils/uploading.rst b/Doc/distutils/uploading.rst index e3aa7c3..36e7b8f 100644 --- a/Doc/distutils/uploading.rst +++ b/Doc/distutils/uploading.rst @@ -11,7 +11,7 @@ package data if the author of the package wishes to. The distutils command The command is invoked immediately after building one or more distribution files. For example, the command :: - python setup.py sdist bdist_wininst upload + python setup.py sdist bdist_wininst upload will cause the source distribution and the Windows installer to be uploaded to PyPI. Note that these will be uploaded even if they are built using an earlier @@ -20,11 +20,14 @@ line for the invocation including the :command:`upload` command are uploaded. The :command:`upload` command uses the username, password, and repository URL from the :file:`$HOME/.pypirc` file (see section :ref:`pypirc` for more on this -file). +file). If a :command:`register` command was previously called in the same command, +and if the password was entered in the prompt, :command:`upload` will reuse the +entered password. This is useful if you do not want to store a clear text +password in the :file:`$HOME/.pypirc` file. You can specify another PyPI server with the :option:`--repository=*url*` option:: - python setup.py sdist bdist_wininst upload -r http://example.com/pypi + python setup.py sdist bdist_wininst upload -r http://example.com/pypi See section :ref:`pypirc` for more on defining several servers. @@ -38,4 +41,3 @@ Other :command:`upload` options include :option:`--repository=` or *section* the name of the section in :file:`$HOME/.pypirc`, and :option:`--show-response` (which displays the full response text from the PyPI server for help in debugging upload problems). - diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index da17620..24d3a86 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -120,6 +120,12 @@ changes, or look through the Subversion logs for all the details. (Contributed by Gregory P. Smith.) +* It is not mandatory anymore to store clear text passwords in the + :file:`.pypirc` file when registering and uploading packages to PyPI. As + long as the username is present in that file, the :mod:`distutils` package + will prompt for the password if not present. + (Added by tarek, with the initial contribution of Nathan Van Gheem; + :issue:`4394`.) .. ====================================================================== .. whole new modules get described in subsections here diff --git a/Lib/distutils/command/register.py b/Lib/distutils/command/register.py index 30e9a37..c271b18 100644 --- a/Lib/distutils/command/register.py +++ b/Lib/distutils/command/register.py @@ -174,19 +174,23 @@ Your selection [default 1]: ''', log.INFO) log.INFO) # possibly save the login - if not self.has_config and code == 200: - self.announce(('I can store your PyPI login so future ' - 'submissions will be faster.'), log.INFO) - self.announce('(the login will be stored in %s)' % \ - self._get_rc_file(), log.INFO) - - choice = 'X' - while choice.lower() not in 'yn': - choice = input('Save your login (y/N)?') - if not choice: - choice = 'n' - if choice.lower() == 'y': - self._store_pypirc(username, password) + if code == 200: + if self.has_config: + # sharing the password in the distribution instance + # so the upload command can reuse it + self.distribution.password = password + else: + self.announce(('I can store your PyPI login so future ' + 'submissions will be faster.'), log.INFO) + self.announce('(the login will be stored in %s)' % \ + self._get_rc_file(), log.INFO) + choice = 'X' + while choice.lower() not in 'yn': + choice = input('Save your login (y/N)?') + if not choice: + choice = 'n' + if choice.lower() == 'y': + self._store_pypirc(username, password) elif choice == '2': data = {':action': 'user'} diff --git a/Lib/distutils/command/upload.py b/Lib/distutils/command/upload.py index 7ba7f58..020e860 100644 --- a/Lib/distutils/command/upload.py +++ b/Lib/distutils/command/upload.py @@ -48,6 +48,11 @@ class upload(PyPIRCCommand): self.repository = config['repository'] self.realm = config['realm'] + # getting the password from the distribution + # if previously set by the register command + if not self.password and self.distribution.password: + self.password = self.distribution.password + def run(self): if not self.distribution.dist_files: raise DistutilsOptionError("No dist file created in earlier command") diff --git a/Lib/distutils/config.py b/Lib/distutils/config.py index 73f3260..5b625f3 100644 --- a/Lib/distutils/config.py +++ b/Lib/distutils/config.py @@ -82,12 +82,12 @@ class PyPIRCCommand(Command): 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)): + ('realm', self.DEFAULT_REALM), + ('password', None)): if config.has_option(server, key): current[key] = config.get(server, key) else: diff --git a/Lib/distutils/dist.py b/Lib/distutils/dist.py index 7903c2a..6c4b4af 100644 --- a/Lib/distutils/dist.py +++ b/Lib/distutils/dist.py @@ -199,6 +199,7 @@ Common commands: (see '--help-commands' for more) self.extra_path = None self.scripts = None self.data_files = None + self.password = '' # And now initialize bookkeeping stuff that can't be supplied by # the caller at all. 'command_obj' maps command names to diff --git a/Lib/distutils/tests/test_register.py b/Lib/distutils/tests/test_register.py index 021b3ea..8826e90 100644 --- a/Lib/distutils/tests/test_register.py +++ b/Lib/distutils/tests/test_register.py @@ -2,6 +2,7 @@ import sys import os import unittest +import getpass from distutils.command.register import register from distutils.core import Distribution @@ -9,7 +10,27 @@ from distutils.core import Distribution from distutils.tests import support from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase -class RawInputs(object): +PYPIRC_NOPASSWORD = """\ +[distutils] + +index-servers = + server1 + +[server1] +username:me +""" + +WANTED_PYPIRC = """\ +[distutils] +index-servers = + pypi + +[pypi] +username:tarek +password:password +""" + +class Inputs(object): """Fakes user inputs.""" def __init__(self, *answers): self.answers = answers @@ -21,18 +42,33 @@ class RawInputs(object): finally: self.index += 1 -WANTED_PYPIRC = """\ -[distutils] -index-servers = - pypi +class FakeServer(object): + """Fakes a PyPI server""" + def __init__(self): + self.calls = [] -[pypi] -username:tarek -password:xxx -""" + def __call__(self, *args): + # we want to compare them, so let's store + # something comparable + els = list(args[0].items()) + els.sort() + self.calls.append(tuple(els)) + return 200, 'OK' class registerTestCase(PyPIRCCommandTestCase): + def setUp(self): + PyPIRCCommandTestCase.setUp(self) + # patching the password prompt + self._old_getpass = getpass.getpass + def _getpass(prompt): + return 'password' + getpass.getpass = _getpass + + def tearDown(self): + getpass.getpass = self._old_getpass + PyPIRCCommandTestCase.tearDown(self) + def test_create_pypirc(self): # this test makes sure a .pypirc file # is created when requested. @@ -50,30 +86,17 @@ class registerTestCase(PyPIRCCommandTestCase): # we shouldn't have a .pypirc file yet self.assert_(not os.path.exists(self.rc)) - # patching raw_input and getpass.getpass + # patching input and getpass.getpass # so register gets happy # # Here's what we are faking : # use your existing login (choice 1.) # Username : 'tarek' - # Password : 'xxx' + # Password : 'password' # Save your login (y/N)? : 'y' - inputs = RawInputs('1', 'tarek', 'y') + inputs = Inputs('1', 'tarek', 'y') from distutils.command import register as register_module register_module.input = inputs.__call__ - def _getpass(prompt): - return 'xxx' - register_module.getpass.getpass = _getpass - class FakeServer(object): - def __init__(self): - self.calls = [] - - def __call__(self, *args): - # we want to compare them, so let's store - # something comparable - els = sorted(args[0].items()) - self.calls.append(tuple(els)) - return 200, 'OK' cmd.post_to_server = pypi_server = FakeServer() @@ -101,6 +124,24 @@ class registerTestCase(PyPIRCCommandTestCase): self.assert_(len(pypi_server.calls), 2) self.assert_(pypi_server.calls[0], pypi_server.calls[1]) + def test_password_not_in_file(self): + + f = open(self.rc, 'w') + f.write(PYPIRC_NOPASSWORD) + f.close() + + dist = Distribution() + cmd = register(dist) + cmd.post_to_server = FakeServer() + + cmd._set_config() + cmd.finalize_options() + cmd.send_metadata() + + # dist.password should be set + # therefore used afterwards by other commands + self.assertEquals(dist.password, 'password') + def test_suite(): return unittest.makeSuite(registerTestCase) diff --git a/Lib/distutils/tests/test_upload.py b/Lib/distutils/tests/test_upload.py index b05ab1f..3f8ca6d 100644 --- a/Lib/distutils/tests/test_upload.py +++ b/Lib/distutils/tests/test_upload.py @@ -9,6 +9,17 @@ from distutils.core import Distribution from distutils.tests import support from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase +PYPIRC_NOPASSWORD = """\ +[distutils] + +index-servers = + server1 + +[server1] +username:me +""" + + class uploadTestCase(PyPIRCCommandTestCase): def test_finalize_options(self): @@ -26,6 +37,24 @@ class uploadTestCase(PyPIRCCommandTestCase): ('repository', 'http://pypi.python.org/pypi')): self.assertEquals(getattr(cmd, attr), waited) + def test_saved_password(self): + # file with no password + f = open(self.rc, 'w') + f.write(PYPIRC_NOPASSWORD) + f.close() + + # make sure it passes + dist = Distribution() + cmd = upload(dist) + cmd.finalize_options() + self.assertEquals(cmd.password, None) + + # make sure we get it as well, if another command + # initialized it at the dist level + dist.password = 'xxx' + cmd = upload(dist) + cmd.finalize_options() + self.assertEquals(cmd.password, 'xxx') def test_suite(): return unittest.makeSuite(uploadTestCase) -- cgit v0.12