17db96d56Sopenharmony_ci"""distutils.command.register 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciImplements the Distutils 'register' command (register with the repository). 47db96d56Sopenharmony_ci""" 57db96d56Sopenharmony_ci 67db96d56Sopenharmony_ci# created 2002/10/21, Richard Jones 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_ciimport getpass 97db96d56Sopenharmony_ciimport io 107db96d56Sopenharmony_ciimport urllib.parse, urllib.request 117db96d56Sopenharmony_cifrom warnings import warn 127db96d56Sopenharmony_ci 137db96d56Sopenharmony_cifrom distutils.core import PyPIRCCommand 147db96d56Sopenharmony_cifrom distutils.errors import * 157db96d56Sopenharmony_cifrom distutils import log 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ciclass register(PyPIRCCommand): 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ci description = ("register the distribution with the Python package index") 207db96d56Sopenharmony_ci user_options = PyPIRCCommand.user_options + [ 217db96d56Sopenharmony_ci ('list-classifiers', None, 227db96d56Sopenharmony_ci 'list the valid Trove classifiers'), 237db96d56Sopenharmony_ci ('strict', None , 247db96d56Sopenharmony_ci 'Will stop the registering if the meta-data are not fully compliant') 257db96d56Sopenharmony_ci ] 267db96d56Sopenharmony_ci boolean_options = PyPIRCCommand.boolean_options + [ 277db96d56Sopenharmony_ci 'verify', 'list-classifiers', 'strict'] 287db96d56Sopenharmony_ci 297db96d56Sopenharmony_ci sub_commands = [('check', lambda self: True)] 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ci def initialize_options(self): 327db96d56Sopenharmony_ci PyPIRCCommand.initialize_options(self) 337db96d56Sopenharmony_ci self.list_classifiers = 0 347db96d56Sopenharmony_ci self.strict = 0 357db96d56Sopenharmony_ci 367db96d56Sopenharmony_ci def finalize_options(self): 377db96d56Sopenharmony_ci PyPIRCCommand.finalize_options(self) 387db96d56Sopenharmony_ci # setting options for the `check` subcommand 397db96d56Sopenharmony_ci check_options = {'strict': ('register', self.strict), 407db96d56Sopenharmony_ci 'restructuredtext': ('register', 1)} 417db96d56Sopenharmony_ci self.distribution.command_options['check'] = check_options 427db96d56Sopenharmony_ci 437db96d56Sopenharmony_ci def run(self): 447db96d56Sopenharmony_ci self.finalize_options() 457db96d56Sopenharmony_ci self._set_config() 467db96d56Sopenharmony_ci 477db96d56Sopenharmony_ci # Run sub commands 487db96d56Sopenharmony_ci for cmd_name in self.get_sub_commands(): 497db96d56Sopenharmony_ci self.run_command(cmd_name) 507db96d56Sopenharmony_ci 517db96d56Sopenharmony_ci if self.dry_run: 527db96d56Sopenharmony_ci self.verify_metadata() 537db96d56Sopenharmony_ci elif self.list_classifiers: 547db96d56Sopenharmony_ci self.classifiers() 557db96d56Sopenharmony_ci else: 567db96d56Sopenharmony_ci self.send_metadata() 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ci def check_metadata(self): 597db96d56Sopenharmony_ci """Deprecated API.""" 607db96d56Sopenharmony_ci warn("distutils.command.register.check_metadata is deprecated, \ 617db96d56Sopenharmony_ci use the check command instead", PendingDeprecationWarning) 627db96d56Sopenharmony_ci check = self.distribution.get_command_obj('check') 637db96d56Sopenharmony_ci check.ensure_finalized() 647db96d56Sopenharmony_ci check.strict = self.strict 657db96d56Sopenharmony_ci check.restructuredtext = 1 667db96d56Sopenharmony_ci check.run() 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci def _set_config(self): 697db96d56Sopenharmony_ci ''' Reads the configuration file and set attributes. 707db96d56Sopenharmony_ci ''' 717db96d56Sopenharmony_ci config = self._read_pypirc() 727db96d56Sopenharmony_ci if config != {}: 737db96d56Sopenharmony_ci self.username = config['username'] 747db96d56Sopenharmony_ci self.password = config['password'] 757db96d56Sopenharmony_ci self.repository = config['repository'] 767db96d56Sopenharmony_ci self.realm = config['realm'] 777db96d56Sopenharmony_ci self.has_config = True 787db96d56Sopenharmony_ci else: 797db96d56Sopenharmony_ci if self.repository not in ('pypi', self.DEFAULT_REPOSITORY): 807db96d56Sopenharmony_ci raise ValueError('%s not found in .pypirc' % self.repository) 817db96d56Sopenharmony_ci if self.repository == 'pypi': 827db96d56Sopenharmony_ci self.repository = self.DEFAULT_REPOSITORY 837db96d56Sopenharmony_ci self.has_config = False 847db96d56Sopenharmony_ci 857db96d56Sopenharmony_ci def classifiers(self): 867db96d56Sopenharmony_ci ''' Fetch the list of classifiers from the server. 877db96d56Sopenharmony_ci ''' 887db96d56Sopenharmony_ci url = self.repository+'?:action=list_classifiers' 897db96d56Sopenharmony_ci response = urllib.request.urlopen(url) 907db96d56Sopenharmony_ci log.info(self._read_pypi_response(response)) 917db96d56Sopenharmony_ci 927db96d56Sopenharmony_ci def verify_metadata(self): 937db96d56Sopenharmony_ci ''' Send the metadata to the package index server to be checked. 947db96d56Sopenharmony_ci ''' 957db96d56Sopenharmony_ci # send the info to the server and report the result 967db96d56Sopenharmony_ci (code, result) = self.post_to_server(self.build_post_data('verify')) 977db96d56Sopenharmony_ci log.info('Server response (%s): %s', code, result) 987db96d56Sopenharmony_ci 997db96d56Sopenharmony_ci def send_metadata(self): 1007db96d56Sopenharmony_ci ''' Send the metadata to the package index server. 1017db96d56Sopenharmony_ci 1027db96d56Sopenharmony_ci Well, do the following: 1037db96d56Sopenharmony_ci 1. figure who the user is, and then 1047db96d56Sopenharmony_ci 2. send the data as a Basic auth'ed POST. 1057db96d56Sopenharmony_ci 1067db96d56Sopenharmony_ci First we try to read the username/password from $HOME/.pypirc, 1077db96d56Sopenharmony_ci which is a ConfigParser-formatted file with a section 1087db96d56Sopenharmony_ci [distutils] containing username and password entries (both 1097db96d56Sopenharmony_ci in clear text). Eg: 1107db96d56Sopenharmony_ci 1117db96d56Sopenharmony_ci [distutils] 1127db96d56Sopenharmony_ci index-servers = 1137db96d56Sopenharmony_ci pypi 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ci [pypi] 1167db96d56Sopenharmony_ci username: fred 1177db96d56Sopenharmony_ci password: sekrit 1187db96d56Sopenharmony_ci 1197db96d56Sopenharmony_ci Otherwise, to figure who the user is, we offer the user three 1207db96d56Sopenharmony_ci choices: 1217db96d56Sopenharmony_ci 1227db96d56Sopenharmony_ci 1. use existing login, 1237db96d56Sopenharmony_ci 2. register as a new user, or 1247db96d56Sopenharmony_ci 3. set the password to a random string and email the user. 1257db96d56Sopenharmony_ci 1267db96d56Sopenharmony_ci ''' 1277db96d56Sopenharmony_ci # see if we can short-cut and get the username/password from the 1287db96d56Sopenharmony_ci # config 1297db96d56Sopenharmony_ci if self.has_config: 1307db96d56Sopenharmony_ci choice = '1' 1317db96d56Sopenharmony_ci username = self.username 1327db96d56Sopenharmony_ci password = self.password 1337db96d56Sopenharmony_ci else: 1347db96d56Sopenharmony_ci choice = 'x' 1357db96d56Sopenharmony_ci username = password = '' 1367db96d56Sopenharmony_ci 1377db96d56Sopenharmony_ci # get the user's login info 1387db96d56Sopenharmony_ci choices = '1 2 3 4'.split() 1397db96d56Sopenharmony_ci while choice not in choices: 1407db96d56Sopenharmony_ci self.announce('''\ 1417db96d56Sopenharmony_ciWe need to know who you are, so please choose either: 1427db96d56Sopenharmony_ci 1. use your existing login, 1437db96d56Sopenharmony_ci 2. register as a new user, 1447db96d56Sopenharmony_ci 3. have the server generate a new password for you (and email it to you), or 1457db96d56Sopenharmony_ci 4. quit 1467db96d56Sopenharmony_ciYour selection [default 1]: ''', log.INFO) 1477db96d56Sopenharmony_ci choice = input() 1487db96d56Sopenharmony_ci if not choice: 1497db96d56Sopenharmony_ci choice = '1' 1507db96d56Sopenharmony_ci elif choice not in choices: 1517db96d56Sopenharmony_ci print('Please choose one of the four options!') 1527db96d56Sopenharmony_ci 1537db96d56Sopenharmony_ci if choice == '1': 1547db96d56Sopenharmony_ci # get the username and password 1557db96d56Sopenharmony_ci while not username: 1567db96d56Sopenharmony_ci username = input('Username: ') 1577db96d56Sopenharmony_ci while not password: 1587db96d56Sopenharmony_ci password = getpass.getpass('Password: ') 1597db96d56Sopenharmony_ci 1607db96d56Sopenharmony_ci # set up the authentication 1617db96d56Sopenharmony_ci auth = urllib.request.HTTPPasswordMgr() 1627db96d56Sopenharmony_ci host = urllib.parse.urlparse(self.repository)[1] 1637db96d56Sopenharmony_ci auth.add_password(self.realm, host, username, password) 1647db96d56Sopenharmony_ci # send the info to the server and report the result 1657db96d56Sopenharmony_ci code, result = self.post_to_server(self.build_post_data('submit'), 1667db96d56Sopenharmony_ci auth) 1677db96d56Sopenharmony_ci self.announce('Server response (%s): %s' % (code, result), 1687db96d56Sopenharmony_ci log.INFO) 1697db96d56Sopenharmony_ci 1707db96d56Sopenharmony_ci # possibly save the login 1717db96d56Sopenharmony_ci if code == 200: 1727db96d56Sopenharmony_ci if self.has_config: 1737db96d56Sopenharmony_ci # sharing the password in the distribution instance 1747db96d56Sopenharmony_ci # so the upload command can reuse it 1757db96d56Sopenharmony_ci self.distribution.password = password 1767db96d56Sopenharmony_ci else: 1777db96d56Sopenharmony_ci self.announce(('I can store your PyPI login so future ' 1787db96d56Sopenharmony_ci 'submissions will be faster.'), log.INFO) 1797db96d56Sopenharmony_ci self.announce('(the login will be stored in %s)' % \ 1807db96d56Sopenharmony_ci self._get_rc_file(), log.INFO) 1817db96d56Sopenharmony_ci choice = 'X' 1827db96d56Sopenharmony_ci while choice.lower() not in 'yn': 1837db96d56Sopenharmony_ci choice = input('Save your login (y/N)?') 1847db96d56Sopenharmony_ci if not choice: 1857db96d56Sopenharmony_ci choice = 'n' 1867db96d56Sopenharmony_ci if choice.lower() == 'y': 1877db96d56Sopenharmony_ci self._store_pypirc(username, password) 1887db96d56Sopenharmony_ci 1897db96d56Sopenharmony_ci elif choice == '2': 1907db96d56Sopenharmony_ci data = {':action': 'user'} 1917db96d56Sopenharmony_ci data['name'] = data['password'] = data['email'] = '' 1927db96d56Sopenharmony_ci data['confirm'] = None 1937db96d56Sopenharmony_ci while not data['name']: 1947db96d56Sopenharmony_ci data['name'] = input('Username: ') 1957db96d56Sopenharmony_ci while data['password'] != data['confirm']: 1967db96d56Sopenharmony_ci while not data['password']: 1977db96d56Sopenharmony_ci data['password'] = getpass.getpass('Password: ') 1987db96d56Sopenharmony_ci while not data['confirm']: 1997db96d56Sopenharmony_ci data['confirm'] = getpass.getpass(' Confirm: ') 2007db96d56Sopenharmony_ci if data['password'] != data['confirm']: 2017db96d56Sopenharmony_ci data['password'] = '' 2027db96d56Sopenharmony_ci data['confirm'] = None 2037db96d56Sopenharmony_ci print("Password and confirm don't match!") 2047db96d56Sopenharmony_ci while not data['email']: 2057db96d56Sopenharmony_ci data['email'] = input(' EMail: ') 2067db96d56Sopenharmony_ci code, result = self.post_to_server(data) 2077db96d56Sopenharmony_ci if code != 200: 2087db96d56Sopenharmony_ci log.info('Server response (%s): %s', code, result) 2097db96d56Sopenharmony_ci else: 2107db96d56Sopenharmony_ci log.info('You will receive an email shortly.') 2117db96d56Sopenharmony_ci log.info(('Follow the instructions in it to ' 2127db96d56Sopenharmony_ci 'complete registration.')) 2137db96d56Sopenharmony_ci elif choice == '3': 2147db96d56Sopenharmony_ci data = {':action': 'password_reset'} 2157db96d56Sopenharmony_ci data['email'] = '' 2167db96d56Sopenharmony_ci while not data['email']: 2177db96d56Sopenharmony_ci data['email'] = input('Your email address: ') 2187db96d56Sopenharmony_ci code, result = self.post_to_server(data) 2197db96d56Sopenharmony_ci log.info('Server response (%s): %s', code, result) 2207db96d56Sopenharmony_ci 2217db96d56Sopenharmony_ci def build_post_data(self, action): 2227db96d56Sopenharmony_ci # figure the data to send - the metadata plus some additional 2237db96d56Sopenharmony_ci # information used by the package server 2247db96d56Sopenharmony_ci meta = self.distribution.metadata 2257db96d56Sopenharmony_ci data = { 2267db96d56Sopenharmony_ci ':action': action, 2277db96d56Sopenharmony_ci 'metadata_version' : '1.0', 2287db96d56Sopenharmony_ci 'name': meta.get_name(), 2297db96d56Sopenharmony_ci 'version': meta.get_version(), 2307db96d56Sopenharmony_ci 'summary': meta.get_description(), 2317db96d56Sopenharmony_ci 'home_page': meta.get_url(), 2327db96d56Sopenharmony_ci 'author': meta.get_contact(), 2337db96d56Sopenharmony_ci 'author_email': meta.get_contact_email(), 2347db96d56Sopenharmony_ci 'license': meta.get_licence(), 2357db96d56Sopenharmony_ci 'description': meta.get_long_description(), 2367db96d56Sopenharmony_ci 'keywords': meta.get_keywords(), 2377db96d56Sopenharmony_ci 'platform': meta.get_platforms(), 2387db96d56Sopenharmony_ci 'classifiers': meta.get_classifiers(), 2397db96d56Sopenharmony_ci 'download_url': meta.get_download_url(), 2407db96d56Sopenharmony_ci # PEP 314 2417db96d56Sopenharmony_ci 'provides': meta.get_provides(), 2427db96d56Sopenharmony_ci 'requires': meta.get_requires(), 2437db96d56Sopenharmony_ci 'obsoletes': meta.get_obsoletes(), 2447db96d56Sopenharmony_ci } 2457db96d56Sopenharmony_ci if data['provides'] or data['requires'] or data['obsoletes']: 2467db96d56Sopenharmony_ci data['metadata_version'] = '1.1' 2477db96d56Sopenharmony_ci return data 2487db96d56Sopenharmony_ci 2497db96d56Sopenharmony_ci def post_to_server(self, data, auth=None): 2507db96d56Sopenharmony_ci ''' Post a query to the server, and return a string response. 2517db96d56Sopenharmony_ci ''' 2527db96d56Sopenharmony_ci if 'name' in data: 2537db96d56Sopenharmony_ci self.announce('Registering %s to %s' % (data['name'], 2547db96d56Sopenharmony_ci self.repository), 2557db96d56Sopenharmony_ci log.INFO) 2567db96d56Sopenharmony_ci # Build up the MIME payload for the urllib2 POST data 2577db96d56Sopenharmony_ci boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' 2587db96d56Sopenharmony_ci sep_boundary = '\n--' + boundary 2597db96d56Sopenharmony_ci end_boundary = sep_boundary + '--' 2607db96d56Sopenharmony_ci body = io.StringIO() 2617db96d56Sopenharmony_ci for key, value in data.items(): 2627db96d56Sopenharmony_ci # handle multiple entries for the same name 2637db96d56Sopenharmony_ci if type(value) not in (type([]), type( () )): 2647db96d56Sopenharmony_ci value = [value] 2657db96d56Sopenharmony_ci for value in value: 2667db96d56Sopenharmony_ci value = str(value) 2677db96d56Sopenharmony_ci body.write(sep_boundary) 2687db96d56Sopenharmony_ci body.write('\nContent-Disposition: form-data; name="%s"'%key) 2697db96d56Sopenharmony_ci body.write("\n\n") 2707db96d56Sopenharmony_ci body.write(value) 2717db96d56Sopenharmony_ci if value and value[-1] == '\r': 2727db96d56Sopenharmony_ci body.write('\n') # write an extra newline (lurve Macs) 2737db96d56Sopenharmony_ci body.write(end_boundary) 2747db96d56Sopenharmony_ci body.write("\n") 2757db96d56Sopenharmony_ci body = body.getvalue().encode("utf-8") 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci # build the Request 2787db96d56Sopenharmony_ci headers = { 2797db96d56Sopenharmony_ci 'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary, 2807db96d56Sopenharmony_ci 'Content-length': str(len(body)) 2817db96d56Sopenharmony_ci } 2827db96d56Sopenharmony_ci req = urllib.request.Request(self.repository, body, headers) 2837db96d56Sopenharmony_ci 2847db96d56Sopenharmony_ci # handle HTTP and include the Basic Auth handler 2857db96d56Sopenharmony_ci opener = urllib.request.build_opener( 2867db96d56Sopenharmony_ci urllib.request.HTTPBasicAuthHandler(password_mgr=auth) 2877db96d56Sopenharmony_ci ) 2887db96d56Sopenharmony_ci data = '' 2897db96d56Sopenharmony_ci try: 2907db96d56Sopenharmony_ci result = opener.open(req) 2917db96d56Sopenharmony_ci except urllib.error.HTTPError as e: 2927db96d56Sopenharmony_ci if self.show_response: 2937db96d56Sopenharmony_ci data = e.fp.read() 2947db96d56Sopenharmony_ci result = e.code, e.msg 2957db96d56Sopenharmony_ci except urllib.error.URLError as e: 2967db96d56Sopenharmony_ci result = 500, str(e) 2977db96d56Sopenharmony_ci else: 2987db96d56Sopenharmony_ci if self.show_response: 2997db96d56Sopenharmony_ci data = self._read_pypi_response(result) 3007db96d56Sopenharmony_ci result = 200, 'OK' 3017db96d56Sopenharmony_ci if self.show_response: 3027db96d56Sopenharmony_ci msg = '\n'.join(('-' * 75, data, '-' * 75)) 3037db96d56Sopenharmony_ci self.announce(msg, log.INFO) 3047db96d56Sopenharmony_ci return result 305