1 """distutils.command.register 
   3 Implements the Distutils 'register' command (register with the repository). 
   6 # created 2002/10/21, Richard Jones 
  10 import sys
, os
, string
, urllib2
, getpass
, urlparse
 
  11 import StringIO
, ConfigParser
 
  13 from distutils
.core 
import Command
 
  14 from distutils
.errors 
import * 
  16 class register(Command
): 
  18     description 
= ("register the distribution with the Python package index") 
  20     DEFAULT_REPOSITORY 
= 'http://www.python.org/pypi' 
  24          "url of repository [default: %s]"%DEFAULT_REPOSITORY
), 
  25         ('list-classifiers', None, 
  26          'list the valid Trove classifiers'), 
  27         ('show-response', None, 
  28          'display full response text from server'), 
  30     boolean_options 
= ['verify', 'show-response', 'list-classifiers'] 
  32     def initialize_options(self
): 
  33         self
.repository 
= None 
  34         self
.show_response 
= 0 
  35         self
.list_classifiers 
= 0 
  37     def finalize_options(self
): 
  38         if self
.repository 
is None: 
  39             self
.repository 
= self
.DEFAULT_REPOSITORY
 
  44             self
.verify_metadata() 
  45         elif self
.list_classifiers
: 
  50     def check_metadata(self
): 
  51         """Ensure that all required elements of meta-data (name, version, 
  52            URL, (author and author_email) or (maintainer and 
  53            maintainer_email)) are supplied by the Distribution object; warn if 
  56         metadata 
= self
.distribution
.metadata
 
  59         for attr 
in ('name', 'version', 'url'): 
  60             if not (hasattr(metadata
, attr
) and getattr(metadata
, attr
)): 
  64             self
.warn("missing required meta-data: " + 
  65                       string
.join(missing
, ", ")) 
  68             if not metadata
.author_email
: 
  69                 self
.warn("missing meta-data: if 'author' supplied, " + 
  70                           "'author_email' must be supplied too") 
  71         elif metadata
.maintainer
: 
  72             if not metadata
.maintainer_email
: 
  73                 self
.warn("missing meta-data: if 'maintainer' supplied, " + 
  74                           "'maintainer_email' must be supplied too") 
  76             self
