From a4cc5dc81364a7300f0ba0bd5711633e803c250a Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 1 May 2018 17:08:16 -0700 Subject: [PATCH] New ?_shape=array option + tweaks to _shape, closes #245 * Default is now ?_shape=arrays (renamed from lists) * New ?_shape=array returns an array of objects as the root object * Changed ?_shape=object to return the object as the root * Updated docs --- datasette/app.py | 17 +++++++++++++---- docs/json_api.rst | 32 +++++++++++++++++++++++++++----- tests/test_api.py | 38 ++++++++++++++++++++++++++++++++++---- 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/datasette/app.py b/datasette/app.py index 30f0d7a1..ec1c8cbd 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -253,7 +253,7 @@ class BaseView(RenderMixin): forward_querystring=False ) # Deal with the _shape option - shape = request.args.get('_shape', 'lists') + shape = request.args.get('_shape', 'arrays') if shape in ('objects', 'object', 'array'): columns = data.get('columns') rows = data.get('rows') @@ -275,7 +275,7 @@ class BaseView(RenderMixin): for row in data['rows']: pk_string = path_from_row_pks(row, pks, not pks) object_rows[pk_string] = row - data['rows'] = object_rows + data = object_rows if error: data = { 'ok': False, @@ -283,9 +283,18 @@ class BaseView(RenderMixin): 'database': name, 'database_hash': hash, } - if shape == 'array': + elif shape == 'array': data = data['rows'] - + elif shape == 'arrays': + pass + else: + status_code = 400 + data = { + 'ok': False, + 'error': 'Invalid _shape: {}'.format(shape), + 'status': 400, + 'title': None, + } headers = {} if self.ds.cors: headers['Access-Control-Allow-Origin'] = '*' diff --git a/docs/json_api.rst b/docs/json_api.rst index e750dbbf..af212ea5 100644 --- a/docs/json_api.rst +++ b/docs/json_api.rst @@ -59,13 +59,35 @@ The ``_shape`` parameter can be used to access alternative formats for the ``rows`` key which may be more convenient for your application. There are three options: -* ``?_shape=lists`` - the default option, shown above -* ``?_shape=objects`` - a list of JSON key/value objects -* ``?_shape=object`` - a JSON object keyed using the primary keys of the rows +* ``?_shape=arrays`` - ``"rows"`` is the default option, shown above +* ``?_shape=objects`` - ``"rows"`` is a list of JSON key/value objects +* ``?_shape=array`` - the entire response is an array of objects +* ``?_shape=object`` - the entire response is a JSON object keyed using the primary keys of the rows ``objects`` looks like this:: - "rows": [ + { + "database": "sf-trees", + ... + "rows": [ + { + "id": 1, + "value": "Myoporum laetum :: Myoporum" + }, + { + "id": 2, + "value": "Metrosideros excelsa :: New Zealand Xmas Tree" + }, + { + "id": 3, + "value": "Pinus radiata :: Monterey Pine" + } + ] + } + +``array`` looks like this:: + + [ { "id": 1, "value": "Myoporum laetum :: Myoporum" @@ -82,7 +104,7 @@ options: ``object`` looks like this:: - "rows": { + { "1": { "id": 1, "value": "Myoporum laetum :: Myoporum" diff --git a/tests/test_api.py b/tests/test_api.py index 40c23d00..6d5fa40a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -330,9 +330,9 @@ def test_jsono_redirects_to_shape_objects(app_client): assert response.headers['Location'].endswith('?_shape=objects') -def test_table_shape_lists(app_client): +def test_table_shape_arrays(app_client): response = app_client.get( - '/test_tables/simple_primary_key.json?_shape=lists', + '/test_tables/simple_primary_key.json?_shape=arrays', gather_request=False ) assert [ @@ -359,6 +359,36 @@ def test_table_shape_objects(app_client): }] == response.json['rows'] +def test_table_shape_array(app_client): + response = app_client.get( + '/test_tables/simple_primary_key.json?_shape=array', + gather_request=False + ) + assert [{ + 'id': '1', + 'content': 'hello', + }, { + 'id': '2', + 'content': 'world', + }, { + 'id': '3', + 'content': '', + }] == response.json + + +def test_table_shape_invalid(app_client): + response = app_client.get( + '/test_tables/simple_primary_key.json?_shape=invalid', + gather_request=False + ) + assert { + 'ok': False, + 'error': 'Invalid _shape: invalid', + 'status': 400, + 'title': None, + } == response.json + + def test_table_shape_object(app_client): response = app_client.get( '/test_tables/simple_primary_key.json?_shape=object', @@ -377,7 +407,7 @@ def test_table_shape_object(app_client): 'id': '3', 'content': '', } - } == response.json['rows'] + } == response.json def test_table_shape_object_compound_primary_Key(app_client): @@ -391,7 +421,7 @@ def test_table_shape_object_compound_primary_Key(app_client): 'pk2': 'b', 'content': 'c', } - } == response.json['rows'] + } == response.json def test_table_with_slashes_in_name(app_client):