0001-data_source-handle-err-in-JSON-outputs-41195.patch
tests/test_datasource.py | ||
---|---|---|
296 | 296 |
assert 'Error loading JSON data source' in caplog.records[-1].message |
297 | 297 |
assert 'error' in caplog.records[-1].message |
298 | 298 | |
299 |
datasource = {'type': 'json', 'value': 'http://remote.example.net/json-list-err1'} |
|
300 |
assert data_sources.get_items(datasource) == [] |
|
301 |
assert 'Error reading JSON data source output (err 1)' in caplog.records[-1].message |
|
302 | ||
299 | 303 | |
300 | 304 |
def test_json_datasource_bad_url_scheme(caplog): |
301 | 305 |
datasource = {'type': 'json', 'value': ''} |
... | ... | |
496 | 500 |
assert urlopen.call_count == 3 |
497 | 501 | |
498 | 502 | |
503 |
def test_named_datasource_id_parameter(requests_pub): |
|
504 |
NamedDataSource.wipe() |
|
505 |
datasource = NamedDataSource(name='foobar') |
|
506 |
datasource.data_source = {'type': 'json', 'value': 'http://whatever/'} |
|
507 |
datasource.id_parameter = 'id' |
|
508 |
datasource.store() |
|
509 | ||
510 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
511 |
value = [{'id': '1', 'text': 'foo'}] |
|
512 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({'data': value})) |
|
513 |
assert datasource.get_structured_value('1') == value[0] |
|
514 |
assert urlopen.call_count == 1 |
|
515 |
assert urlopen.call_args[0][0] == 'http://whatever/?id=1' |
|
516 | ||
517 |
# try again, get from request.datasources_cache |
|
518 |
assert datasource.get_structured_value('1') == value[0] |
|
519 |
assert urlopen.call_count == 1 # no new call |
|
520 | ||
521 |
get_request().datasources_cache = {} |
|
522 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
523 |
value = [{'id': '1', 'text': 'bar'}, {'id': '2', 'text': 'foo'}] |
|
524 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({'data': value})) |
|
525 |
assert datasource.get_structured_value('1') == value[0] |
|
526 |
assert urlopen.call_count == 1 |
|
527 | ||
528 |
get_request().datasources_cache = {} |
|
529 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
530 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({'data': []})) # empty list |
|
531 |
assert datasource.get_structured_value('1') is None |
|
532 |
assert urlopen.call_count == 1 |
|
533 | ||
534 |
get_request().datasources_cache = {} |
|
535 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
536 |
value = [{'id': '1', 'text': 'foo'}] |
|
537 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({'data': value, 'err': 0})) |
|
538 |
assert datasource.get_structured_value('1') == value[0] |
|
539 |
assert urlopen.call_count == 1 |
|
540 | ||
541 |
get_request().datasources_cache = {} |
|
542 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
543 |
value = [{'id': '1', 'text': 'foo'}] |
|
544 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({'data': value, 'err': 1})) |
|
545 |
assert datasource.get_structured_value('1') is None |
|
546 |
assert urlopen.call_count == 1 |
|
547 |
# no cache for errors |
|
548 |
assert datasource.get_structured_value('1') is None |
|
549 |
assert urlopen.call_count == 2 # called again |
|
550 | ||
551 |
get_request().datasources_cache = {} |
|
552 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
553 |
value = {'id': '1', 'text': 'foo'} # not a list |
|
554 |
urlopen.side_effect = lambda *args: StringIO(json.dumps({'data': value})) |
|
555 |
assert datasource.get_structured_value('1') is None |
|
556 |
assert urlopen.call_count == 1 |
|
557 | ||
558 |
get_request().datasources_cache = {} |
|
559 |
with mock.patch('wcs.qommon.misc.urlopen') as urlopen: |
|
560 |
urlopen.side_effect = lambda *args: StringIO('not json') |
|
561 |
assert datasource.get_structured_value('1') is None |
|
562 |
assert urlopen.call_count == 1 |
|
563 | ||
564 | ||
499 | 565 |
def test_named_datasource_in_formdef(): |
500 | 566 |
from wcs.formdef import FormDef |
501 | 567 |
datasource = NamedDataSource(name='foobar') |
tests/utilities.py | ||
---|---|---|
329 | 329 |
'http://remote.example.net/json-list': (200, '{"data": [{"id": "a", "text": "b"}]}', None), |
330 | 330 |
'http://remote.example.net/json-err0': (200, '{"data": "foo", "err": 0}', None), |
331 | 331 |
'http://remote.example.net/json-err1': (200, '{"data": "", "err": 1}', None), |
332 |
'http://remote.example.net/json-list-err1': (200, '{"data": [{"id": "a", "text": "b"}], "err": 1}', None), |
|
332 | 333 |
'http://remote.example.net/json-errstr': (200, '{"data": "", "err": "bug"}', None), |
333 | 334 |
'http://remote.example.net/json-errheader0': (200, '{"foo": "bar"}', |
334 | 335 |
{'x-error-code': '0'}), |
wcs/data_sources.py | ||
---|---|---|
116 | 116 |
return tupled_items |
117 | 117 | |
118 | 118 | |
119 |
def request_json_items(url): |
|
120 |
url = sign_url_auto_orig(url) |
|
121 |
try: |
|
122 |
entries = misc.json_loads(misc.urlopen(url).read()) |
|
123 |
if not isinstance(entries, dict): |
|
124 |
raise ValueError('not a json dict') |
|
125 |
if entries.get('err') not in (None, 0, "0"): |
|
126 |
raise ValueError('err %s' % entries['err']) |
|
127 |
if not isinstance(entries.get('data'), list): |
|
128 |
raise ValueError('not a json dict with a data list attribute') |
|
129 |
except misc.ConnectionError as e: |
|
130 |
get_logger().warn('Error loading JSON data source (%s)' % str(e)) |
|
131 |
return None |
|
132 |
except (ValueError, TypeError) as e: |
|
133 |
get_logger().warn('Error reading JSON data source output (%s)' % str(e)) |
|
134 |
return None |
|
135 |
items = [] |
|
136 |
for item in entries.get('data'): |
|
137 |
# skip malformed items |
|
138 |
if item.get('id') is None or item.get('id') == '': |
|
139 |
continue |
|
140 |
if 'text' not in item: |
|
141 |
item['text'] = item['id'] |
|
142 |
items.append(item) |
|
143 |
return items |
|
144 | ||
145 | ||
119 | 146 |
def get_structured_items(data_source, mode=None): |
120 | 147 |
cache_duration = 0 |
121 | 148 | |
... | ... | |
195 | 222 |
if items is not None: |
196 | 223 |
return items |
197 | 224 | |
198 |
unsigned_url = url |
|
199 |
url = sign_url_auto_orig(url) |
|
200 |
try: |
|
201 |
entries = misc.json_loads(misc.urlopen(url).read()) |
|
202 |
if type(entries) is not dict: |
|
203 |
raise ValueError('not a json dict') |
|
204 |
if type(entries.get('data')) is not list: |
|
205 |
raise ValueError('not a json dict with a data list attribute') |
|
206 |
items = [] |
|
207 |
for item in entries.get('data'): |
|
208 |
# skip malformed items |
|
209 |
if item.get('id') is None or item.get('id') == '': |
|
210 |
continue |
|
211 |
if 'text' not in item: |
|
212 |
item['text'] = item['id'] |
|
213 |
items.append(item) |
|
214 |
if hasattr(request, 'datasources_cache'): |
|
215 |
request.datasources_cache[unsigned_url] = items |
|
216 | ||
217 |
if cache_duration: |
|
218 |
cache.set(cache_key, items, cache_duration) |
|
219 | ||
220 |
return items |
|
221 |
except misc.ConnectionError as e: |
|
222 |
get_logger().warn('Error loading JSON data source (%s)' % str(e)) |
|
223 |
except ValueError as e: |
|
224 |
get_logger().warn('Error reading JSON data source output (%s)' % str(e)) |
|
225 |
items = request_json_items(url) |
|
226 |
if items is None: |
|
227 |
return [] |
|
228 |
if hasattr(request, 'datasources_cache'): |
|
229 |
request.datasources_cache[url] = items |
|
230 |
if cache_duration: |
|
231 |
cache.set(cache_key, items, cache_duration) |
|
232 |
return items |
|
225 | 233 |
return [] |
226 | 234 | |
227 | 235 | |
... | ... | |
356 | 364 |
get_session().get_data_source_query_url_token(self.get_json_query_url())) |
357 | 365 |
return None |
358 | 366 | |
359 |
def load_json(self, param_name, param_value):
|
|
367 |
def get_value_by_id(self, param_name, param_value):
|
|
360 | 368 |
url = self.data_source.get('value').strip() |
361 | 369 |
if Template.is_template_string(url): |
362 | 370 |
vars = get_publisher().substitutions.get_context_variables(mode='lazy') |
... | ... | |
370 | 378 | |
371 | 379 |
request = get_request() |
372 | 380 |
if hasattr(request, 'datasources_cache') and url in request.datasources_cache: |
373 |
return request.datasources_cache[url] |
|
381 |
items = request.datasources_cache[url] |
|
382 |
if not items: # cache may contains empty list from get_structured_items |
|
383 |
return None |
|
384 |
return items[0] |
|
374 | 385 | |
375 |
unsigned_url = url
|
|
376 |
url = sign_url_auto_orig(url)
|
|
377 |
resp = misc.urlopen(url).read()
|
|
386 |
items = request_json_items(url)
|
|
387 |
if not items: # None or empty list are not valid
|
|
388 |
return None
|
|
378 | 389 |
if hasattr(request, 'datasources_cache'): |
379 |
request.datasources_cache[unsigned_url] = resp
|
|
380 |
return resp
|
|
390 |
request.datasources_cache[url] = items
|
|
391 |
return items[0]
|
|
381 | 392 | |
382 | 393 |
def get_display_value(self, option_id): |
383 | 394 |
value = self.get_structured_value(option_id) |
... | ... | |
388 | 399 |
def get_structured_value(self, option_id): |
389 | 400 |
value = None |
390 | 401 |
if self.type == 'json' and self.id_parameter: |
391 |
resp = self.load_json(self.id_parameter, option_id) |
|
392 |
response = misc.json_loads(resp) |
|
393 |
if response['data']: |
|
394 |
value = response['data'][0] |
|
402 |
value = self.get_value_by_id(self.id_parameter, option_id) |
|
395 | 403 |
else: |
396 | 404 |
structured_items = get_structured_items(self.data_source, mode='lazy') |
397 | 405 |
for item in structured_items: |
398 |
- |