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