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

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

Issue 2868333004: Add URL recipe module from "depot_tools". (Closed)
Patch Set: response api object 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
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.models
19 from requests.packages.urllib3.util.retry import Retry
20
21
22 class Output(object):
23 """Output is a file-like object which writes content to a sink.
24
25 If a prefix is supplied, it will validate and discard that prefix before
26 writing output. If the prefix did not validate, a ValueError will be raised.
27 """
28
29 def __init__(self, sink, prefix=None):
30 self.total = 0
31
32 self._sink = sink
33 self._prefix = prefix
34 self._prefix_idx = 0
35 self._prefix_buf = []
36
37 def write(self, content):
iannucci 2017/05/12 00:53:52 assert len(prefix) < CHUNK_SIZE, use iter() interf
dnj 2017/05/12 02:15:04 Done.
38 self.total += len(content)
39
40 # If we still have prefix remaining, read and validate it.
41 if self._prefix and self._prefix_idx < len(self._prefix):
42 prefix_part = self._prefix[self._prefix_idx:][:len(content)]
43 d = content[:len(prefix_part)]
44 self._prefix_buf.append(d)
45 self._prefix_idx += len(prefix_part)
46
47 if d != prefix_part:
48 total_prefix = ''.join(self._prefix_buf)
49 raise ValueError(
50 'Expected prefix was not observed: [%s] != [%s]...' % (
51 total_prefix, self._prefix[:len(total_prefix)]))
52 content = content[len(prefix_part):]
53
54 if content:
55 self._sink.write(content)
56
57
58 def _download(url, outfile, headers, transient_retry, strip_prefix):
59 s = requests.Session()
60 if transient_retry:
61 # See http://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html
62 retry = Retry(
63 total=10,
64 connect=5,
65 read=5,
66 redirect=5,
67 status_forcelist=range(500, 600),
68 backoff_factor=0.2,
69 )
70 print retry
71 s.mount(url, requests.adapters.HTTPAdapter(max_retries=retry))
72
73
74 logging.info('Connecting...')
75 r = s.get(url, headers=headers, stream=True)
76 if r.status_code != requests.codes.ok:
77 r.raise_for_status()
78
79 if outfile:
80 fd = open(outfile, 'wb')
81 else:
82 fd = sys.stdout
83 with fd:
84 out = Output(fd, prefix=strip_prefix)
85 logging.info('Downloading...')
86 for chunk in r.iter_content(1024*1024):
87 out.write(chunk)
88 logging.info('Downloaded %.1f MB so far', out.total / 1024 / 1024)
89 return r.status_code, out.total
90
91
92 def main():
93 parser = argparse.ArgumentParser(
94 description='Get a url and print its document.',
95 prog='./runit.py pycurl.py')
96 parser.add_argument('url', help='the url to fetch')
iannucci 2017/05/12 00:53:52 urlparse validate (up in the recipe)?
dnj 2017/05/12 02:15:04 Done.
97 parser.add_argument('--status-json', metavar='PATH', action='store',
iannucci 2017/05/12 00:53:52 required=True ?
dnj 2017/05/12 02:15:04 Done.
98 help='Write HTTP status result JSON. If set, all complete HTTP '
99 'responses will exit with 0, regardless of their status code.')
100 parser.add_argument('--no-transient-retry', action='store_true',
101 help='Do not perform automatic retries on transient failures.')
102 parser.add_argument('--headers-json', action='store',
103 help='A json file containing any headers to include with the request.')
104 parser.add_argument('--outfile', help='write output to this file')
105 parser.add_argument('--strip-prefix', action='store',
106 help='Expect this string at the beginning of the response, and strip it.')
107
108 args = parser.parse_args()
109
110 headers = None
111 if args.headers_json:
112 with open(args.headers_json, 'r') as json_file:
113 headers = json.load(json_file)
114
115 status = {}
116 try:
117 status_code, size = _download(
118 args.url, args.outfile, headers, not args.no_transient_retry,
119 args.strip_prefix)
120 status = {
121 'status_code': status_code,
122 'success': True,
123 'size': size,
124 }
125 except requests.HTTPError as e:
126 if not args.status_json:
127 raise
128 status = {
129 'status_code': e.response.status_code,
130 'success': False,
131 }
132
133 if args.status_json:
134 with open(args.status_json, 'w') as fd:
135 json.dump(status, fd)
136 return 0
137
138
139 if __name__ == '__main__':
140 logging.basicConfig()
141 logging.getLogger().setLevel(logging.INFO)
142 logging.getLogger("requests").setLevel(logging.DEBUG)
143 sys.exit(main())
144
145
146 ##
147 # The following section is read by "vpython" and used to construct the
148 # VirtualEnv for this tool.
149 #
150 # These imports were lifted from "/bootstrap/venv.cfg".
151 ##
152 # [VPYTHON:BEGIN]
153 #
154 # wheel: <
155 # name: "infra/python/wheels/cryptography/${platform}_${py_version}_${py_abi}"
156 # version: "version:1.8.1"
157 # >
158 #
159 # wheel: <
160 # name: "infra/python/wheels/appdirs-py2_py3"
161 # version: "version:1.4.3"
162 # >
163 #
164 # wheel: <
165 # name: "infra/python/wheels/asn1crypto-py2_py3"
166 # version: "version:0.22.0"
167 # >
168 #
169 # wheel: <
170 # name: "infra/python/wheels/enum34-py2"
171 # version: "version:1.1.6"
172 # >
173 #
174 # wheel: <
175 # name: "infra/python/wheels/cffi/${platform}_${py_version}_${py_abi}"
176 # version: "version:1.10.0"
177 # >
178 #
179 # wheel: <
180 # name: "infra/python/wheels/idna-py2_py3"
181 # version: "version:2.5"
182 # >
183 #
184 # wheel: <
185 # name: "infra/python/wheels/ipaddress-py2"
186 # version: "version:1.0.18"
187 # >
188 #
189 # wheel: <
190 # name: "infra/python/wheels/packaging-py2_py3"
191 # version: "version:16.8"
192 # >
193 #
194 # wheel: <
195 # name: "infra/python/wheels/pyasn1-py2_py3"
196 # version: "version:0.2.3"
197 # >
198 #
199 # wheel: <
200 # name: "infra/python/wheels/pycparser-py2_py3"
201 # version: "version:2.17"
202 # >
203 #
204 # wheel: <
205 # name: "infra/python/wheels/pyopenssl-py2_py3"
206 # version: "version:17.0.0"
207 # >
208 #
209 # wheel: <
210 # name: "infra/python/wheels/pyparsing-py2_py3"
211 # version: "version:2.2.0"
212 # >
213 #
214 # wheel: <
215 # name: "infra/python/wheels/setuptools-py2_py3"
216 # version: "version:34.3.2"
217 # >
218 #
219 # wheel: <
220 # name: "infra/python/wheels/six-py2_py3"
221 # version: "version:1.10.0"
222 # >
223 #
224 # wheel: <
225 # name: "infra/python/wheels/requests-py2_py3"
226 # version: "version:2.13.0"
227 # >
228 #
229 # [VPYTHON:END]
230 ##
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698