0001 """distutils.command.register 0002 0003 Implements the Distutils 'register' command (register with the repository). 0004 """ 0005 0006 # created 2002/10/21, Richard Jones 0007 0008 __revision__ = "$Id: register.py,v 1.7 2004/07/18 06:14:43 tim_one Exp $" 0009 0010 import sys, os, string, urllib2, getpass, urlparse 0011 import StringIO, ConfigParser 0012 0013 from distutils.core import Command 0014 from distutils.errors import * 0015 0016 class register(Command): 0017 0018 description = ("register the distribution with the Python package index") 0019 0020 DEFAULT_REPOSITORY = 'http://www.python.org/pypi' 0021 0022 user_options = [ 0023 ('repository=', 'r', 0024 "url of repository [default: %s]"%DEFAULT_REPOSITORY), 0025 ('list-classifiers', None, 0026 'list the valid Trove classifiers'), 0027 ('show-response', None, 0028 'display full response text from server'), 0029 ] 0030 boolean_options = ['verify', 'show-response', 'list-classifiers'] 0031 0032 def initialize_options(self): 0033 self.repository = None 0034 self.show_response = 0 0035 self.list_classifiers = 0 0036 0037 def finalize_options(self): 0038 if self.repository is None: 0039 self.repository = self.DEFAULT_REPOSITORY 0040 0041 def run(self): 0042 self.check_metadata() 0043 if self.dry_run: 0044 self.verify_metadata() 0045 elif self.list_classifiers: 0046 self.classifiers() 0047 else: 0048 self.send_metadata() 0049 0050 def check_metadata(self): 0051 """Ensure that all required elements of meta-data (name, version, 0052 URL, (author and author_email) or (maintainer and 0053 maintainer_email)) are supplied by the Distribution object; warn if 0054 any are missing. 0055 """ 0056 metadata = self.distribution.metadata 0057 0058 missing = [] 0059 for attr in ('name', 'version', 'url'): 0060 if not (hasattr(metadata, attr) and getattr(metadata, attr)): 0061 missing.append(attr) 0062 0063 if missing: 0064 self.warn("missing required meta-data: " + 0065 string.join(missing, ", ")) 0066 0067 if metadata.author: 0068 if not metadata.author_email: 0069 self.warn("missing meta-data: if 'author' supplied, " + 0070 "'author_email' must be supplied too") 0071 elif metadata.maintainer: 0072 if not metadata.maintainer_email: 0073 self.warn("missing meta-data: if 'maintainer' supplied, " + 0074 "'maintainer_email' must be supplied too") 0075 else: 0076 self.warn("missing meta-data: either (author and author_email) " + 0077 "or (maintainer and maintainer_email) " + 0078 "must be supplied") 0079 0080 def classifiers(self): 0081 ''' Fetch the list of classifiers from the server. 0082 ''' 0083 response = urllib2.urlopen(self.repository+'?:action=list_classifiers') 0084 print response.read() 0085 0086 def verify_metadata(self): 0087 ''' Send the metadata to the package index server to be checked. 0088 ''' 0089 # send the info to the server and report the result 0090 (code, result) = self.post_to_server(self.build_post_data('verify')) 0091 print 'Server response (%s): %s'%(code, result) 0092 0093 def send_metadata(self): 0094 ''' Send the metadata to the package index server. 0095 0096 Well, do the following: 0097 1. figure who the user is, and then 0098 2. send the data as a Basic auth'ed POST. 0099 0100 First we try to read the username/password from $HOME/.pypirc, 0101 which is a ConfigParser-formatted file with a section 0102 [server-login] containing username and password entries (both 0103 in clear text). Eg: 0104 0105 [server-login] 0106 username: fred 0107 password: sekrit 0108 0109 Otherwise, to figure who the user is, we offer the user three 0110 choices: 0111 0112 1. use existing login, 0113 2. register as a new user, or 0114 3. set the password to a random string and email the user. 0115 0116 ''' 0117 choice = 'x' 0118 username = password = '' 0119 0120 # see if we can short-cut and get the username/password from the 0121 # config 0122 config = None 0123 if os.environ.has_key('HOME'): 0124 rc = os.path.join(os.environ['HOME'], '.pypirc') 0125 if os.path.exists(rc): 0126 print 'Using PyPI login from %s'%rc 0127 config = ConfigParser.ConfigParser() 0128 config.read(rc) 0129 username = config.get('server-login', 'username') 0130 password = config.get('server-login', 'password') 0131 choice = '1' 0132 0133 # get the user's login info 0134 choices = '1 2 3 4'.split() 0135 while choice not in choices: 0136 print '''We need to know who you are, so please choose either: 0137 1. use your existing login, 0138 2. register as a new user, 0139 3. have the server generate a new password for you (and email it to you), or 0140 4. quit 0141 Your selection [default 1]: ''', 0142 choice = raw_input() 0143 if not choice: 0144 choice = '1' 0145 elif choice not in choices: 0146 print 'Please choose one of the four options!' 0147 0148 if choice == '1': 0149 # get the username and password 0150 while not username: 0151 username = raw_input('Username: ') 0152 while not password: 0153 password = getpass.getpass('Password: ') 0154 0155 # set up the authentication 0156 auth = urllib2.HTTPPasswordMgr() 0157 host = urlparse.urlparse(self.repository)[1] 0158 auth.add_password('pypi', host, username, password) 0159 0160 # send the info to the server and report the result 0161 code, result = self.post_to_server(self.build_post_data('submit'), 0162 auth) 0163 print 'Server response (%s): %s'%(code, result) 0164 0165 # possibly save the login 0166 if os.environ.has_key('HOME') and config is None and code == 200: 0167 rc = os.path.join(os.environ['HOME'], '.pypirc') 0168 print 'I can store your PyPI login so future submissions will be faster.' 0169 print '(the login will be stored in %s)'%rc 0170 choice = 'X' 0171 while choice.lower() not in 'yn': 0172 choice = raw_input('Save your login (y/N)?') 0173 if not choice: 0174 choice = 'n' 0175 if choice.lower() == 'y': 0176 f = open(rc, 'w') 0177 f.write('[server-login]\nusername:%s\npassword:%s\n'%( 0178 username, password)) 0179 f.close() 0180 try: 0181 os.chmod(rc, 0600) 0182 except: 0183 pass 0184 elif choice == '2': 0185 data = {':action': 'user'} 0186 data['name'] = data['password'] = data['email'] = '' 0187 data['confirm'] = None 0188 while not data['name']: 0189 data['name'] = raw_input('Username: ') 0190 while data['password'] != data['confirm']: 0191 while not data['password']: 0192 data['password'] = getpass.getpass('Password: ') 0193 while not data['confirm']: 0194 data['confirm'] = getpass.getpass(' Confirm: ') 0195 if data['password'] != data['confirm']: 0196 data['password'] = '' 0197 data['confirm'] = None 0198 print "Password and confirm don't match!" 0199 while not data['email']: 0200 data['email'] = raw_input(' EMail: ') 0201 code, result = self.post_to_server(data) 0202 if code != 200: 0203 print 'Server response (%s): %s'%(code, result) 0204 else: 0205 print 'You will receive an email shortly.' 0206 print 'Follow the instructions in it to complete registration.' 0207 elif choice == '3': 0208 data = {':action': 'password_reset'} 0209 data['email'] = '' 0210 while not data['email']: 0211 data['email'] = raw_input('Your email address: ') 0212 code, result = self.post_to_server(data) 0213 print 'Server response (%s): %s'%(code, result) 0214 0215 def build_post_data(self, action): 0216 # figure the data to send - the metadata plus some additional 0217 # information used by the package server 0218 meta = self.distribution.metadata 0219 data = { 0220 ':action': action, 0221 'metadata_version' : '1.0', 0222 'name': meta.get_name(), 0223 'version': meta.get_version(), 0224 'summary': meta.get_description(), 0225 'home_page': meta.get_url(), 0226 'author': meta.get_contact(), 0227 'author_email': meta.get_contact_email(), 0228 'license': meta.get_licence(), 0229 'description': meta.get_long_description(), 0230 'keywords': meta.get_keywords(), 0231 'platform': meta.get_platforms(), 0232 'classifiers': meta.get_classifiers(), 0233 'download_url': meta.get_download_url(), 0234 } 0235 return data 0236 0237 def post_to_server(self, data, auth=None): 0238 ''' Post a query to the server, and return a string response. 0239 ''' 0240 0241 # Build up the MIME payload for the urllib2 POST data 0242 boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' 0243 sep_boundary = '\n--' + boundary 0244 end_boundary = sep_boundary + '--' 0245 body = StringIO.StringIO() 0246 for key, value in data.items(): 0247 # handle multiple entries for the same name 0248 if type(value) != type([]): 0249 value = [value] 0250 for value in value: 0251 value = str(value) 0252 body.write(sep_boundary) 0253 body.write('\nContent-Disposition: form-data; name="%s"'%key) 0254 body.write("\n\n") 0255 body.write(value) 0256 if value and value[-1] == '\r': 0257 body.write('\n') # write an extra newline (lurve Macs) 0258 body.write(end_boundary) 0259 body.write("\n") 0260 body = body.getvalue() 0261 0262 # build the Request 0263 headers = { 0264 'Content-type': 'multipart/form-data; boundary=%s'%boundary, 0265 'Content-length': str(len(body)) 0266 } 0267 req = urllib2.Request(self.repository, body, headers) 0268 0269 # handle HTTP and include the Basic Auth handler 0270 opener = urllib2.build_opener( 0271 urllib2.HTTPBasicAuthHandler(password_mgr=auth) 0272 ) 0273 data = '' 0274 try: 0275 result = opener.open(req) 0276 except urllib2.HTTPError, e: 0277 if self.show_response: 0278 data = e.fp.read() 0279 result = e.code, e.msg 0280 except urllib2.URLError, e: 0281 result = 500, str(e) 0282 else: 0283 if self.show_response: 0284 data = result.read() 0285 result = 200, 'OK' 0286 if self.show_response: 0287 print '-'*75, data, '-'*75 0288 return result 0289
Generated by PyXR 0.9.4