Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(802)

Side by Side Diff: recipe_modules/url/resources/pycurl.py

Issue 2868333004: Add URL recipe module from "depot_tools". (Closed)
Patch Set: Update comments with exceptions Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « recipe_modules/url/example.expected/basic.json ('k') | recipe_modules/url/test_api.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright 2017 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file.
5
6 # NOTE: This was imported from Chromium's "tools/build" at revision:
7 # 65976b6e2a612439681dc42830e90dbcdf550f40
8
9 import argparse
10 import json
11 import logging
12 import os
13 import sys
14 import time
15
16 import requests
17 import requests.adapters
18 import requests.exceptions
19 import requests.models
20 from requests.packages.urllib3.util.retry import Retry
21
22
23 # Size of chunks (4MiB).
24 CHUNK_SIZE = 1024 * 1024 * 4
25
26
27 def _download(url, outfile, headers, transient_retry, strip_prefix):
28 s = requests.Session()
29 retry = None
30 if transient_retry > 0:
31 # See http://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html
32 retry = Retry(
33 total=transient_retry,
34 connect=5,
35 read=5,
36 redirect=5,
37 status_forcelist=range(500, 600),
38 backoff_factor=0.2,
39 raise_on_status=False,
40 )
41 print retry
42 s.mount(url, requests.adapters.HTTPAdapter(max_retries=retry))
43
44
45 logging.info('Connecting to %s ...', url)
46 r = s.get(url, headers=headers, stream=True)
47 if r.status_code != requests.codes.ok:
48 r.raise_for_status()
49
50 if outfile:
51 fd = open(outfile, 'wb')
52 else:
53 fd = sys.stdout
54
55 total = 0
56 with fd:
57 logging.info('Downloading %s ...', url)
58 loaded_prefix = ''
59 for chunk in r.iter_content(CHUNK_SIZE):
60 total += len(chunk)
61
62 if strip_prefix:
63 remaining_prefix = strip_prefix[len(loaded_prefix):]
64 round_prefix = chunk[:len(remaining_prefix)]
65 loaded_prefix += round_prefix
66 if round_prefix != remaining_prefix[:len(round_prefix)]:
67 raise ValueError(
68 'Expected prefix was not observed: %r != %r...' % (
69 loaded_prefix, strip_prefix))
70 chunk = chunk[len(loaded_prefix):]
71 if not chunk:
72 continue
73
74 fd.write(chunk)
75 logging.info('Downloaded %.1f MB so far', total / 1024 / 1024)
76 return r.status_code, total
77
78
79 def main():
80 parser = argparse.ArgumentParser(
81 description='Get a url and print its document.',
82 prog='./runit.py pycurl.py')
83 parser.add_argument('--url', required=True, help='the url to fetch')
84 parser.add_argument('--status-json', metavar='PATH', required=True,
85 help='Write HTTP status result JSON. If set, all complete HTTP '
86 'responses will exit with 0, regardless of their status code.')
87
88 parser.add_argument('--transient-retry', type=int, default=10,
89 help='Number of retry attempts (with exponential backoff) to make on '
90 'transient failure (default is %(default)s).')
91 parser.add_argument('--headers-json', type=argparse.FileType('r'),
92 help='A json file containing any headers to include with the request.')
93 parser.add_argument('--outfile', help='write output to this file')
94 parser.add_argument('--strip-prefix', action='store', type=json.loads,
95 help='Expect this string at the beginning of the response, and strip it.')
96
97 args = parser.parse_args()
98
99 headers = None
100 if args.headers_json:
101 headers = json.load(args.headers_json)
102
103 if args.strip_prefix and len(args.strip_prefix) > CHUNK_SIZE:
104 raise ValueError('Prefix length (%d) must be <= chunk size (%d)' % (
105 len(args.strip_prefix), CHUNK_SIZE))
106
107 status = {}
108 try:
109 status_code, size = _download(
110 args.url, args.outfile, headers, args.transient_retry,
111 args.strip_prefix)
112 status = {
113 'status_code': status_code,
114 'success': True,
115 'size': size,
116 }
117 except requests.HTTPError as e:
118 body = e.response.text
119 status = {
120 'status_code': e.response.status_code,
121 'success': False,
122 'size': len(body),
123 'error_body': body,
124 }
125
126 with open(args.status_json, 'w') as fd:
127 json.dump(status, fd)
128 return 0
129
130
131 if __name__ == '__main__':
132 logging.basicConfig()
133 logging.getLogger().setLevel(logging.INFO)
134 logging.getLogger("requests").setLevel(logging.DEBUG)
135 sys.exit(main())
136
137
138 ##
139 # The following section is read by "vpython" and used to construct the
140 # VirtualEnv for this tool.
141 #
142 # These imports were lifted from "/bootstrap/venv.cfg".
143 ##
144 # [VPYTHON:BEGIN]
145 #
146 # wheel: <
147 # name: "infra/python/wheels/cryptography/${platform}_${py_version}_${py_abi}"
148 # version: "version:1.8.1"
149 # >
150 #
151 # wheel: <
152 # name: "infra/python/wheels/appdirs-py2_py3"
153 # version: "version:1.4.3"
154 # >
155 #
156 # wheel: <
157 # name: "infra/python/wheels/asn1crypto-py2_py3"
158 # version: "version:0.22.0"
159 # >
160 #
161 # wheel: <
162 # name: "infra/python/wheels/enum34-py2"
163 # version: "version:1.1.6"
164 # >
165 #
166 # wheel: <
167 # name: "infra/python/wheels/cffi/${platform}_${py_version}_${py_abi}"
168 # version: "version:1.10.0"
169 # >
170 #
171 # wheel: <
172 # name: "infra/python/wheels/idna-py2_py3"
173 # version: "version:2.5"
174 # >
175 #
176 # wheel: <
177 # name: "infra/python/wheels/ipaddress-py2"
178 # version: "version:1.0.18"
179 # >
180 #
181 # wheel: <
182 # name: "infra/python/wheels/packaging-py2_py3"
183 # version: "version:16.8"
184 # >
185 #
186 # wheel: <
187 # name: "infra/python/wheels/pyasn1-py2_py3"
188 # version: "version:0.2.3"
189 # >
190 #
191 # wheel: <
192 # name: "infra/python/wheels/pycparser-py2_py3"
193 # version: "version:2.17"
194 # >
195 #
196 # wheel: <
197 # name: "infra/python/wheels/pyopenssl-py2_py3"
198 # version: "version:17.0.0"
199 # >
200 #
201 # wheel: <
202 # name: "infra/python/wheels/pyparsing-py2_py3"
203 # version: "version:2.2.0"
204 # >
205 #
206 # wheel: <
207 # name: "infra/python/wheels/setuptools-py2_py3"
208 # version: "version:34.3.2"
209 # >
210 #
211 # wheel: <
212 # name: "infra/python/wheels/six-py2_py3"
213 # version: "version:1.10.0"
214 # >
215 #
216 # wheel: <
217 # name: "infra/python/wheels/requests-py2_py3"
218 # version: "version:2.13.0"
219 # >
220 #
221 # [VPYTHON:END]
222 ##
OLDNEW
« no previous file with comments | « recipe_modules/url/example.expected/basic.json ('k') | recipe_modules/url/test_api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698