mirror of
https://github.com/simonw/datasette.git
synced 2026-10-29 06:37:01 +01:00
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
92 lines
3.6 KiB
Diff
92 lines
3.6 KiB
Diff
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
|