1 """distutils.command.register
3 Implements the Distutils 'register' command (register with the repository).
6 # created 2002/10/21, Richard Jones
10 import os
, string
, urllib2
, getpass
, urlparse
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'),
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
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
45 self
.finalize_options()
49 for cmd_name
in self
.get_sub_commands():
50 self
.run_command(cmd_name
)
53 self
.verify_metadata()
54 elif self
.list_classifiers
:
59 def check_metadata(self
):
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
69 def _set_config(self
):
70 ''' Reads the configuration file and set attributes.
72 config
= self
._read
_pypirc
()
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
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.
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.
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
120 Otherwise, to figure who the user is, we offer the user three
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
132 username
= self
.username
133 password
= self
.password
136 username
= password
= ''
138 # get the user's login info
139 choices
= '1 2 3 4'.split()
140 while choice
not in choices
:
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
147 Your selection [default 1]: ''', log
.INFO
)
152 elif choice
not in choices
:
153 print 'Please choose one of the four options!'
156 # get the username and password
158 username
= raw_input('Username: ')
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'),
169 self
.announce('Server response (%s): %s' % (code
, result
),
172 # possibly save the login
175 # sharing the password in the distribution instance
176 # so the upload command can reuse it
177 self
.distribution
.password
= password
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
)
184 while choice
.lower() not in 'yn':
185 choice
= raw_input('Save your login (y/N)?')
188 if choice
.lower() == 'y':
189 self
._store
_pypirc
(username
, password
)
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
)
210 log
.info('Server response (%s): %s' % (code
, result
))
212 log
.info('You will receive an email shortly.')
213 log
.info(('Follow the instructions in it to '
214 'complete registration.'))
216 data
= {':action': 'password_reset'}
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
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(),
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'
251 def post_to_server(self
, data
, auth
=None):
252 ''' Post a query to the server, and return a string response.
255 self
.announce('Registering %s to %s' % (data
['name'],
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( () )):
268 value
= unicode(value
).encode("utf-8")
269 body
.write(sep_boundary
)
270 body
.write('\nContent-Disposition: form-data; name="%s"'%key
)
273 if value
and value
[-1] == '\r':
274 body
.write('\n') # write an extra newline (lurve Macs)
275 body
.write(end_boundary
)
277 body
= body
.getvalue()
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
)
292 result
= opener
.open(req
)
293 except urllib2
.HTTPError
, e
:
294 if self
.show_response
:
296 result
= e
.code
, e
.msg
297 except urllib2
.URLError
, e
:
300 if self
.show_response
:
303 if self
.show_response
:
305 self
.announce('%s%s%s' % (dashes
, data
, dashes
))