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 repository" 
  20     DEFAULT_REPOSITORY 
= 'http://www.python.org/pypi' 
  24          "url of repository [default: %s]"%DEFAULT_REPOSITORY
), 
  26          'verify the package metadata for correctness'), 
  27         ('list-classifiers', None, 
  28          'list the valid Trove classifiers'), 
  29         ('show-response', None, 
  30          'display full response text from server'), 
  32     boolean_options 
= ['verify', 'show-response', 'list-classifiers'] 
  34     def initialize_options(self
): 
  35         self
.repository 
= None 
  37         self
.show_response 
= 0 
  38         self
.list_classifiers 
= 0 
  40     def finalize_options(self
): 
  41         if self
.repository 
is None: 
  42             self
.repository 
= self
.DEFAULT_REPOSITORY
 
  47             self
.verify_metadata() 
  48         elif self
.list_classifiers
: 
  53     def check_metadata(self
): 
  54         """Ensure that all required elements of meta-data (name, version, 
  55            URL, (author and author_email) or (maintainer and 
  56            maintainer_email)) are supplied by the Distribution object; warn if 
  59         metadata 
= self
.distribution
.metadata
 
  62         for attr 
in ('name', 'version', 'url'): 
  63             if not (hasattr(metadata
, attr
) and getattr(metadata
, attr
)): 
  67             self
.warn("missing required meta-data: " + 
  68                       string
.join(missing
, ", ")) 
  71             if not metadata
.author_email
: 
  72                 self
.warn("missing meta-data: if 'author' supplied, " + 
  73                           "'author_email' must be supplied too") 
  74         elif metadata
.maintainer
: 
  75             if not metadata
.maintainer_email
: 
  76                 self
.warn("missing meta-data: if 'maintainer' supplied, " + 
  77                           "'maintainer_email' must be supplied too") 
  79             self
.warn("missing meta-data: either (author and author_email) " + 
  80                       "or (maintainer and maintainer_email) " + 
  83     def classifiers(self
): 
  84         ''' Fetch the list of classifiers from the server. 
  86         response 
= urllib2
.urlopen(self
.repository
+'?:action=list_classifiers') 
  89     def verify_metadata(self
): 
  90         ''' Send the metadata to the package index server to be checked. 
  92         # send the info to the server and report the result 
  93         (code
, result
) = self
.post_to_server(self
.build_post_data('verify')) 
  94         print 'Server response (%s): %s'%(code
, result
) 
  96     def send_metadata(self
): 
  97         ''' Send the metadata to the package index server. 
  99             Well, do the following: 
 100             1. figure who the user is, and then 
 101             2. send the data as a Basic auth'ed POST. 
 103             First we try to read the username/password from $HOME/.pypirc, 
 104             which is a ConfigParser-formatted file with a section 
 105             [server-login] containing username and password entries (both 
 112             Otherwise, to figure who the user is, we offer the user three 
 115              1. use existing login, 
 116              2. register as a new user, or 
 117              3. set the password to a random string and email the user. 
 121         username 
= password 
= '' 
 123         # see if we can short-cut and get the username/password from the 
 126         if os
.environ
.has_key('HOME'): 
 127             rc 
= os
.path
.join(os
.environ
['HOME'], '.pypirc') 
 128             if os
.path
.exists(rc
): 
 129                 print 'Using PyPI login from %s'%rc 
 130                 config 
= ConfigParser
.ConfigParser() 
 132                 username 
= config
.get('server-login', 'username') 
 133                 password 
= config
.get('server-login', 'password') 
 136         # get the user's login info 
 137         choices 
= '1 2 3 4'.split() 
 138         while choice 
not in choices
: 
 139             print '''We need to know who you are, so please choose either: 
 140  1. use your existing login, 
 141  2. register as a new user, 
 142  3. have the server generate a new password for you (and email it to you), or 
 144 Your selection [default 1]: ''', 
 148             elif choice 
not in choices
: 
 149                 print 'Please choose one of the four options!' 
 152             # get the username and password 
 154                 username 
= raw_input('Username: ') 
 156                 password 
= getpass
.getpass('Password: ') 
 158             # set up the authentication 
 159             auth 
= urllib2
.HTTPPasswordMgr() 
 160             host 
= urlparse
.urlparse(self
.repository
)[1] 
 161             auth
.add_password('pypi', host
, username
, password
) 
 163             # send the info to the server and report the result 
 164             code
, result 
= self
.post_to_server(self
.build_post_data('submit'), 
 166             print 'Server response (%s): %s'%(code
, result
) 
 168             # possibly save the login 
 169             if os
.environ
.has_key('HOME') and config 
is None and code 
== 200: 
 170                 rc 
= os
.path
.join(os
.environ
['HOME'], '.pypirc') 
 171                 print 'I can store your PyPI login so future submissions will be faster.' 
 172                 print '(the login will be stored in %s)'%rc 
 174                 while choice
.lower() not in 'yn': 
 175                     choice 
= raw_input('Save your login (y/N)?') 
 178                 if choice
.lower() == 'y': 
 180                     f
.write('[server-login]\nusername:%s\npassword:%s\n'%( 
 188             data 
= {':action': 'user'}
 
 189             data
['name'] = data
['password'] = data
['email'] = '' 
 190             data
['confirm'] = None 
 191             while not data
['name']: 
 192                 data
['name'] = raw_input('Username: ') 
 193             while data
['password'] != data
['confirm']: 
 194                 while not data
['password']: 
 195                     data
['password'] = getpass
.getpass('Password: ') 
 196                 while not data
['confirm']: 
 197                     data
['confirm'] = getpass
.getpass(' Confirm: ') 
 198                 if data
['password'] != data
['confirm']: 
 199                     data
['password'] = '' 
 200                     data
['confirm'] = None 
 201                     print "Password and confirm don't match!" 
 202             while not data
['email']: 
 203                 data
['email'] = raw_input('   EMail: ') 
 204             code
, result 
= self
.post_to_server(data
) 
 206                 print 'Server response (%s): %s'%(code
, result
) 
 208                 print 'You will receive an email shortly.' 
 209                 print 'Follow the instructions in it to complete registration.' 
 211             data 
= {':action': 'password_reset'}
 
 213             while not data
['email']: 
 214                 data
['email'] = raw_input('Your email address: ') 
 215             code
, result 
= self
.post_to_server(data
) 
 216             print 'Server response (%s): %s'%(code
, result
) 
 218     def build_post_data(self
, action
): 
 219         # figure the data to send - the metadata plus some additional 
 220         # information used by the package server 
 221         meta 
= self
.distribution
.metadata
 
 224             'metadata_version' : '1.0', 
 225             'name': meta
.get_name(), 
 226             'version': meta
.get_version(), 
 227             'summary': meta
.get_description(), 
 228             'home_page': meta
.get_url(), 
 229             'author': meta
.get_contact(), 
 230             'author_email': meta
.get_contact_email(), 
 231             'license': meta
.get_licence(), 
 232             'description': meta
.get_long_description(), 
 233             'keywords': meta
.get_keywords(), 
 234             'platform': meta
.get_platforms(), 
 235             'classifiers': meta
.get_classifiers(), 
 236             'download_url': meta
.get_download_url(), 
 240     def post_to_server(self
, data
, auth
=None): 
 241         ''' Post a query to the server, and return a string response. 
 244         # Build up the MIME payload for the urllib2 POST data 
 245         boundary 
= '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254' 
 246         sep_boundary 
= '\n--' + boundary
 
 247         end_boundary 
= sep_boundary 
+ '--' 
 248         body 
= StringIO
.StringIO() 
 249         for key
, value 
in data
.items(): 
 250             # handle multiple entries for the same name 
 251             if type(value
) != type([]): 
 255                 body
.write(sep_boundary
) 
 256                 body
.write('\nContent-Disposition: form-data; name="%s"'%key
) 
 259                 if value 
and value
[-1] == '\r': 
 260                     body
.write('\n')  # write an extra newline (lurve Macs) 
 261         body
.write(end_boundary
) 
 263         body 
= body
.getvalue() 
 267             'Content-type': 'multipart/form-data; boundary=%s'%boundary
, 
 268             'Content-length': str(len(body
)) 
 270         req 
= urllib2
.Request(self
.repository
, body
, headers
) 
 272         # handle HTTP and include the Basic Auth handler 
 273         opener 
= urllib2
.build_opener( 
 274             urllib2
.HTTPBasicAuthHandler(password_mgr
=auth
) 
 278             result 
= opener
.open(req
) 
 279         except urllib2
.HTTPError
, e
: 
 280             if self
.show_response
: 
 282             result 
= e
.code
, e
.msg
 
 283         except urllib2
.URLError
, e
: 
 286             if self
.show_response
: 
 289         if self
.show_response
: 
 290             print '-'*75, data
, '-'*75