Kaydet (Commit) 1a240fb9 authored tarafından Tarek Ziadé's avatar Tarek Ziadé

fixed #4394 make the storage of the password optional in .pypirc

üst 5b913e31
...@@ -8,17 +8,17 @@ The Python Package Index (PyPI) holds meta-data describing distributions ...@@ -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 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:: 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:: Distutils will respond with the following prompt::
running register running register
We need to know who you are, so please choose either: We need to know who you are, so please choose either:
1. use your existing login, 1. use your existing login,
2. register as a new user, 2. register as a new user,
3. have the server generate a new password for you (and email it to you), or 3. have the server generate a new password for you (and email it to you), or
4. quit 4. quit
Your selection [default 1]: Your selection [default 1]:
Note: if your username and password are saved locally, you will not see this Note: if your username and password are saved locally, you will not see this
menu. menu.
...@@ -55,40 +55,50 @@ The .pypirc file ...@@ -55,40 +55,50 @@ The .pypirc file
The format of the :file:`.pypirc` file is as follows:: The format of the :file:`.pypirc` file is as follows::
[distutils] [distutils]
index-servers = index-servers =
pypi pypi
[pypi] [pypi]
repository: <repository-url> repository: <repository-url>
username: <username> username: <username>
password: <password> password: <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] - *repository*, that defines the url of the PyPI server. Defaults to
index-servers = ``http://www.python.org/pypi``.
pypi - *username*, which is the registered username on the PyPI server.
other - *password*, that will be used to authenticate. If omitted the user
will be prompt to type it when needed.
[pypi] If you want to define another server a new section can be created and
repository: <repository-url> listed in the *index-servers* variable::
username: <username>
password: <password>
[other] [distutils]
repository: http://example.com/pypi index-servers =
username: <username> pypi
password: <password> other
The command can then be called with the -r option:: [pypi]
repository: <repository-url>
username: <username>
password: <password>
python setup.py register -r http://example.com/pypi [other]
repository: http://example.com/pypi
username: <username>
password: <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
...@@ -13,7 +13,7 @@ package data if the author of the package wishes to. The distutils command ...@@ -13,7 +13,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 The command is invoked immediately after building one or more distribution
files. For example, the command :: 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 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 PyPI. Note that these will be uploaded even if they are built using an earlier
...@@ -22,11 +22,14 @@ line for the invocation including the :command:`upload` command are uploaded. ...@@ -22,11 +22,14 @@ line for the invocation including the :command:`upload` command are uploaded.
The :command:`upload` command uses the username, password, and repository URL 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 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:: 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. See section :ref:`pypirc` for more on defining several servers.
...@@ -40,4 +43,3 @@ Other :command:`upload` options include :option:`--repository=<url>` or ...@@ -40,4 +43,3 @@ Other :command:`upload` options include :option:`--repository=<url>` or
*section* the name of the section in :file:`$HOME/.pypirc`, and *section* the name of the section in :file:`$HOME/.pypirc`, and
:option:`--show-response` (which displays the full response text from the PyPI :option:`--show-response` (which displays the full response text from the PyPI
server for help in debugging upload problems). server for help in debugging upload problems).
...@@ -120,6 +120,12 @@ changes, or look through the Subversion logs for all the details. ...@@ -120,6 +120,12 @@ changes, or look through the Subversion logs for all the details.
(Contributed by Gregory P. Smith.) (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 .. whole new modules get described in subsections here
......
...@@ -173,19 +173,23 @@ Your selection [default 1]: ''', log.INFO) ...@@ -173,19 +173,23 @@ Your selection [default 1]: ''', log.INFO)
log.INFO) log.INFO)
# possibly save the login # possibly save the login
if not self.has_config and code == 200: if code == 200:
self.announce(('I can store your PyPI login so future ' if self.has_config:
'submissions will be faster.'), log.INFO) # sharing the password in the distribution instance
self.announce('(the login will be stored in %s)' % \ # so the upload command can reuse it
self._get_rc_file(), log.INFO) self.distribution.password = password
else:
choice = 'X' self.announce(('I can store your PyPI login so future '
while choice.lower() not in 'yn': 'submissions will be faster.'), log.INFO)
choice = raw_input('Save your login (y/N)?') self.announce('(the login will be stored in %s)' % \
if not choice: self._get_rc_file(), log.INFO)
choice = 'n' choice = 'X'
if choice.lower() == 'y': while choice.lower() not in 'yn':
self._store_pypirc(username, password) choice = raw_input('Save your login (y/N)?')
if not choice:
choice = 'n'
if choice.lower() == 'y':
self._store_pypirc(username, password)
elif choice == '2': elif choice == '2':
data = {':action': 'user'} data = {':action': 'user'}
......
...@@ -50,6 +50,11 @@ class upload(PyPIRCCommand): ...@@ -50,6 +50,11 @@ class upload(PyPIRCCommand):
self.repository = config['repository'] self.repository = config['repository']
self.realm = config['realm'] 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): def run(self):
if not self.distribution.dist_files: if not self.distribution.dist_files:
raise DistutilsOptionError("No dist file created in earlier command") raise DistutilsOptionError("No dist file created in earlier command")
......
...@@ -82,12 +82,12 @@ class PyPIRCCommand(Command): ...@@ -82,12 +82,12 @@ class PyPIRCCommand(Command):
for server in _servers: for server in _servers:
current = {'server': server} current = {'server': server}
current['username'] = config.get(server, 'username') current['username'] = config.get(server, 'username')
current['password'] = config.get(server, 'password')
# optional params # optional params
for key, default in (('repository', for key, default in (('repository',
self.DEFAULT_REPOSITORY), self.DEFAULT_REPOSITORY),
('realm', self.DEFAULT_REALM)): ('realm', self.DEFAULT_REALM),
('password', None)):
if config.has_option(server, key): if config.has_option(server, key):
current[key] = config.get(server, key) current[key] = config.get(server, key)
else: else:
......
...@@ -206,6 +206,7 @@ Common commands: (see '--help-commands' for more) ...@@ -206,6 +206,7 @@ Common commands: (see '--help-commands' for more)
self.extra_path = None self.extra_path = None
self.scripts = None self.scripts = None
self.data_files = None self.data_files = None
self.password = ''
# And now initialize bookkeeping stuff that can't be supplied by # And now initialize bookkeeping stuff that can't be supplied by
# the caller at all. 'command_obj' maps command names to # the caller at all. 'command_obj' maps command names to
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import sys import sys
import os import os
import unittest import unittest
import getpass
from distutils.command.register import register from distutils.command.register import register
from distutils.core import Distribution from distutils.core import Distribution
...@@ -9,6 +10,26 @@ from distutils.core import Distribution ...@@ -9,6 +10,26 @@ from distutils.core import Distribution
from distutils.tests import support from distutils.tests import support
from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase
PYPIRC_NOPASSWORD = """\
[distutils]
index-servers =
server1
[server1]
username:me
"""
WANTED_PYPIRC = """\
[distutils]
index-servers =
pypi
[pypi]
username:tarek
password:password
"""
class RawInputs(object): class RawInputs(object):
"""Fakes user inputs.""" """Fakes user inputs."""
def __init__(self, *answers): def __init__(self, *answers):
...@@ -21,18 +42,33 @@ class RawInputs(object): ...@@ -21,18 +42,33 @@ class RawInputs(object):
finally: finally:
self.index += 1 self.index += 1
WANTED_PYPIRC = """\ class FakeServer(object):
[distutils] """Fakes a PyPI server"""
index-servers = def __init__(self):
pypi self.calls = []
[pypi] def __call__(self, *args):
username:tarek # we want to compare them, so let's store
password:xxx # something comparable
""" els = args[0].items()
els.sort()
self.calls.append(tuple(els))
return 200, 'OK'
class registerTestCase(PyPIRCCommandTestCase): 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): def test_create_pypirc(self):
# this test makes sure a .pypirc file # this test makes sure a .pypirc file
# is created when requested. # is created when requested.
...@@ -56,25 +92,11 @@ class registerTestCase(PyPIRCCommandTestCase): ...@@ -56,25 +92,11 @@ class registerTestCase(PyPIRCCommandTestCase):
# Here's what we are faking : # Here's what we are faking :
# use your existing login (choice 1.) # use your existing login (choice 1.)
# Username : 'tarek' # Username : 'tarek'
# Password : 'xxx' # Password : 'password'
# Save your login (y/N)? : 'y' # Save your login (y/N)? : 'y'
inputs = RawInputs('1', 'tarek', 'y') inputs = RawInputs('1', 'tarek', 'y')
from distutils.command import register as register_module from distutils.command import register as register_module
register_module.raw_input = inputs.__call__ register_module.raw_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 = args[0].items()
els.sort()
self.calls.append(tuple(els))
return 200, 'OK'
cmd.post_to_server = pypi_server = FakeServer() cmd.post_to_server = pypi_server = FakeServer()
...@@ -102,6 +124,24 @@ class registerTestCase(PyPIRCCommandTestCase): ...@@ -102,6 +124,24 @@ class registerTestCase(PyPIRCCommandTestCase):
self.assert_(len(pypi_server.calls), 2) self.assert_(len(pypi_server.calls), 2)
self.assert_(pypi_server.calls[0], pypi_server.calls[1]) 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(): def test_suite():
return unittest.makeSuite(registerTestCase) return unittest.makeSuite(registerTestCase)
......
...@@ -9,6 +9,17 @@ from distutils.core import Distribution ...@@ -9,6 +9,17 @@ from distutils.core import Distribution
from distutils.tests import support from distutils.tests import support
from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase
PYPIRC_NOPASSWORD = """\
[distutils]
index-servers =
server1
[server1]
username:me
"""
class uploadTestCase(PyPIRCCommandTestCase): class uploadTestCase(PyPIRCCommandTestCase):
def test_finalize_options(self): def test_finalize_options(self):
...@@ -26,6 +37,24 @@ class uploadTestCase(PyPIRCCommandTestCase): ...@@ -26,6 +37,24 @@ class uploadTestCase(PyPIRCCommandTestCase):
('repository', 'http://pypi.python.org/pypi')): ('repository', 'http://pypi.python.org/pypi')):
self.assertEquals(getattr(cmd, attr), waited) 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(): def test_suite():
return unittest.makeSuite(uploadTestCase) return unittest.makeSuite(uploadTestCase)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment