PageRenderTime 883ms CodeModel.GetById 201ms app.highlight 469ms RepoModel.GetById 197ms app.codeStats 1ms

/Lib/distutils/command/register.py

http://unladen-swallow.googlecode.com/
Python | 301 lines | 288 code | 6 blank | 7 comment | 9 complexity | ca87da90b9f003c1213b87c1f0399892 MD5 | raw file
  1"""distutils.command.register
  2
  3Implements the Distutils 'register' command (register with the repository).
  4"""
  5
  6# created 2002/10/21, Richard Jones
  7
  8__revision__ = "$Id: register.py 67944 2008-12-27 13:28:42Z tarek.ziade $"
  9
 10import os, string, urllib2, getpass, urlparse
 11import StringIO
 12
 13from distutils.core import PyPIRCCommand
 14from distutils.errors import *
 15from distutils import log
 16
 17class register(PyPIRCCommand):
 18
 19    description = ("register the distribution with the Python package index")
 20    user_options = PyPIRCCommand.user_options + [
 21        ('list-classifiers', None,
 22         'list the valid Trove classifiers'),
 23        ]
 24    boolean_options = PyPIRCCommand.boolean_options + [
 25        'verify', 'list-classifiers']
 26
 27    def initialize_options(self):
 28        PyPIRCCommand.initialize_options(self)
 29        self.list_classifiers = 0
 30
 31    def run(self):
 32        self.finalize_options()
 33        self._set_config()
 34        self.check_metadata()
 35        if self.dry_run:
 36            self.verify_metadata()
 37        elif self.list_classifiers:
 38            self.classifiers()
 39        else:
 40            self.send_metadata()
 41
 42    def check_metadata(self):
 43        """Ensure that all required elements of meta-data (name, version,
 44           URL, (author and author_email) or (maintainer and
 45           maintainer_email)) are supplied by the Distribution object; warn if
 46           any are missing.
 47        """
 48        metadata = self.distribution.metadata
 49
 50        missing = []
 51        for attr in ('name', 'version', 'url'):
 52            if not (hasattr(metadata, attr) and getattr(metadata, attr)):
 53                missing.append(attr)
 54
 55        if missing:
 56            self.warn("missing required meta-data: " +
 57                      string.join(missing, ", "))
 58
 59        if metadata.author:
 60            if not metadata.author_email:
 61                self.warn("missing meta-data: if 'author' supplied, " +
 62                          "'author_email' must be supplied too")
 63        elif metadata.maintainer:
 64            if not metadata.maintainer_email:
 65                self.warn("missing meta-data: if 'maintainer' supplied, " +
 66                          "'maintainer_email' must be supplied too")
 67        else:
 68            self.warn("missing meta-data: either (author and author_email) " +
 69                      "or (maintainer and maintainer_email) " +
 70                      "must be supplied")
 71
 72    def _set_config(self):
 73        ''' Reads the configuration file and set attributes.
 74        '''
 75        config = self._read_pypirc()
 76        if config != {}:
 77            self.username = config['username']
 78            self.password = config['password']
 79            self.repository = config['repository']
 80            self.realm = config['realm']
 81            self.has_config = True
 82        else:
 83            if self.repository not in ('pypi', self.DEFAULT_REPOSITORY):
 84                raise ValueError('%s not found in .pypirc' % self.repository)
 85            if self.repository == 'pypi':
 86                self.repository = self.DEFAULT_REPOSITORY
 87            self.has_config = False
 88
 89    def classifiers(self):
 90        ''' Fetch the list of classifiers from the server.
 91        '''
 92        response = urllib2.urlopen(self.repository+'?:action=list_classifiers')
 93        print response.read()
 94
 95    def verify_metadata(self):
 96        ''' Send the metadata to the package index server to be checked.
 97        '''
 98        # send the info to the server and report the result
 99        (code, result) = self.post_to_server(self.build_post_data('verify'))
100        print 'Server response (%s): %s'%(code, result)
101
102
103    def send_metadata(self):
104        ''' Send the metadata to the package index server.
105
106            Well, do the following:
107            1. figure who the user is, and then
108            2. send the data as a Basic auth'ed POST.
109
110            First we try to read the username/password from $HOME/.pypirc,
111            which is a ConfigParser-formatted file with a section
112            [distutils] containing username and password entries (both
113            in clear text). Eg:
114
115                [distutils]
116                index-servers =
117                    pypi
118
119                [pypi]
120                username: fred
121                password: sekrit
122
123            Otherwise, to figure who the user is, we offer the user three
124            choices:
125
126             1. use existing login,
127             2. register as a new user, or
128             3. set the password to a random string and email the user.
129
130        '''
131        # see if we can short-cut and get the username/password from the
132        # config
133        if self.has_config:
134            choice = '1'
135            username = self.username
136            password = self.password
137        else:
138            choice = 'x'
139            username = password = ''
140
141        # get the user's login info
142        choices = '1 2 3 4'.split()
143        while choice not in choices:
144            self.announce('''\
145We need to know who you are, so please choose either:
146 1. use your existing login,
147 2. register as a new user,
148 3. have the server generate a new password for you (and email it to you), or
149 4. quit
150Your selection [default 1]: ''', log.INFO)
151
152            choice = raw_input()
153            if not choice:
154                choice = '1'
155            elif choice not in choices:
156                print 'Please choose one of the four options!'
157
158        if choice == '1':
159            # get the username and password
160            while not username:
161                username = raw_input('Username: ')
162            while not password:
163                password = getpass.getpass('Password: ')
164
165            # set up the authentication
166            auth = urllib2.HTTPPasswordMgr()
167            host = urlparse.urlparse(self.repository)[1]
168            auth.add_password(self.realm, host, username, password)
169            # send the info to the server and report the result
170            code, result = self.post_to_server(self.build_post_data('submit'),
171                auth)
172            self.announce('Server response (%s): %s' % (code, result),
173                          log.INFO)
174
175            # possibly save the login
176            if not self.has_config and code == 200:
177                self.announce(('I can store your PyPI login so future '
178                               'submissions will be faster.'), log.INFO)
179                self.announce('(the login will be stored in %s)' % \
180                              self._get_rc_file(), log.INFO)
181
182                choice = 'X'
183                while choice.lower() not in 'yn':
184                    choice = raw_input('Save your login (y/N)?')
185                    if not choice:
186                        choice = 'n'
187                if choice.lower() == 'y':
188                    self._store_pypirc(username, password)
189
190        elif choice == '2':
191            data = {':action': 'user'}
192            data['name'] = data['password'] = data['email'] = ''
193            data['confirm'] = None
194            while not data['name']:
195                data['name'] = raw_input('Username: ')
196            while data['password'] != data['confirm']:
197                while not data['password']:
198                    data['password'] = getpass.getpass('Password: ')
199                while not data['confirm']:
200                    data['confirm'] = getpass.getpass(' Confirm: ')
201                if data['password'] != data['confirm']:
202                    data['password'] = ''
203                    data['confirm'] = None
204                    print "Password and confirm don't match!"
205            while not data['email']:
206                data['email'] = raw_input('   EMail: ')
207            code, result = self.post_to_server(data)
208            if code != 200:
209                print 'Server response (%s): %s'%(code, result)
210            else:
211                print 'You will receive an email shortly.'
212                print 'Follow the instructions in it to complete registration.'
213        elif choice == '3':
214            data = {':action': 'password_reset'}
215            data['email'] = ''
216            while not data['email']:
217                data['email'] = raw_input('Your email address: ')
218            code, result = self.post_to_server(data)
219            print 'Server response (%s): %s'%(code, result)
220
221    def build_post_data(self, action):
222        # figure the data to send - the metadata plus some additional
223        # information used by the package server
224        meta = self.distribution.metadata
225        data = {
226            ':action': action,
227            'metadata_version' : '1.0',
228            'name': meta.get_name(),
229            'version': meta.get_version(),
230            'summary': meta.get_description(),
231            'home_page': meta.get_url(),
232            'author': meta.get_contact(),
233            'author_email': meta.get_contact_email(),
234            'license': meta.get_licence(),
235            'description': meta.get_long_description(),
236            'keywords': meta.get_keywords(),
237            'platform': meta.get_platforms(),
238            'classifiers': meta.get_classifiers(),
239            'download_url': meta.get_download_url(),
240            # PEP 314
241            'provides': meta.get_provides(),
242            'requires': meta.get_requires(),
243            'obsoletes': meta.get_obsoletes(),
244        }
245        if data['provides'] or data['requires'] or data['obsoletes']:
246            data['metadata_version'] = '1.1'
247        return data
248
249    def post_to_server(self, data, auth=None):
250        ''' Post a query to the server, and return a string response.
251        '''
252        self.announce('Registering %s to %s' % (data['name'],
253                                                self.repository), log.INFO)
254        # Build up the MIME payload for the urllib2 POST data
255        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
256        sep_boundary = '\n--' + boundary
257        end_boundary = sep_boundary + '--'
258        body = StringIO.StringIO()
259        for key, value in data.items():
260            # handle multiple entries for the same name
261            if type(value) not in (type([]), type( () )):
262                value = [value]
263            for value in value:
264                value = unicode(value).encode("utf-8")
265                body.write(sep_boundary)
266                body.write('\nContent-Disposition: form-data; name="%s"'%key)
267                body.write("\n\n")
268                body.write(value)
269                if value and value[-1] == '\r':
270                    body.write('\n')  # write an extra newline (lurve Macs)
271        body.write(end_boundary)
272        body.write("\n")
273        body = body.getvalue()
274
275        # build the Request
276        headers = {
277            'Content-type': 'multipart/form-data; boundary=%s; charset=utf-8'%boundary,
278            'Content-length': str(len(body))
279        }
280        req = urllib2.Request(self.repository, body, headers)
281
282        # handle HTTP and include the Basic Auth handler
283        opener = urllib2.build_opener(
284            urllib2.HTTPBasicAuthHandler(password_mgr=auth)
285        )
286        data = ''
287        try:
288            result = opener.open(req)
289        except urllib2.HTTPError, e:
290            if self.show_response:
291                data = e.fp.read()
292            result = e.code, e.msg
293        except urllib2.URLError, e:
294            result = 500, str(e)
295        else:
296            if self.show_response:
297                data = result.read()
298            result = 200, 'OK'
299        if self.show_response:
300            print '-'*75, data, '-'*75
301        return result