ยปCore Development>Code coverage>Lib/packaging/command/register.py

Python code coverage for Lib/packaging/command/register.py

#countcontent
1n/a"""Register a release with a project index."""
2n/a
3n/a# Contributed by Richard Jones
4n/a
5n/aimport getpass
6n/aimport urllib.error
7n/aimport urllib.parse
8n/aimport urllib.request
9n/a
10n/afrom packaging import logger
11n/afrom packaging.util import (read_pypirc, generate_pypirc, DEFAULT_REPOSITORY,
12n/a DEFAULT_REALM, get_pypirc_path, encode_multipart)
13n/afrom packaging.command.cmd import Command
14n/a
15n/aclass register(Command):
16n/a
17n/a description = "register a release with PyPI"
18n/a user_options = [
19n/a ('repository=', 'r',
20n/a "repository URL [default: %s]" % DEFAULT_REPOSITORY),
21n/a ('show-response', None,
22n/a "display full response text from server"),
23n/a ('list-classifiers', None,
24n/a "list valid Trove classifiers"),
25n/a ('strict', None ,
26n/a "stop the registration if the metadata is not fully compliant")
27n/a ]
28n/a
29n/a boolean_options = ['show-response', 'list-classifiers', 'strict']
30n/a
31n/a def initialize_options(self):
32n/a self.repository = None
33n/a self.realm = None
34n/a self.show_response = False
35n/a self.list_classifiers = False
36n/a self.strict = False
37n/a
38n/a def finalize_options(self):
39n/a if self.repository is None:
40n/a self.repository = DEFAULT_REPOSITORY
41n/a if self.realm is None:
42n/a self.realm = DEFAULT_REALM
43n/a
44n/a def run(self):
45n/a self._set_config()
46n/a
47n/a # Check the package metadata
48n/a check = self.distribution.get_command_obj('check')
49n/a if check.strict != self.strict and not check.all:
50n/a # If check was already run but with different options,
51n/a # re-run it
52n/a check.strict = self.strict
53n/a check.all = True
54n/a self.distribution.have_run.pop('check', None)
55n/a self.run_command('check')
56n/a
57n/a if self.dry_run:
58n/a self.verify_metadata()
59n/a elif self.list_classifiers:
60n/a self.classifiers()
61n/a else:
62n/a self.send_metadata()
63n/a
64n/a def _set_config(self):
65n/a ''' Reads the configuration file and set attributes.
66n/a '''
67n/a config = read_pypirc(self.repository, self.realm)
68n/a if config != {}:
69n/a self.username = config['username']
70n/a self.password = config['password']
71n/a self.repository = config['repository']
72n/a self.realm = config['realm']
73n/a self.has_config = True
74n/a else:
75n/a if self.repository not in ('pypi', DEFAULT_REPOSITORY):
76n/a raise ValueError('%s not found in .pypirc' % self.repository)
77n/a if self.repository == 'pypi':
78n/a self.repository = DEFAULT_REPOSITORY
79n/a self.has_config = False
80n/a
81n/a def classifiers(self):
82n/a ''' Fetch the list of classifiers from the server.
83n/a '''
84n/a response = urllib.request.urlopen(self.repository+'?:action=list_classifiers')
85n/a logger.info(response.read())
86n/a
87n/a def verify_metadata(self):
88n/a ''' Send the metadata to the package index server to be checked.
89n/a '''
90n/a # send the info to the server and report the result
91n/a code, result = self.post_to_server(self.build_post_data('verify'))
92n/a logger.info('server response (%s): %s', code, result)
93n/a
94n/a
95n/a def send_metadata(self):
96n/a ''' Send the metadata to the package index server.
97n/a
98n/a Well, do the following:
99n/a 1. figure who the user is, and then
100n/a 2. send the data as a Basic auth'ed POST.
101n/a
102n/a First we try to read the username/password from $HOME/.pypirc,
103n/a which is a ConfigParser-formatted file with a section
104n/a [distutils] containing username and password entries (both
105n/a in clear text). Eg:
106n/a
107n/a [distutils]
108n/a index-servers =
109n/a pypi
110n/a
111n/a [pypi]
112n/a username: fred
113n/a password: sekrit
114n/a
115n/a Otherwise, to figure who the user is, we offer the user three
116n/a choices:
117n/a
118n/a 1. use existing login,
119n/a 2. register as a new user, or
120n/a 3. set the password to a random string and email the user.
121n/a
122n/a '''
123n/a # TODO factor registration out into another method
124n/a # TODO use print to print, not logging
125n/a
126n/a # see if we can short-cut and get the username/password from the
127n/a # config
128n/a if self.has_config:
129n/a choice = '1'
130n/a username = self.username
131n/a password = self.password
132n/a else:
133n/a choice = 'x'
134n/a username = password = ''
135n/a
136n/a # get the user's login info
137n/a choices = '1 2 3 4'.split()
138n/a while choice not in choices:
139n/a logger.info('''\
140n/aWe need to know who you are, so please choose either:
141n/a 1. use your existing login,
142n/a 2. register as a new user,
143n/a 3. have the server generate a new password for you (and email it to you), or
144n/a 4. quit
145n/aYour selection [default 1]: ''')
146n/a
147n/a choice = input()
148n/a if not choice:
149n/a choice = '1'
150n/a elif choice not in choices:
151n/a print('Please choose one of the four options!')
152n/a
153n/a if choice == '1':
154n/a # get the username and password
155n/a while not username:
156n/a username = input('Username: ')
157n/a while not password:
158n/a password = getpass.getpass('Password: ')
159n/a
160n/a # set up the authentication
161n/a auth = urllib.request.HTTPPasswordMgr()
162n/a host = urllib.parse.urlparse(self.repository)[1]
163n/a auth.add_password(self.realm, host, username, password)
164n/a # send the info to the server and report the result
165n/a code, result = self.post_to_server(self.build_post_data('submit'),
166n/a auth)
167n/a logger.info('Server response (%s): %s', code, result)
168n/a
169n/a # possibly save the login
170n/a if code == 200:
171n/a if self.has_config:
172n/a # sharing the password in the distribution instance
173n/a # so the upload command can reuse it
174n/a self.distribution.password = password
175n/a else:
176n/a logger.info(
177n/a 'I can store your PyPI login so future submissions '
178n/a 'will be faster.\n(the login will be stored in %s)',
179n/a get_pypirc_path())
180n/a choice = 'X'
181n/a while choice.lower() not in ('y', 'n'):
182n/a choice = input('Save your login (y/N)?')
183n/a if not choice:
184n/a choice = 'n'
185n/a if choice.lower() == 'y':
186n/a generate_pypirc(username, password)
187n/a
188n/a elif choice == '2':
189n/a data = {':action': 'user'}
190n/a data['name'] = data['password'] = data['email'] = ''
191n/a data['confirm'] = None
192n/a while not data['name']:
193n/a data['name'] = input('Username: ')
194n/a while data['password'] != data['confirm']:
195n/a while not data['password']:
196n/a data['password'] = getpass.getpass('Password: ')
197n/a while not data['confirm']:
198n/a data['confirm'] = getpass.getpass(' Confirm: ')
199n/a if data['password'] != data['confirm']:
200n/a data['password'] = ''
201n/a data['confirm'] = None
202n/a print("Password and confirm don't match!")
203n/a while not data['email']:
204n/a data['email'] = input(' EMail: ')
205n/a code, result = self.post_to_server(data)
206n/a if code != 200:
207n/a logger.info('server response (%s): %s', code, result)
208n/a else:
209n/a logger.info('you will receive an email shortly; follow the '
210n/a 'instructions in it to complete registration.')
211n/a elif choice == '3':
212n/a data = {':action': 'password_reset'}
213n/a data['email'] = ''
214n/a while not data['email']:
215n/a data['email'] = input('Your email address: ')
216n/a code, result = self.post_to_server(data)
217n/a logger.info('server response (%s): %s', code, result)
218n/a
219n/a def build_post_data(self, action):
220n/a # figure the data to send - the metadata plus some additional
221n/a # information used by the package server
222n/a data = self.distribution.metadata.todict()
223n/a data[':action'] = action
224n/a return data
225n/a
226n/a # XXX to be refactored with upload.upload_file
227n/a def post_to_server(self, data, auth=None):
228n/a ''' Post a query to the server, and return a string response.
229n/a '''
230n/a if 'name' in data:
231n/a logger.info('Registering %s to %s', data['name'], self.repository)
232n/a # Build up the MIME payload for the urllib2 POST data
233n/a content_type, body = encode_multipart(data.items(), [])
234n/a
235n/a # build the Request
236n/a headers = {
237n/a 'Content-type': content_type,
238n/a 'Content-length': str(len(body))
239n/a }
240n/a req = urllib.request.Request(self.repository, body, headers)
241n/a
242n/a # handle HTTP and include the Basic Auth handler
243n/a opener = urllib.request.build_opener(
244n/a urllib.request.HTTPBasicAuthHandler(password_mgr=auth)
245n/a )
246n/a data = ''
247n/a try:
248n/a result = opener.open(req)
249n/a except urllib.error.HTTPError as e:
250n/a if self.show_response:
251n/a data = e.fp.read()
252n/a result = e.code, e.msg
253n/a except urllib.error.URLError as e:
254n/a result = 500, str(e)
255n/a else:
256n/a if self.show_response:
257n/a data = result.read()
258n/a result = 200, 'OK'
259n/a if self.show_response:
260n/a dashes = '-' * 75
261n/a logger.info('%s%s%s', dashes, data, dashes)
262n/a
263n/a return result