| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  | """distutils.command.register
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Implements the Distutils 'register' command (register with the repository). | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # created 2002/10/21, Richard Jones | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | __revision__ = "$Id$" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-02-23 17:40:11 +00:00
										 |  |  | import os, string, urllib2, getpass, urlparse | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  | import StringIO, ConfigParser | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from distutils.core import Command | 
					
						
							|  |  |  | from distutils.errors import * | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class register(Command): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2003-03-03 18:26:01 +00:00
										 |  |  |     description = ("register the distribution with the Python package index") | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-07-25 16:24:23 +00:00
										 |  |  |     DEFAULT_REPOSITORY = 'http://pypi.python.org/pypi' | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     user_options = [ | 
					
						
							|  |  |  |         ('repository=', 'r', | 
					
						
							|  |  |  |          "url of repository [default: %s]"%DEFAULT_REPOSITORY), | 
					
						
							|  |  |  |         ('list-classifiers', None, | 
					
						
							|  |  |  |          'list the valid Trove classifiers'), | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |         ('show-response', None, | 
					
						
							|  |  |  |          'display full response text from server'), | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |         ] | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |     boolean_options = ['verify', 'show-response', 'list-classifiers'] | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def initialize_options(self): | 
					
						
							|  |  |  |         self.repository = None | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |         self.show_response = 0 | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |         self.list_classifiers = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def finalize_options(self): | 
					
						
							|  |  |  |         if self.repository is None: | 
					
						
							|  |  |  |             self.repository = self.DEFAULT_REPOSITORY | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self): | 
					
						
							|  |  |  |         self.check_metadata() | 
					
						
							| 
									
										
										
										
											2003-04-09 12:35:51 +00:00
										 |  |  |         if self.dry_run: | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |             self.verify_metadata() | 
					
						
							|  |  |  |         elif self.list_classifiers: | 
					
						
							|  |  |  |             self.classifiers() | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.send_metadata() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_metadata(self): | 
					
						
							|  |  |  |         """Ensure that all required elements of meta-data (name, version,
 | 
					
						
							|  |  |  |            URL, (author and author_email) or (maintainer and | 
					
						
							|  |  |  |            maintainer_email)) are supplied by the Distribution object; warn if | 
					
						
							|  |  |  |            any are missing. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         metadata = self.distribution.metadata | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         missing = [] | 
					
						
							|  |  |  |         for attr in ('name', 'version', 'url'): | 
					
						
							|  |  |  |             if not (hasattr(metadata, attr) and getattr(metadata, attr)): | 
					
						
							|  |  |  |                 missing.append(attr) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if missing: | 
					
						
							|  |  |  |             self.warn("missing required meta-data: " + | 
					
						
							|  |  |  |                       string.join(missing, ", ")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if metadata.author: | 
					
						
							|  |  |  |             if not metadata.author_email: | 
					
						
							|  |  |  |                 self.warn("missing meta-data: if 'author' supplied, " + | 
					
						
							|  |  |  |                           "'author_email' must be supplied too") | 
					
						
							|  |  |  |         elif metadata.maintainer: | 
					
						
							|  |  |  |             if not metadata.maintainer_email: | 
					
						
							|  |  |  |                 self.warn("missing meta-data: if 'maintainer' supplied, " + | 
					
						
							|  |  |  |                           "'maintainer_email' must be supplied too") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.warn("missing meta-data: either (author and author_email) " + | 
					
						
							|  |  |  |                       "or (maintainer and maintainer_email) " + | 
					
						
							|  |  |  |                       "must be supplied") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def classifiers(self): | 
					
						
							|  |  |  |         ''' Fetch the list of classifiers from the server.
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         response = urllib2.urlopen(self.repository+'?:action=list_classifiers') | 
					
						
							|  |  |  |         print response.read() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def verify_metadata(self): | 
					
						
							|  |  |  |         ''' Send the metadata to the package index server to be checked.
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         # send the info to the server and report the result | 
					
						
							|  |  |  |         (code, result) = self.post_to_server(self.build_post_data('verify')) | 
					
						
							|  |  |  |         print 'Server response (%s): %s'%(code, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def send_metadata(self): | 
					
						
							|  |  |  |         ''' Send the metadata to the package index server.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             Well, do the following: | 
					
						
							|  |  |  |             1. figure who the user is, and then | 
					
						
							|  |  |  |             2. send the data as a Basic auth'ed POST. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             First we try to read the username/password from $HOME/.pypirc, | 
					
						
							|  |  |  |             which is a ConfigParser-formatted file with a section | 
					
						
							|  |  |  |             [server-login] containing username and password entries (both | 
					
						
							|  |  |  |             in clear text). Eg: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 [server-login] | 
					
						
							|  |  |  |                 username: fred | 
					
						
							|  |  |  |                 password: sekrit | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             Otherwise, to figure who the user is, we offer the user three | 
					
						
							|  |  |  |             choices: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |              1. use existing login, | 
					
						
							|  |  |  |              2. register as a new user, or | 
					
						
							|  |  |  |              3. set the password to a random string and email the user. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  |         choice = 'x' | 
					
						
							|  |  |  |         username = password = '' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # see if we can short-cut and get the username/password from the | 
					
						
							|  |  |  |         # config | 
					
						
							|  |  |  |         config = None | 
					
						
							| 
									
										
										
										
											2008-02-21 18:18:37 +00:00
										 |  |  |         if 'HOME' in os.environ: | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |             rc = os.path.join(os.environ['HOME'], '.pypirc') | 
					
						
							|  |  |  |             if os.path.exists(rc): | 
					
						
							|  |  |  |                 print 'Using PyPI login from %s'%rc | 
					
						
							|  |  |  |                 config = ConfigParser.ConfigParser() | 
					
						
							|  |  |  |                 config.read(rc) | 
					
						
							|  |  |  |                 username = config.get('server-login', 'username') | 
					
						
							|  |  |  |                 password = config.get('server-login', 'password') | 
					
						
							|  |  |  |                 choice = '1' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # get the user's login info | 
					
						
							|  |  |  |         choices = '1 2 3 4'.split() | 
					
						
							|  |  |  |         while choice not in choices: | 
					
						
							|  |  |  |             print '''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]: ''',
 | 
					
						
							|  |  |  |             choice = raw_input() | 
					
						
							|  |  |  |             if not choice: | 
					
						
							|  |  |  |                 choice = '1' | 
					
						
							|  |  |  |             elif choice not in choices: | 
					
						
							|  |  |  |                 print 'Please choose one of the four options!' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if choice == '1': | 
					
						
							|  |  |  |             # get the username and password | 
					
						
							|  |  |  |             while not username: | 
					
						
							|  |  |  |                 username = raw_input('Username: ') | 
					
						
							|  |  |  |             while not password: | 
					
						
							|  |  |  |                 password = getpass.getpass('Password: ') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # set up the authentication | 
					
						
							|  |  |  |             auth = urllib2.HTTPPasswordMgr() | 
					
						
							|  |  |  |             host = urlparse.urlparse(self.repository)[1] | 
					
						
							|  |  |  |             auth.add_password('pypi', host, username, password) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # send the info to the server and report the result | 
					
						
							|  |  |  |             code, result = self.post_to_server(self.build_post_data('submit'), | 
					
						
							|  |  |  |                 auth) | 
					
						
							|  |  |  |             print 'Server response (%s): %s'%(code, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # possibly save the login | 
					
						
							| 
									
										
										
										
											2008-02-21 18:18:37 +00:00
										 |  |  |             if 'HOME' in os.environ and config is None and code == 200: | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |                 rc = os.path.join(os.environ['HOME'], '.pypirc') | 
					
						
							|  |  |  |                 print 'I can store your PyPI login so future submissions will be faster.' | 
					
						
							|  |  |  |                 print '(the login will be stored in %s)'%rc | 
					
						
							|  |  |  |                 choice = 'X' | 
					
						
							|  |  |  |                 while choice.lower() not in 'yn': | 
					
						
							|  |  |  |                     choice = raw_input('Save your login (y/N)?') | 
					
						
							|  |  |  |                     if not choice: | 
					
						
							|  |  |  |                         choice = 'n' | 
					
						
							|  |  |  |                 if choice.lower() == 'y': | 
					
						
							|  |  |  |                     f = open(rc, 'w') | 
					
						
							|  |  |  |                     f.write('[server-login]\nusername:%s\npassword:%s\n'%( | 
					
						
							|  |  |  |                         username, password)) | 
					
						
							|  |  |  |                     f.close() | 
					
						
							|  |  |  |                     try: | 
					
						
							|  |  |  |                         os.chmod(rc, 0600) | 
					
						
							|  |  |  |                     except: | 
					
						
							|  |  |  |                         pass | 
					
						
							|  |  |  |         elif choice == '2': | 
					
						
							|  |  |  |             data = {':action': 'user'} | 
					
						
							|  |  |  |             data['name'] = data['password'] = data['email'] = '' | 
					
						
							|  |  |  |             data['confirm'] = None | 
					
						
							|  |  |  |             while not data['name']: | 
					
						
							|  |  |  |                 data['name'] = raw_input('Username: ') | 
					
						
							|  |  |  |             while data['password'] != data['confirm']: | 
					
						
							|  |  |  |                 while not data['password']: | 
					
						
							|  |  |  |                     data['password'] = getpass.getpass('Password: ') | 
					
						
							|  |  |  |                 while not data['confirm']: | 
					
						
							|  |  |  |                     data['confirm'] = getpass.getpass(' Confirm: ') | 
					
						
							|  |  |  |                 if data['password'] != data['confirm']: | 
					
						
							|  |  |  |                     data['password'] = '' | 
					
						
							|  |  |  |                     data['confirm'] = None | 
					
						
							|  |  |  |                     print "Password and confirm don't match!" | 
					
						
							|  |  |  |             while not data['email']: | 
					
						
							|  |  |  |                 data['email'] = raw_input('   EMail: ') | 
					
						
							|  |  |  |             code, result = self.post_to_server(data) | 
					
						
							|  |  |  |             if code != 200: | 
					
						
							|  |  |  |                 print 'Server response (%s): %s'%(code, result) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 print 'You will receive an email shortly.' | 
					
						
							|  |  |  |                 print 'Follow the instructions in it to complete registration.' | 
					
						
							|  |  |  |         elif choice == '3': | 
					
						
							|  |  |  |             data = {':action': 'password_reset'} | 
					
						
							|  |  |  |             data['email'] = '' | 
					
						
							|  |  |  |             while not data['email']: | 
					
						
							|  |  |  |                 data['email'] = raw_input('Your email address: ') | 
					
						
							|  |  |  |             code, result = self.post_to_server(data) | 
					
						
							|  |  |  |             print 'Server response (%s): %s'%(code, result) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def build_post_data(self, action): | 
					
						
							|  |  |  |         # figure the data to send - the metadata plus some additional | 
					
						
							|  |  |  |         # information used by the package server | 
					
						
							|  |  |  |         meta = self.distribution.metadata | 
					
						
							|  |  |  |         data = { | 
					
						
							|  |  |  |             ':action': action, | 
					
						
							|  |  |  |             'metadata_version' : '1.0', | 
					
						
							|  |  |  |             'name': meta.get_name(), | 
					
						
							|  |  |  |             'version': meta.get_version(), | 
					
						
							|  |  |  |             'summary': meta.get_description(), | 
					
						
							|  |  |  |             'home_page': meta.get_url(), | 
					
						
							|  |  |  |             'author': meta.get_contact(), | 
					
						
							|  |  |  |             'author_email': meta.get_contact_email(), | 
					
						
							|  |  |  |             'license': meta.get_licence(), | 
					
						
							|  |  |  |             'description': meta.get_long_description(), | 
					
						
							|  |  |  |             'keywords': meta.get_keywords(), | 
					
						
							|  |  |  |             'platform': meta.get_platforms(), | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |             'classifiers': meta.get_classifiers(), | 
					
						
							| 
									
										
										
										
											2003-02-19 14:27:21 +00:00
										 |  |  |             'download_url': meta.get_download_url(), | 
					
						
							| 
									
										
										
										
											2005-03-20 22:19:47 +00:00
										 |  |  |             # PEP 314 | 
					
						
							|  |  |  |             'provides': meta.get_provides(), | 
					
						
							|  |  |  |             'requires': meta.get_requires(), | 
					
						
							|  |  |  |             'obsoletes': meta.get_obsoletes(), | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2005-03-20 22:19:47 +00:00
										 |  |  |         if data['provides'] or data['requires'] or data['obsoletes']: | 
					
						
							|  |  |  |             data['metadata_version'] = '1.1' | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |         return data | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def post_to_server(self, data, auth=None): | 
					
						
							|  |  |  |         ''' Post a query to the server, and return a string response.
 | 
					
						
							|  |  |  |         '''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Build up the MIME payload for the urllib2 POST data | 
					
						
							|  |  |  |         boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' | 
					
						
							|  |  |  |         sep_boundary = '\n--' + boundary | 
					
						
							|  |  |  |         end_boundary = sep_boundary + '--' | 
					
						
							|  |  |  |         body = StringIO.StringIO() | 
					
						
							|  |  |  |         for key, value in data.items(): | 
					
						
							|  |  |  |             # handle multiple entries for the same name | 
					
						
							| 
									
										
										
										
											2006-10-06 13:18:26 +00:00
										 |  |  |             if type(value) not in (type([]), type( () )): | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |                 value = [value] | 
					
						
							|  |  |  |             for value in value: | 
					
						
							| 
									
										
										
										
											2005-03-31 13:57:38 +00:00
										 |  |  |                 value = unicode(value).encode("utf-8") | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |                 body.write(sep_boundary) | 
					
						
							|  |  |  |                 body.write('\nContent-Disposition: form-data; name="%s"'%key) | 
					
						
							|  |  |  |                 body.write("\n\n") | 
					
						
							|  |  |  |                 body.write(value) | 
					
						
							|  |  |  |                 if value and value[-1] == '\r': | 
					
						
							|  |  |  |                     body.write('\n')  # write an extra newline (lurve Macs) | 
					
						
							|  |  |  |         body.write(end_boundary) | 
					
						
							|  |  |  |         body.write("\n") | 
					
						
							|  |  |  |         body = body.getvalue() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # build the Request | 
					
						
							|  |  |  |         headers = { | 
					
						
							| 
									
										
										
										
											2005-03-31 13:57:38 +00:00
										 |  |  |             'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary, | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |             'Content-length': str(len(body)) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         req = urllib2.Request(self.repository, body, headers) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # handle HTTP and include the Basic Auth handler | 
					
						
							|  |  |  |         opener = urllib2.build_opener( | 
					
						
							|  |  |  |             urllib2.HTTPBasicAuthHandler(password_mgr=auth) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         data = '' | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             result = opener.open(req) | 
					
						
							|  |  |  |         except urllib2.HTTPError, e: | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |             if self.show_response: | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |                 data = e.fp.read() | 
					
						
							|  |  |  |             result = e.code, e.msg | 
					
						
							|  |  |  |         except urllib2.URLError, e: | 
					
						
							|  |  |  |             result = 500, str(e) | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |             if self.show_response: | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |                 data = result.read() | 
					
						
							|  |  |  |             result = 200, 'OK' | 
					
						
							| 
									
										
										
										
											2003-02-19 13:49:35 +00:00
										 |  |  |         if self.show_response: | 
					
						
							| 
									
										
										
										
											2003-01-03 15:29:28 +00:00
										 |  |  |             print '-'*75, data, '-'*75 | 
					
						
							|  |  |  |         return result |