OLD | NEW |
---|---|
(Empty) | |
1 # Copyright 2017 The LUCI Authors. All rights reserved. | |
2 # Use of this source code is governed under the Apache License, Version 2.0 | |
3 # that can be found in the LICENSE file. | |
4 | |
5 from recipe_engine import recipe_api | |
6 | |
7 import urllib | |
8 | |
9 class UrlApi(recipe_api.RecipeApi): | |
10 quote = staticmethod(urllib.quote) | |
iannucci
2017/05/12 00:53:52
buh? staticmethod shouldn't be needed
dnj
2017/05/12 02:15:04
Done.
| |
11 urlencode = staticmethod(urllib.urlencode) | |
12 | |
13 # JSON prefix used with Gerrit and Gitiles. | |
14 GERRIT_JSON_PREFIX = ')]}\n' | |
15 | |
16 | |
17 class Response(object): | |
iannucci
2017/05/12 00:53:52
note: capture error body and stuff in different fi
dnj
2017/05/12 02:15:04
Done.
| |
18 """Response is an HTTP response object.""" | |
19 | |
20 def __init__(self, method, output, status): | |
21 self._method = method | |
22 self._status = status | |
23 self._result = output | |
24 | |
25 @property | |
26 def method(self): | |
iannucci
2017/05/12 00:53:52
DOOOOGGGGG STRIIING
dnj
2017/05/12 02:15:04
Done.
| |
27 return self._method | |
28 | |
29 @property | |
30 def status_code(self): | |
31 return self._status['status_code'] | |
32 | |
33 def raise_on_error(self): | |
34 if not self._status['success']: | |
35 raise ValueError('HTTP status (%d)' % (self.status_code,)) | |
iannucci
2017/05/12 00:53:52
real exception?
dnj
2017/05/12 02:15:04
Done.
| |
36 | |
37 @property | |
38 def output(self): | |
39 return self._result | |
40 | |
41 | |
42 @recipe_api.non_step | |
43 def join(self, *parts): | |
44 """Constructs a URL path from composite parts. | |
45 | |
46 Args: | |
47 parts (str...): Strings to concastenate. Any leading or trailing slashes | |
48 will be stripped from intermediate strings to ensure that they join | |
49 together. Trailing slashes will not be stripped from the last part. | |
50 """ | |
51 if parts: | |
52 parts = list(parts) | |
53 if len(parts) > 1: | |
54 for i, p in enumerate(parts[:-1]): | |
55 parts[i] = p.strip('/') | |
56 parts[-1] = parts[-1].lstrip('/') | |
57 return '/'.join(parts) | |
58 | |
59 def get_file(self, url, path, step_name=None, headers=None, | |
60 transient_retry=True, strip_prefix=None, **kwargs): | |
61 """GET data at given URL and writes it to file. | |
62 | |
63 Args: | |
64 url: URL to request. | |
65 path (Path): the Path where the content will be written. | |
66 step_name: optional step name, 'fetch <url>' by default. | |
67 headers: a {header_name: value} dictionary for HTTP headers. | |
68 transient_retry (bool): If True (default), transient HTTP errors (>500) | |
69 will automatically be retried with exponential backoff. If False, | |
70 exactly one attempt will be made. | |
71 strip_prefix (str or None): If not None, this prefix must be present at | |
72 the beginning of the response, and will be stripped from the resulting | |
73 content (e.g., GERRIT_JSON_PREFIX). | |
74 | |
75 Returns: | |
76 Response with "path" as its "output" value. | |
77 """ | |
78 return self._get_step(url, path, step_name, headers, transient_retry, | |
79 strip_prefix, False, **kwargs) | |
80 | |
81 def must_get_file(self, *args, **kwargs): | |
82 """Like "get_file", but always raises an exception on error.""" | |
83 resp = self.get_file(*args, **kwargs) | |
84 resp.raise_on_error() | |
85 return resp | |
86 | |
87 def get_text(self, url, step_name=None, headers=None, transient_retry=True, | |
88 **kwargs): | |
89 """GET data at given URL and writes it to file. | |
90 | |
91 Args: | |
92 url: URL to request. | |
93 step_name: optional step name, 'fetch <url>' by default. | |
94 headers: a {header_name: value} dictionary for HTTP headers. | |
95 transient_retry (bool): If True (default), transient HTTP errors (>500) | |
96 will automatically be retried with exponential backoff. If False, | |
97 exactly one attempt will be made. | |
98 | |
99 Returns: | |
100 Response with a string "output" value. | |
101 """ | |
102 return self._get_step(url, None, step_name, headers, transient_retry, | |
103 None, False, **kwargs) | |
104 | |
105 def must_get_text(self, *args, **kwargs): | |
106 """Like "get_text", but always raises an exception on error.""" | |
107 resp = self.get_text(*args, **kwargs) | |
108 resp.raise_on_error() | |
109 return resp | |
110 | |
111 def get_json(self, url, step_name=None, headers=None, transient_retry=True, | |
112 strip_prefix=None, log=False, **kwargs): | |
iannucci
2017/05/12 00:53:52
no kwargs just passthrough timeout
dnj
2017/05/12 02:15:04
Done.
| |
113 """GET data at given URL and writes it to file. | |
114 | |
115 Args: | |
116 url: URL to request. | |
117 step_name: optional step name, 'fetch <url>' by default. | |
118 headers: a {header_name: value} dictionary for HTTP headers. | |
119 transient_retry (bool): If True (default), transient HTTP errors (>500) | |
120 will automatically be retried with exponential backoff. If False, | |
121 exactly one attempt will be made. | |
122 strip_prefix (str or None): If not None, this prefix must be present at | |
123 the beginning of the response, and will be stripped from the resulting | |
124 content (e.g., GERRIT_JSON_PREFIX). | |
125 log (bool): If True, emit the JSON content as a log. | |
126 | |
127 Returns: | |
128 Response with JSON "output" value. | |
129 """ | |
130 return self._get_step(url, None, step_name, headers, transient_retry, | |
131 strip_prefix, 'log' if log else True, **kwargs) | |
132 | |
133 def must_get_json(self, *args, **kwargs): | |
134 """Like "get_json", but always raises an exception on error.""" | |
135 resp = self.get_json(*args, **kwargs) | |
136 resp.raise_on_error() | |
137 return resp | |
138 | |
139 def _get_step(self, url, path, step_name, headers, transient_retry, | |
140 strip_prefix, as_json, **kwargs): | |
141 step_name = step_name or 'GET %s' % url | |
142 | |
143 args = [ | |
144 url, | |
145 '--status-json', self.m.json.output(add_json_log=False, | |
146 name='status_json'), | |
147 ] | |
148 | |
149 if as_json: | |
150 log = as_json == 'log' | |
151 args += ['--outfile', self.m.json.output(add_json_log=log, | |
152 name='output')] | |
153 else: | |
154 args += ['--outfile', self.m.raw_io.output_text(leak_to=path, | |
155 name='output')] | |
156 | |
157 if headers: | |
158 args += ['--headers-json', self.m.json.input(headers)] | |
159 if strip_prefix: | |
160 args += ['--strip-prefix', strip_prefix] | |
161 if not transient_retry: | |
162 args.append('--no-transient-retry') | |
163 | |
164 result = self.m.python( | |
165 step_name, | |
166 self.resource('pycurl.py'), | |
167 args=args, | |
168 venv=True, | |
169 **kwargs) | |
170 status = result.json.outputs['status_json'] | |
171 | |
172 output = path | |
173 if not output: | |
174 if as_json: | |
175 output = result.json.outputs['output'] | |
176 else: | |
177 output = result.raw_io.output_texts['output'] | |
178 return self.Response('GET', output, status) | |
OLD | NEW |