?_json_infinity=1 for handling Infinity/-Infinity - fixes #332

This commit is contained in:
Simon Willison 2018-07-23 20:07:57 -07:00
commit 700d83d8ad
No known key found for this signature in database
GPG key ID: 17E2DEA2588B7F52
5 changed files with 64 additions and 2 deletions

View file

@ -864,3 +864,15 @@ class LimitedWriter:
self.limit_bytes self.limit_bytes
)) ))
self.writer.write(bytes) self.writer.write(bytes)
_infinities = {float("inf"), float("-inf")}
def remove_infinites(row):
if any((c in _infinities) if isinstance(c, float) else 0 for c in row):
return [
None if (isinstance(c, float) and c in _infinities) else c
for c in row
]
return row

View file

@ -20,8 +20,10 @@ from datasette.utils import (
path_from_row_pks, path_from_row_pks,
path_with_added_args, path_with_added_args,
path_with_format, path_with_format,
remove_infinites,
resolve_table_and_format, resolve_table_and_format,
to_css_class to_css_class,
value_as_boolean,
) )
ureg = pint.UnitRegistry() ureg = pint.UnitRegistry()
@ -334,6 +336,12 @@ class BaseView(RenderMixin):
data["rows"], data["columns"], json_cols, data["rows"], data["columns"], json_cols,
) )
# unless _json_infinity=1 requested, replace infinity with None
if "rows" in data and not value_as_boolean(
request.args.get("_json_infinity", "0")
):
data["rows"] = [remove_infinites(row) for row in data["rows"]]
# Deal with the _shape option # Deal with the _shape option
shape = request.args.get("_shape", "arrays") shape = request.args.get("_shape", "arrays")
if shape == "arrayfirst": if shape == "arrayfirst":

View file

@ -148,6 +148,12 @@ querystring arguments:
Compare `this query without the argument <https://fivethirtyeight.datasettes.com/fivethirtyeight.json?sql=select+%27{%22this+is%22%3A+%22a+json+object%22}%27+as+d&_shape=array>`_ to `this query using the argument <https://fivethirtyeight.datasettes.com/fivethirtyeight.json?sql=select+%27{%22this+is%22%3A+%22a+json+object%22}%27+as+d&_shape=array&_json=d>`_ Compare `this query without the argument <https://fivethirtyeight.datasettes.com/fivethirtyeight.json?sql=select+%27{%22this+is%22%3A+%22a+json+object%22}%27+as+d&_shape=array>`_ to `this query using the argument <https://fivethirtyeight.datasettes.com/fivethirtyeight.json?sql=select+%27{%22this+is%22%3A+%22a+json+object%22}%27+as+d&_shape=array&_json=d>`_
``?_json_infinity=on``
If your data contains infinity or -infinity values, Datasette will replace
them with None when returning them as JSON. If you pass ``_json_infinity=1``
Datasette will instead return them as ``Infinity`` or ``-Infinity`` which is
invalid JSON but can be processed by some custom JSON parsers.
``?_timelimit=MS`` ``?_timelimit=MS``
Sets a custom time limit for the query in ms. You can use this for optimistic Sets a custom time limit for the query in ms. You can use this for optimistic
queries where you would like Datasette to give up if the query takes too queries where you would like Datasette to give up if the query takes too

View file

@ -367,6 +367,15 @@ CREATE TABLE [select] (
); );
INSERT INTO [select] VALUES ('group', 'having', 'and'); INSERT INTO [select] VALUES ('group', 'having', 'and');
CREATE TABLE infinity (
value REAL
);
INSERT INTO infinity VALUES
(1e999),
(-1e999),
(1.5)
;
CREATE TABLE facet_cities ( CREATE TABLE facet_cities (
id integer primary key, id integer primary key,
name text name text

View file

@ -18,7 +18,7 @@ def test_homepage(app_client):
assert response.json.keys() == {'fixtures': 0}.keys() assert response.json.keys() == {'fixtures': 0}.keys()
d = response.json['fixtures'] d = response.json['fixtures']
assert d['name'] == 'fixtures' assert d['name'] == 'fixtures'
assert d['tables_count'] == 19 assert d['tables_count'] == 20
def test_database_page(app_client): def test_database_page(app_client):
@ -153,6 +153,15 @@ def test_database_page(app_client):
'label_column': None, 'label_column': None,
'fts_table': None, 'fts_table': None,
'primary_keys': ['pk'], 'primary_keys': ['pk'],
}, {
"name": "infinity",
"columns": ["value"],
"count": 3,
"primary_keys": [],
"label_column": None,
"hidden": False,
"fts_table": None,
"foreign_keys": {"incoming": [], "outgoing": []}
}, { }, {
'columns': ['id', 'content', 'content2'], 'columns': ['id', 'content', 'content2'],
'name': 'primary_key_multiple_columns', 'name': 'primary_key_multiple_columns',
@ -1281,3 +1290,21 @@ def test_config_force_https_urls():
"toggle_url" "toggle_url"
].startswith("https://") ].startswith("https://")
assert response.json["suggested_facets"][0]["toggle_url"].startswith("https://") assert response.json["suggested_facets"][0]["toggle_url"].startswith("https://")
def test_infinity_returned_as_null(app_client):
response = app_client.get("/fixtures/infinity.json?_shape=array")
assert [
{"rowid": 1, "value": None},
{"rowid": 2, "value": None},
{"rowid": 3, "value": 1.5}
] == response.json
def test_infinity_returned_as_invalid_json_if_requested(app_client):
response = app_client.get("/fixtures/infinity.json?_shape=array&_json_infinity=1")
assert [
{"rowid": 1, "value": float("inf")},
{"rowid": 2, "value": float("-inf")},
{"rowid": 3, "value": 1.5}
] == response.json