Projet

Général

Profil

0001-data_source-handle-err-in-JSON-outputs-41195.patch

Thomas Noël, 01 avril 2020 14:39

Télécharger (10 ko)

Voir les différences:

Subject: [PATCH] data_source: handle err in JSON outputs (#41195)

 tests/test_datasource.py | 66 +++++++++++++++++++++++++++++++
 tests/utilities.py       |  1 +
 wcs/data_sources.py      | 84 ++++++++++++++++++++++------------------
 3 files changed, 113 insertions(+), 38 deletions(-)
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
-