Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 51a6a4c

Browse files
committed
[Patch #658094 ] PEP 301 implementation
Add the 'register' distutils command
1 parent 282e2c3 commit 51a6a4c

1 file changed

Lines changed: 293 additions & 0 deletions

File tree

Lib/distutils/command/register.py

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

0 commit comments

Comments
 (0)