datasette/perf/request-response/patches/response-serialization-2-json-renderer-skip-infinity-scan.diff
Claude d1dbb40362
Add request/response profiling harness and proposed optimisation patches
Tools for profiling Datasette's request/response cycle in-process with
Python 3.15's profiling.sampling, plus the patches proposed by the study
(one per optimisation, and ALL-combined.diff). The patches are not
applied to the code. A report with measurements follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8mEgRS8swZfYqwqrVMeo5
2026-10-13 00:12:35 +00:00

92 lines
3.6 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff --git a/datasette/renderer.py b/datasette/renderer.py
index 0e01f52..f3cf8ac 100644
--- a/datasette/renderer.py
+++ b/datasette/renderer.py
@@ -32,6 +32,28 @@ def convert_specific_columns_to_json(rows, columns, json_cols):
def json_renderer(request, args, data, error, truncated=None):
"""Render a response as JSON"""
+ # unless _json_infinity=1 requested, replace infinity with None
+ if "rows" in data and not value_as_boolean(args.get("_json_infinity", "0")):
+ # Infinite values are rare, so rather than checking every value of
+ # every row first, render with allow_nan=False - which raises
+ # ValueError if there are any - and only then replace them and retry
+ original_data = dict(data)
+ try:
+ return _json_renderer(
+ request, args, data, error, truncated, allow_nan=False
+ )
+ except ValueError:
+ data.clear()
+ data.update(original_data)
+ return _json_renderer(
+ request, args, data, error, truncated, replace_infinity=True
+ )
+ return _json_renderer(request, args, data, error, truncated)
+
+
+def _json_renderer(
+ request, args, data, error, truncated, allow_nan=True, replace_infinity=False
+):
status_code = 200
# Handle the _json= parameter which may modify data["rows"]
@@ -43,8 +65,7 @@ def json_renderer(request, args, data, error, truncated=None):
data["rows"], data["columns"], json_cols
)
- # unless _json_infinity=1 requested, replace infinity with None
- if "rows" in data and not value_as_boolean(args.get("_json_infinity", "0")):
+ if replace_infinity:
data["rows"] = [remove_infinites(row) for row in data["rows"]]
# Deal with the _shape option
@@ -112,10 +133,13 @@ def json_renderer(request, args, data, error, truncated=None):
# Handle _nl option for _shape=array
nl = args.get("_nl", "")
if nl and shape == "array":
- body = "\n".join(json.dumps(item, cls=CustomJSONEncoder) for item in data)
+ body = "\n".join(
+ json.dumps(item, cls=CustomJSONEncoder, allow_nan=allow_nan)
+ for item in data
+ )
content_type = "text/plain"
else:
- body = json.dumps(data, cls=CustomJSONEncoder)
+ body = json.dumps(data, cls=CustomJSONEncoder, allow_nan=allow_nan)
content_type = "application/json; charset=utf-8"
headers = {}
return Response(
diff --git a/tests/test_table_api.py b/tests/test_table_api.py
index 58a5ddc..ae5f528 100644
--- a/tests/test_table_api.py
+++ b/tests/test_table_api.py
@@ -1302,6 +1302,28 @@ async def test_infinity_returned_as_invalid_json_if_requested(ds_client):
]
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+ "path,expected",
+ [
+ # Infinity in a column that _shape=arrayfirst does not output - this
+ # used to fail with a 500 because the infinity scan turned the first
+ # sqlite3.Row into a list
+ ("/fixtures/-/query.json?sql=select+1+as+a,+9e999+as+b&_shape=arrayfirst", [1]),
+ ("/fixtures/infinity/1.json?_shape=arrayfirst", [1]),
+ # Infinity in the output column is still replaced with null
+ (
+ "/fixtures/-/query.json?sql=select+1+as+a+union+all+select+9e999&_shape=arrayfirst",
+ [1, None],
+ ),
+ ],
+)
+async def test_infinity_arrayfirst(ds_client, path, expected):
+ response = await ds_client.get(path)
+ assert response.status_code == 200
+ assert response.json() == expected
+
+
@pytest.mark.asyncio
async def test_custom_query_with_unicode_characters(ds_client):
# /fixtures/𝐜𝐢𝐭𝐢𝐞𝐬.json