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