.warn("missing meta-data: either (author and author_email) " + 
  77                       "or (maintainer and maintainer_email) " + 
  80     def classifiers(self
): 
  81         ''' Fetch the list of classifiers from the server. 
  83         response 
= urllib2
.urlopen(self
.repository
+'?:action=list_classifiers') 
  86     def verify_metadata(self
): 
  87         ''' Send the metadata to the package index server to be checked. 
  89         # send the info to the server and report the result 
  90         (code
, result
) = self
.post_to_server(self
.build_post_data('verify')) 
  91         print 'Server response (%s): %s'%(code
, result
) 
  93     def send_metadata(self
): 
  94         ''' Send the metadata to the package index server. 
  96             Well, do the following: 
  97             1. figure who the user is, and then 
  98             2. send the data as a Basic auth'ed POST. 
 100             First we try to read the username/password from $HOME/.pypirc, 
 101             which is a ConfigParser-formatted file with a section 
 102             [server-login] containing username and password entries (both 
 109             Otherwise, to figure who the user is, we offer the user three 
 112              1. use existing login, 
 113              2. register as a new user, or 
 114              3. set the password to a random string and email the user. 
 118         username 
= password 
= '' 
 120         # see if we can short-cut and get the username/password from the 
 123         if os
.environ
.has_key('HOME'): 
 124             rc 
= os
.path
.join(os
.environ
['HOME'], '.pypirc') 
 125             if os
.path
.exists(rc
): 
 126                 print 'Using PyPI login from %s'%rc 
 127                 config 
= ConfigParser
.ConfigParser() 
 129                 username 
= config
.get('server-login', 'username') 
 130                 password 
= config
.get('server-login', 'password') 
 133         # get the user's login info 
 134         choices 
= '1 2 3 4'.split() 
 135         while choice 
not in choices
: 
 136             print '''We need to know who you are, so please choose either: 
 137  1. use your existing login, 
 138  2. register as a new user, 
 139  3. have the server generate a new password for you (and email it to you), or 
 141 Your selection [default 1]: ''', 
 145             elif choice 
not in choices
: 
 146                 print 'Please choose one of the four options!' 
 149             # get the username and password 
 151                 username 
= raw_input('Username: ') 
 153                 password 
= getpass
.getpass('Password: ') 
 155             # set up the authentication 
 156             auth 
= urllib2
.HTTPPasswordMgr() 
 157             host 
= urlparse
.urlparse(self
.repository
)[1] 
 158             auth
.add_password('pypi', host
, username
, password
) 
 160             # send the info to the server and report the result 
 161             code
, result 
= self
.post_to_server(self
.build_post_data('submit'), 
 163             print 'Server response (%s): %s'%(code
, result
) 
 165             # possibly save the login 
 166             if os
.environ
.has_key('HOME') and config 
is None and code 
== 200: 
 167                 rc 
= os
.path
.join(os
.environ
['HOME'], '.pypirc') 
 168                 print 'I can store your PyPI login so future submissions will be faster.' 
 169                 print '(the login will be stored in %s)'%rc 
 171                 while choice
.lower() not in 'yn': 
 172                     choice 
= raw_input('Save your login (y/N)?') 
 175                 if choice
.lower() == 'y': 
 177                     f
.write('[server-login]\nusername:%s\npassword:%s\n'%( 
 185             data 
= {':action': 'user'}
 
 186             data
['name'] = data
['password'] = data
['email'] = '' 
 187             data
['confirm'] = None 
 188             while not data
['name']: 
 189                 data
['name'] = raw_input('Username: ') 
 190             while data
['password'] != data
['confirm']: 
 191                 while not data
['password']: 
 192                     data
['password'] = getpass
.getpass('Password: ') 
 193                 while not data
['confirm']: 
 194                     data
['confirm'] = getpass
.getpass(' Confirm: ') 
 195                 if data
['password'] != data
['confirm']: 
 196                     data
['password'] = '' 
 197                     data
['confirm'] = None 
 198                     print "Password and confirm don't match!" 
 199             while not data
['email']: 
 200                 data
['email'] = raw_input('   EMail: ') 
 201             code
, result 
= self
.post_to_server(data
) 
 203                 print 'Server response (%s): %s'%(code
, result
) 
 205                 print 'You will receive an email shortly.' 
 206                 print 'Follow the instructions in it to complete registration.' 
 208             data 
= {':action': 'password_reset'}
 
 210             while not data
['email']: 
 211                 data
['email'] = raw_input('Your email address: ') 
 212             code
, result 
= self
.post_to_server(data
) 
 213             print 'Server response (%s): %s'%(code
, result
) 
 215     def build_post_data(self
, action
): 
 216         # figure the data to send - the metadata plus some additional 
 217         # information used by the package server 
 218         meta 
= self
.distribution
.metadata
 
 221             'metadata_version' : '1.0', 
 222             'name': meta
.get_name(), 
 223             'version': meta
.get_version(), 
 224             'summary': meta
.get_description(), 
 225             'home_page': meta
.get_url(), 
 226             'author': meta
.get_contact(), 
 227             'author_email': meta
.get_contact_email(), 
 228             'license': meta
.get_licence(), 
 229             'description': meta
.get_long_description(), 
 230             'keywords': meta
.get_keywords(), 
 231             'platform': meta
.get_platforms(), 
 232             'classifiers': meta
.get_classifiers(), 
 233             'download_url': meta
.get_download_url(), 
 237     def post_to_server(self
, data
, auth
=None): 
 238         ''' Post a query to the server, and return a string response. 
 241         # Build up the MIME payload for the urllib2 POST data 
 242         boundary 
= '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' 
 243         sep_boundary 
= '\n--' + boundary
 
 244         end_boundary 
= sep_boundary 
+ '--' 
 245         body 
= StringIO
.StringIO() 
 246         for key
, value 
in data
.items(): 
 247             # handle multiple entries for the same name 
 248             if type(value
) != type([]): 
 252                 body
.write(sep_boundary
) 
 253                 body
.write('\nContent-Disposition: form-data; name="%s"'%key
) 
 256                 if value 
and value
[-1] == '\r': 
 257                     body
.write('\n')  # write an extra newline (lurve Macs) 
 258         body
.write(end_boundary
) 
 260         body 
= body
.getvalue() 
 264             'Content-type': 'multipart/form-data; boundary=%s'%boundary
, 
 265             'Content-length': str(len(body
)) 
 267         req 
= urllib2
.Request(self
.repository
, body
, headers
) 
 269         # handle HTTP and include the Basic Auth handler 
 270         opener 
= urllib2
.build_opener( 
 271             urllib2
.HTTPBasicAuthHandler(password_mgr
=auth
) 
 275             result 
= opener
.open(req
) 
 276         except urllib2
.HTTPError
, e
: 
 277             if self
.show_response
: 
 279             result 
= e
.code
, e
.msg
 
 280         except urllib2
.URLError
, e
: 
 283             if self
.show_response
: 
 286         if self
.show_response
: 
 287             print '-'*75, data
, '-'*75