Path: blob/main/test/lib/python3.9/site-packages/setuptools/_distutils/command/register.py
4804 views
"""distutils.command.register12Implements the Distutils 'register' command (register with the repository).3"""45# created 2002/10/21, Richard Jones67import getpass8import io9import urllib.parse, urllib.request10from warnings import warn1112from distutils.core import PyPIRCCommand13from distutils.errors import *14from distutils import log1516class register(PyPIRCCommand):1718description = ("register the distribution with the Python package index")19user_options = PyPIRCCommand.user_options + [20('list-classifiers', None,21'list the valid Trove classifiers'),22('strict', None ,23'Will stop the registering if the meta-data are not fully compliant')24]25boolean_options = PyPIRCCommand.boolean_options + [26'verify', 'list-classifiers', 'strict']2728sub_commands = [('check', lambda self: True)]2930def initialize_options(self):31PyPIRCCommand.initialize_options(self)32self.list_classifiers = 033self.strict = 03435def finalize_options(self):36PyPIRCCommand.finalize_options(self)37# setting options for the `check` subcommand38check_options = {'strict': ('register', self.strict),39'restructuredtext': ('register', 1)}40self.distribution.command_options['check'] = check_options4142def run(self):43self.finalize_options()44self._set_config()4546# Run sub commands47for cmd_name in self.get_sub_commands():48self.run_command(cmd_name)4950if self.dry_run:51self.verify_metadata()52elif self.list_classifiers:53self.classifiers()54else:55self.send_metadata()5657def check_metadata(self):58"""Deprecated API."""59warn("distutils.command.register.check_metadata is deprecated, \60use the check command instead", PendingDeprecationWarning)61check = self.distribution.get_command_obj('check')62check.ensure_finalized()63check.strict = self.strict64check.restructuredtext = 165check.run()6667def _set_config(self):68''' Reads the configuration file and set attributes.69'''70config = self._read_pypirc()71if config != {}:72self.username = config['username']73self.password = config['password']74self.repository = config['repository']75self.realm = config['realm']76self.has_config = True77else:78if self.repository not in ('pypi', self.DEFAULT_REPOSITORY):79raise ValueError('%s not found in .pypirc' % self.repository)80if self.repository == 'pypi':81self.repository = self.DEFAULT_REPOSITORY82self.has_config = False8384def classifiers(self):85''' Fetch the list of classifiers from the server.86'''87url = self.repository+'?:action=list_classifiers'88response = urllib.request.urlopen(url)89log.info(self._read_pypi_response(response))9091def verify_metadata(self):92''' Send the metadata to the package index server to be checked.93'''94# send the info to the server and report the result95(code, result) = self.post_to_server(self.build_post_data('verify'))96log.info('Server response (%s): %s', code, result)9798def send_metadata(self):99''' Send the metadata to the package index server.100101Well, do the following:1021. figure who the user is, and then1032. send the data as a Basic auth'ed POST.104105First we try to read the username/password from $HOME/.pypirc,106which is a ConfigParser-formatted file with a section107[distutils] containing username and password entries (both108in clear text). Eg:109110[distutils]111index-servers =112pypi113114[pypi]115username: fred116password: sekrit117118Otherwise, to figure who the user is, we offer the user three119choices:1201211. use existing login,1222. register as a new user, or1233. set the password to a random string and email the user.124125'''126# see if we can short-cut and get the username/password from the127# config128if self.has_config:129choice = '1'130username = self.username131password = self.password132else:133choice = 'x'134username = password = ''135136# get the user's login info137choices = '1 2 3 4'.split()138while choice not in choices:139self.announce('''\140We need to know who you are, so please choose either:1411. use your existing login,1422. register as a new user,1433. have the server generate a new password for you (and email it to you), or1444. quit145Your selection [default 1]: ''', log.INFO)146choice = input()147if not choice:148choice = '1'149elif choice not in choices:150print('Please choose one of the four options!')151152if choice == '1':153# get the username and password154while not username:155username = input('Username: ')156while not password:157password = getpass.getpass('Password: ')158159# set up the authentication160auth = urllib.request.HTTPPasswordMgr()161host = urllib.parse.urlparse(self.repository)[1]162auth.add_password(self.realm, host, username, password)163# send the info to the server and report the result164code, result = self.post_to_server(self.build_post_data('submit'),165auth)166self.announce('Server response (%s): %s' % (code, result),167log.INFO)168169# possibly save the login170if code == 200:171if self.has_config:172# sharing the password in the distribution instance173# so the upload command can reuse it174self.distribution.password = password175else:176self.announce(('I can store your PyPI login so future '177'submissions will be faster.'), log.INFO)178self.announce('(the login will be stored in %s)' % \179self._get_rc_file(), log.INFO)180choice = 'X'181while choice.lower() not in 'yn':182choice = input('Save your login (y/N)?')183if not choice:184choice = 'n'185if choice.lower() == 'y':186self._store_pypirc(username, password)187188elif choice == '2':189data = {':action': 'user'}190data['name'] = data['password'] = data['email'] = ''191data['confirm'] = None192while not data['name']:193data['name'] = input('Username: ')194while data['password'] != data['confirm']:195while not data['password']:196data['password'] = getpass.getpass('Password: ')197while not data['confirm']:198data['confirm'] = getpass.getpass(' Confirm: ')199if data['password'] != data['confirm']:200data['password'] = ''201data['confirm'] = None202print("Password and confirm don't match!")203while not data['email']:204data['email'] = input(' EMail: ')205code, result = self.post_to_server(data)206if code != 200:207log.info('Server response (%s): %s', code, result)208else:209log.info('You will receive an email shortly.')210log.info(('Follow the instructions in it to '211'complete registration.'))212elif choice == '3':213data = {':action': 'password_reset'}214data['email'] = ''215while not data['email']:216data['email'] = input('Your email address: ')217code, result = self.post_to_server(data)218log.info('Server response (%s): %s', code, result)219220def build_post_data(self, action):221# figure the data to send - the metadata plus some additional222# information used by the package server223meta = self.distribution.metadata224data = {225':action': action,226'metadata_version' : '1.0',227'name': meta.get_name(),228'version': meta.get_version(),229'summary': meta.get_description(),230'home_page': meta.get_url(),231'author': meta.get_contact(),232'author_email': meta.get_contact_email(),233'license': meta.get_licence(),234'description': meta.get_long_description(),235'keywords': meta.get_keywords(),236'platform': meta.get_platforms(),237'classifiers': meta.get_classifiers(),238'download_url': meta.get_download_url(),239# PEP 314240'provides': meta.get_provides(),241'requires': meta.get_requires(),242'obsoletes': meta.get_obsoletes(),243}244if data['provides'] or data['requires'] or data['obsoletes']:245data['metadata_version'] = '1.1'246return data247248def post_to_server(self, data, auth=None):249''' Post a query to the server, and return a string response.250'''251if 'name' in data:252self.announce('Registering %s to %s' % (data['name'],253self.repository),254log.INFO)255# Build up the MIME payload for the urllib2 POST data256boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'257sep_boundary = '\n--' + boundary258end_boundary = sep_boundary + '--'259body = io.StringIO()260for key, value in data.items():261# handle multiple entries for the same name262if type(value) not in (type([]), type( () )):263value = [value]264for value in value:265value = str(value)266body.write(sep_boundary)267body.write('\nContent-Disposition: form-data; name="%s"'%key)268body.write("\n\n")269body.write(value)270if value and value[-1] == '\r':271body.write('\n') # write an extra newline (lurve Macs)272body.write(end_boundary)273body.write("\n")274body = body.getvalue().encode("utf-8")275276# build the Request277headers = {278'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary,279'Content-length': str(len(body))280}281req = urllib.request.Request(self.repository, body, headers)282283# handle HTTP and include the Basic Auth handler284opener = urllib.request.build_opener(285urllib.request.HTTPBasicAuthHandler(password_mgr=auth)286)287data = ''288try:289result = opener.open(req)290except urllib.error.HTTPError as e:291if self.show_response:292data = e.fp.read()293result = e.code, e.msg294except urllib.error.URLError as e:295result = 500, str(e)296else:297if self.show_response:298data = self._read_pypi_response(result)299result = 200, 'OK'300if self.show_response:301msg = '\n'.join(('-' * 75, data, '-' * 75))302self.announce(msg, log.INFO)303return result304305306