ยปCore Development>Code coverage>Lib/distutils/command/upload.py

Python code coverage for Lib/distutils/command/upload.py

#countcontent
1n/a"""
2n/adistutils.command.upload
3n/a
4n/aImplements the Distutils 'upload' subcommand (upload package to a package
5n/aindex).
6n/a"""
7n/a
8n/aimport os
9n/aimport io
10n/aimport platform
11n/aimport hashlib
12n/afrom base64 import standard_b64encode
13n/afrom urllib.request import urlopen, Request, HTTPError
14n/afrom urllib.parse import urlparse
15n/afrom distutils.errors import DistutilsError, DistutilsOptionError
16n/afrom distutils.core import PyPIRCCommand
17n/afrom distutils.spawn import spawn
18n/afrom distutils import log
19n/a
20n/aclass upload(PyPIRCCommand):
21n/a
22n/a description = "upload binary package to PyPI"
23n/a
24n/a user_options = PyPIRCCommand.user_options + [
25n/a ('sign', 's',
26n/a 'sign files to upload using gpg'),
27n/a ('identity=', 'i', 'GPG identity used to sign files'),
28n/a ]
29n/a
30n/a boolean_options = PyPIRCCommand.boolean_options + ['sign']
31n/a
32n/a def initialize_options(self):
33n/a PyPIRCCommand.initialize_options(self)
34n/a self.username = ''
35n/a self.password = ''
36n/a self.show_response = 0
37n/a self.sign = False
38n/a self.identity = None
39n/a
40n/a def finalize_options(self):
41n/a PyPIRCCommand.finalize_options(self)
42n/a if self.identity and not self.sign:
43n/a raise DistutilsOptionError(
44n/a "Must use --sign for --identity to have meaning"
45n/a )
46n/a config = self._read_pypirc()
47n/a if config != {}:
48n/a self.username = config['username']
49n/a self.password = config['password']
50n/a self.repository = config['repository']
51n/a self.realm = config['realm']
52n/a
53n/a # getting the password from the distribution
54n/a # if previously set by the register command
55n/a if not self.password and self.distribution.password:
56n/a self.password = self.distribution.password
57n/a
58n/a def run(self):
59n/a if not self.distribution.dist_files:
60n/a msg = "No dist file created in earlier command"
61n/a raise DistutilsOptionError(msg)
62n/a for command, pyversion, filename in self.distribution.dist_files:
63n/a self.upload_file(command, pyversion, filename)
64n/a
65n/a def upload_file(self, command, pyversion, filename):
66n/a # Makes sure the repository URL is compliant
67n/a schema, netloc, url, params, query, fragments = \
68n/a urlparse(self.repository)
69n/a if params or query or fragments:
70n/a raise AssertionError("Incompatible url %s" % self.repository)
71n/a
72n/a if schema not in ('http', 'https'):
73n/a raise AssertionError("unsupported schema " + schema)
74n/a
75n/a # Sign if requested
76n/a if self.sign:
77n/a gpg_args = ["gpg", "--detach-sign", "-a", filename]
78n/a if self.identity:
79n/a gpg_args[2:2] = ["--local-user", self.identity]
80n/a spawn(gpg_args,
81n/a dry_run=self.dry_run)
82n/a
83n/a # Fill in the data - send all the meta-data in case we need to
84n/a # register a new release
85n/a f = open(filename,'rb')
86n/a try:
87n/a content = f.read()
88n/a finally:
89n/a f.close()
90n/a meta = self.distribution.metadata
91n/a data = {
92n/a # action
93n/a ':action': 'file_upload',
94n/a 'protocol_version': '1',
95n/a
96n/a # identify release
97n/a 'name': meta.get_name(),
98n/a 'version': meta.get_version(),
99n/a
100n/a # file content
101n/a 'content': (os.path.basename(filename),content),
102n/a 'filetype': command,
103n/a 'pyversion': pyversion,
104n/a 'md5_digest': hashlib.md5(content).hexdigest(),
105n/a
106n/a # additional meta-data
107n/a 'metadata_version': '1.0',
108n/a 'summary': meta.get_description(),
109n/a 'home_page': meta.get_url(),
110n/a 'author': meta.get_contact(),
111n/a 'author_email': meta.get_contact_email(),
112n/a 'license': meta.get_licence(),
113n/a 'description': meta.get_long_description(),
114n/a 'keywords': meta.get_keywords(),
115n/a 'platform': meta.get_platforms(),
116n/a 'classifiers': meta.get_classifiers(),
117n/a 'download_url': meta.get_download_url(),
118n/a # PEP 314
119n/a 'provides': meta.get_provides(),
120n/a 'requires': meta.get_requires(),
121n/a 'obsoletes': meta.get_obsoletes(),
122n/a }
123n/a comment = ''
124n/a if command == 'bdist_rpm':
125n/a dist, version, id = platform.dist()
126n/a if dist:
127n/a comment = 'built for %s %s' % (dist, version)
128n/a elif command == 'bdist_dumb':
129n/a comment = 'built for %s' % platform.platform(terse=1)
130n/a data['comment'] = comment
131n/a
132n/a if self.sign:
133n/a data['gpg_signature'] = (os.path.basename(filename) + ".asc",
134n/a open(filename+".asc", "rb").read())
135n/a
136n/a # set up the authentication
137n/a user_pass = (self.username + ":" + self.password).encode('ascii')
138n/a # The exact encoding of the authentication string is debated.
139n/a # Anyway PyPI only accepts ascii for both username or password.
140n/a auth = "Basic " + standard_b64encode(user_pass).decode('ascii')
141n/a
142n/a # Build up the MIME payload for the POST data
143n/a boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
144n/a sep_boundary = b'\r\n--' + boundary.encode('ascii')
145n/a end_boundary = sep_boundary + b'--\r\n'
146n/a body = io.BytesIO()
147n/a for key, value in data.items():
148n/a title = '\r\nContent-Disposition: form-data; name="%s"' % key
149n/a # handle multiple entries for the same name
150n/a if not isinstance(value, list):
151n/a value = [value]
152n/a for value in value:
153n/a if type(value) is tuple:
154n/a title += '; filename="%s"' % value[0]
155n/a value = value[1]
156n/a else:
157n/a value = str(value).encode('utf-8')
158n/a body.write(sep_boundary)
159n/a body.write(title.encode('utf-8'))
160n/a body.write(b"\r\n\r\n")
161n/a body.write(value)
162n/a if value and value[-1:] == b'\r':
163n/a body.write(b'\n') # write an extra newline (lurve Macs)
164n/a body.write(end_boundary)
165n/a body = body.getvalue()
166n/a
167n/a msg = "Submitting %s to %s" % (filename, self.repository)
168n/a self.announce(msg, log.INFO)
169n/a
170n/a # build the Request
171n/a headers = {
172n/a 'Content-type': 'multipart/form-data; boundary=%s' % boundary,
173n/a 'Content-length': str(len(body)),
174n/a 'Authorization': auth,
175n/a }
176n/a
177n/a request = Request(self.repository, data=body,
178n/a headers=headers)
179n/a # send the data
180n/a try:
181n/a result = urlopen(request)
182n/a status = result.getcode()
183n/a reason = result.msg
184n/a except HTTPError as e:
185n/a status = e.code
186n/a reason = e.msg
187n/a except OSError as e:
188n/a self.announce(str(e), log.ERROR)
189n/a raise
190n/a
191n/a if status == 200:
192n/a self.announce('Server response (%s): %s' % (status, reason),
193n/a log.INFO)
194n/a if self.show_response:
195n/a text = self._read_pypi_response(result)
196n/a msg = '\n'.join(('-' * 75, text, '-' * 75))
197n/a self.announce(msg, log.INFO)
198n/a else:
199n/a msg = 'Upload failed (%s): %s' % (status, reason)
200n/a self.announce(msg, log.ERROR)
201n/a raise DistutilsError(msg)