{"html_url": "https://github.com/simonw/datasette/issues/1822#issuecomment-1258760299", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1822", "id": 1258760299, "node_id": "IC_kwDOBm6k_c5LByhr", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-09-26T23:25:12Z", "updated_at": "2022-09-26T23:25:55Z", "author_association": "OWNER", "body": "A start:\r\n```diff\r\ndiff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py\r\nindex 8a2fa060..41ade961 100644\r\n--- a/datasette/utils/asgi.py\r\n+++ b/datasette/utils/asgi.py\r\n@@ -118,7 +118,7 @@ class Request:\r\n return dict(parse_qsl(body.decode(\"utf-8\"), keep_blank_values=True))\r\n \r\n @classmethod\r\n- def fake(cls, path_with_query_string, method=\"GET\", scheme=\"http\", url_vars=None):\r\n+ def fake(cls, path_with_query_string, *, method=\"GET\", scheme=\"http\", url_vars=None):\r\n \"\"\"Useful for constructing Request objects for tests\"\"\"\r\n path, _, query_string = path_with_query_string.partition(\"?\")\r\n scope = {\r\n@@ -204,7 +204,7 @@ class AsgiWriter:\r\n )\r\n \r\n \r\n-async def asgi_send_json(send, info, status=200, headers=None):\r\n+async def asgi_send_json(send, info, *, status=200, headers=None):\r\n headers = headers or {}\r\n await asgi_send(\r\n send,\r\n@@ -215,7 +215,7 @@ async def asgi_send_json(send, info, status=200, headers=None):\r\n )\r\n \r\n \r\n-async def asgi_send_html(send, html, status=200, headers=None):\r\n+async def asgi_send_html(send, html, *, status=200, headers=None):\r\n headers = headers or {}\r\n await asgi_send(\r\n send,\r\n@@ -226,7 +226,7 @@ async def asgi_send_html(send, html, status=200, headers=None):\r\n )\r\n \r\n \r\n-async def asgi_send_redirect(send, location, status=302):\r\n+async def asgi_send_redirect(send, location, *, status=302):\r\n await asgi_send(\r\n send,\r\n \"\",\r\n@@ -236,12 +236,12 @@ async def asgi_send_redirect(send, location, status=302):\r\n )\r\n \r\n \r\n-async def asgi_send(send, content, status, headers=None, content_type=\"text/plain\"):\r\n+async def asgi_send(send, content, status, *, headers=None, content_type=\"text/plain\"):\r\n await asgi_start(send, status, headers, content_type)\r\n await send({\"type\": \"http.response.body\", \"body\": content.encode(\"utf-8\")})\r\n \r\n \r\n-async def asgi_start(send, status, headers=None, content_type=\"text/plain\"):\r\n+async def asgi_start(send, status, *, headers=None, content_type=\"text/plain\"):\r\n headers = headers or {}\r\n # Remove any existing content-type header\r\n headers = {k: v for k, v in headers.items() if k.lower() != \"content-type\"}\r\n@@ -259,7 +259,7 @@ async def asgi_start(send, status, headers=None, content_type=\"text/plain\"):\r\n \r\n \r\n async def asgi_send_file(\r\n- send, filepath, filename=None, content_type=None, chunk_size=4096, headers=None\r\n+ send, filepath, filename=None, *, content_type=None, chunk_size=4096, headers=None\r\n ):\r\n headers = headers or {}\r\n if filename:\r\n@@ -284,7 +284,7 @@ async def asgi_send_file(\r\n )\r\n \r\n \r\n-def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None):\r\n+def asgi_static(root_path, *, chunk_size=4096, headers=None, content_type=None):\r\n root_path = Path(root_path)\r\n \r\n async def inner_static(request, send):\r\n@@ -313,7 +313,7 @@ def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None):\r\n \r\n \r\n class Response:\r\n- def __init__(self, body=None, status=200, headers=None, content_type=\"text/plain\"):\r\n+ def __init__(self, body=None, *, status=200, headers=None, content_type=\"text/plain\"):\r\n self.body = body\r\n self.status = status\r\n self.headers = headers or {}\r\n@@ -346,6 +346,7 @@ class Response:\r\n self,\r\n key,\r\n value=\"\",\r\n+ *,\r\n max_age=None,\r\n expires=None,\r\n path=\"/\",\r\n@@ -374,7 +375,7 @@ class Response:\r\n self._set_cookie_headers.append(cookie.output(header=\"\").strip())\r\n \r\n @classmethod\r\n- def html(cls, body, status=200, headers=None):\r\n+ def html(cls, body, *, status=200, headers=None):\r\n return cls(\r\n body,\r\n status=status,\r\n@@ -383,7 +384,7 @@ class Response:\r\n )\r\n \r\n @classmethod\r\n- def text(cls, body, status=200, headers=None):\r\n+ def text(cls, body, *, status=200, headers=None):\r\n return cls(\r\n str(body),\r\n status=status,\r\n@@ -392,7 +393,7 @@ class Response:\r\n )\r\n \r\n @classmethod\r\n- def json(cls, body, status=200, headers=None, default=None):\r\n+ def json(cls, body, *, status=200, headers=None, default=None):\r\n return cls(\r\n json.dumps(body, default=default),\r\n status=status,\r\n@@ -401,7 +402,7 @@ class Response:\r\n )\r\n \r\n @classmethod\r\n- def redirect(cls, path, status=302, headers=None):\r\n+ def redirect(cls, path, *, status=302, headers=None):\r\n headers = headers or {}\r\n headers[\"Location\"] = path\r\n return cls(\"\", status=status, headers=headers)\r\n@@ -412,6 +413,7 @@ class AsgiFileDownload:\r\n self,\r\n filepath,\r\n filename=None,\r\n+ *,\r\n content_type=\"application/octet-stream\",\r\n headers=None,\r\n ):\r\n```\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex 03d1dacc..4d4e5584 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -190,6 +190,7 @@ class Datasette:\r\n def __init__(\r\n self,\r\n files=None,\r\n+ *,\r\n immutables=None,\r\n cache_headers=True,\r\n cors=False,\r\n ```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1386854246, "label": "Switch to keyword-only arguments for a bunch of internal methods"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1822#issuecomment-1258757544", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1822", "id": 1258757544, "node_id": "IC_kwDOBm6k_c5LBx2o", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-09-26T23:21:23Z", "updated_at": "2022-09-26T23:21:23Z", "author_association": "OWNER", "body": "Everything on https://docs.datasette.io/en/stable/internals.html that uses keyword arguments should do this I think.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1386854246, "label": "Switch to keyword-only arguments for a bunch of internal methods"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1822#issuecomment-1258827688", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1822", "id": 1258827688, "node_id": "IC_kwDOBm6k_c5LCC-o", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-09-27T00:44:04Z", "updated_at": "2022-09-27T00:44:04Z", "author_association": "OWNER", "body": "I'll do this in a PR.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1386854246, "label": "Switch to keyword-only arguments for a bunch of internal methods"}, "performed_via_github_app": null}