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.
The format of the :file:`.pypirc` file is as follows::
- [distutils]
- index-servers =
- pypi
+ [distutils]
+ index-servers =
+ pypi
- [pypi]
- repository: <repository-url>
- username: <username>
- password: <password>
+ [pypi]
+ repository: <repository-url>
+ username: <username>
+ 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]
- 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: <repository-url>
- username: <username>
- password: <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: <username>
- password: <password>
+ [distutils]
+ index-servers =
+ pypi
+ 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
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
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.
*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).
-
(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
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'}
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")
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:
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
import sys
import os
import unittest
+import getpass
from distutils.command.register import register
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
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.
# 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()
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)
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):
('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)