diff --git a/datasette/app.py b/datasette/app.py index 03d1dacc..9ae4706a 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -190,6 +190,7 @@ class Datasette: def __init__( self, files=None, + *, immutables=None, cache_headers=True, cors=False, @@ -410,7 +411,7 @@ class Datasette: def unsign(self, signed, namespace="default"): return URLSafeSerializer(self._secret, namespace).loads(signed) - def get_database(self, name=None, route=None): + def get_database(self, name=None, *, route=None): if route is not None: matches = [db for db in self.databases.values() if db.route == route] if not matches: @@ -421,7 +422,7 @@ class Datasette: name = [key for key in self.databases.keys() if key != "_internal"][0] return self.databases[name] - def add_database(self, db, name=None, route=None): + def add_database(self, db, name=None, *, route=None): new_databases = self.databases.copy() if name is None: # Pick a unique name for this database @@ -466,7 +467,7 @@ class Datasette: orig[key] = upd_value return orig - def metadata(self, key=None, database=None, table=None, fallback=True): + def metadata(self, key=None, *, database=None, table=None, fallback=True): """ Looks up metadata, cascading backwards from specified level. Returns None if metadata value is not found. @@ -518,7 +519,7 @@ class Datasette: def _metadata(self): return self.metadata() - def plugin_config(self, plugin_name, database=None, table=None, fallback=True): + def plugin_config(self, plugin_name, *, database=None, table=None, fallback=True): """Return config for plugin, falling back from specified database/table""" plugins = self.metadata( "plugins", database=database, table=table, fallback=fallback @@ -714,6 +715,7 @@ class Datasette: db_name, sql, params=None, + *, truncate=False, custom_time_limit=None, page_size=None, @@ -943,7 +945,7 @@ class Datasette: ) async def render_template( - self, templates, context=None, request=None, view_name=None + self, templates, context=None, *, request=None, view_name=None ): if not self._startup_invoked: raise Exception("render_template() called before await ds.invoke_startup()") diff --git a/datasette/utils/asgi.py b/datasette/utils/asgi.py index 8a2fa060..fca6e004 100644 --- a/datasette/utils/asgi.py +++ b/datasette/utils/asgi.py @@ -118,7 +118,9 @@ class Request: return dict(parse_qsl(body.decode("utf-8"), keep_blank_values=True)) @classmethod - def fake(cls, path_with_query_string, method="GET", scheme="http", url_vars=None): + def fake( + cls, path_with_query_string, *, method="GET", scheme="http", url_vars=None + ): """Useful for constructing Request objects for tests""" path, _, query_string = path_with_query_string.partition("?") scope = { @@ -204,7 +206,7 @@ class AsgiWriter: ) -async def asgi_send_json(send, info, status=200, headers=None): +async def asgi_send_json(send, info, *, status=200, headers=None): headers = headers or {} await asgi_send( send, @@ -215,7 +217,7 @@ async def asgi_send_json(send, info, status=200, headers=None): ) -async def asgi_send_html(send, html, status=200, headers=None): +async def asgi_send_html(send, html, *, status=200, headers=None): headers = headers or {} await asgi_send( send, @@ -226,7 +228,7 @@ async def asgi_send_html(send, html, status=200, headers=None): ) -async def asgi_send_redirect(send, location, status=302): +async def asgi_send_redirect(send, location, *, status=302): await asgi_send( send, "", @@ -236,12 +238,12 @@ async def asgi_send_redirect(send, location, status=302): ) -async def asgi_send(send, content, status, headers=None, content_type="text/plain"): - await asgi_start(send, status, headers, content_type) +async def asgi_send(send, content, status, *, headers=None, content_type="text/plain"): + await asgi_start(send, status=status, headers=headers, content_type=content_type) await send({"type": "http.response.body", "body": content.encode("utf-8")}) -async def asgi_start(send, status, headers=None, content_type="text/plain"): +async def asgi_start(send, status, *, headers=None, content_type="text/plain"): headers = headers or {} # Remove any existing content-type header headers = {k: v for k, v in headers.items() if k.lower() != "content-type"} @@ -259,7 +261,7 @@ async def asgi_start(send, status, headers=None, content_type="text/plain"): async def asgi_send_file( - send, filepath, filename=None, content_type=None, chunk_size=4096, headers=None + send, filepath, filename=None, *, content_type=None, chunk_size=4096, headers=None ): headers = headers or {} if filename: @@ -270,9 +272,11 @@ async def asgi_send_file( if first: await asgi_start( send, - 200, - headers, - content_type or guess_type(str(filepath))[0] or "text/plain", + status=200, + headers=headers, + content_type=content_type + or guess_type(str(filepath))[0] + or "text/plain", ) first = False more_body = True @@ -284,7 +288,7 @@ async def asgi_send_file( ) -def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None): +def asgi_static(root_path, *, chunk_size=4096, headers=None, content_type=None): root_path = Path(root_path) async def inner_static(request, send): @@ -292,28 +296,32 @@ def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None): try: full_path = (root_path / path).resolve().absolute() except FileNotFoundError: - await asgi_send_html(send, "404: Directory not found", 404) + await asgi_send_html(send, "404: Directory not found", status=404) return if full_path.is_dir(): - await asgi_send_html(send, "403: Directory listing is not allowed", 403) + await asgi_send_html( + send, "403: Directory listing is not allowed", status=403 + ) return # Ensure full_path is within root_path to avoid weird "../" tricks try: full_path.relative_to(root_path.resolve()) except ValueError: - await asgi_send_html(send, "404: Path not inside root path", 404) + await asgi_send_html(send, "404: Path not inside root path", status=404) return try: await asgi_send_file(send, full_path, chunk_size=chunk_size) except FileNotFoundError: - await asgi_send_html(send, "404: File not found", 404) + await asgi_send_html(send, "404: File not found", status=404) return return inner_static class Response: - def __init__(self, body=None, status=200, headers=None, content_type="text/plain"): + def __init__( + self, body=None, *, status=200, headers=None, content_type="text/plain" + ): self.body = body self.status = status self.headers = headers or {} @@ -346,6 +354,7 @@ class Response: self, key, value="", + *, max_age=None, expires=None, path="/", @@ -374,7 +383,7 @@ class Response: self._set_cookie_headers.append(cookie.output(header="").strip()) @classmethod - def html(cls, body, status=200, headers=None): + def html(cls, body, *, status=200, headers=None): return cls( body, status=status, @@ -383,7 +392,7 @@ class Response: ) @classmethod - def text(cls, body, status=200, headers=None): + def text(cls, body, *, status=200, headers=None): return cls( str(body), status=status, @@ -392,7 +401,7 @@ class Response: ) @classmethod - def json(cls, body, status=200, headers=None, default=None): + def json(cls, body, *, status=200, headers=None, default=None): return cls( json.dumps(body, default=default), status=status, @@ -401,7 +410,7 @@ class Response: ) @classmethod - def redirect(cls, path, status=302, headers=None): + def redirect(cls, path, *, status=302, headers=None): headers = headers or {} headers["Location"] = path return cls("", status=status, headers=headers) @@ -412,6 +421,7 @@ class AsgiFileDownload: self, filepath, filename=None, + *, content_type="application/octet-stream", headers=None, ):