Keyword-only arguments for a bunch of internal methods, refs #1822

pull/1823/head
Simon Willison 2022-09-26 17:43:55 -07:00
rodzic 5f9f567acb
commit 49a11a6042
2 zmienionych plików z 38 dodań i 26 usunięć

Wyświetl plik

@ -190,6 +190,7 @@ class Datasette:
def __init__( def __init__(
self, self,
files=None, files=None,
*,
immutables=None, immutables=None,
cache_headers=True, cache_headers=True,
cors=False, cors=False,
@ -410,7 +411,7 @@ class Datasette:
def unsign(self, signed, namespace="default"): def unsign(self, signed, namespace="default"):
return URLSafeSerializer(self._secret, namespace).loads(signed) 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: if route is not None:
matches = [db for db in self.databases.values() if db.route == route] matches = [db for db in self.databases.values() if db.route == route]
if not matches: if not matches:
@ -421,7 +422,7 @@ class Datasette:
name = [key for key in self.databases.keys() if key != "_internal"][0] name = [key for key in self.databases.keys() if key != "_internal"][0]
return self.databases[name] 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() new_databases = self.databases.copy()
if name is None: if name is None:
# Pick a unique name for this database # Pick a unique name for this database
@ -466,7 +467,7 @@ class Datasette:
orig[key] = upd_value orig[key] = upd_value
return orig 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. Looks up metadata, cascading backwards from specified level.
Returns None if metadata value is not found. Returns None if metadata value is not found.
@ -518,7 +519,7 @@ class Datasette:
def _metadata(self): def _metadata(self):
return self.metadata() 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""" """Return config for plugin, falling back from specified database/table"""
plugins = self.metadata( plugins = self.metadata(
"plugins", database=database, table=table, fallback=fallback "plugins", database=database, table=table, fallback=fallback
@ -714,6 +715,7 @@ class Datasette:
db_name, db_name,
sql, sql,
params=None, params=None,
*,
truncate=False, truncate=False,
custom_time_limit=None, custom_time_limit=None,
page_size=None, page_size=None,
@ -943,7 +945,7 @@ class Datasette:
) )
async def render_template( 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: if not self._startup_invoked:
raise Exception("render_template() called before await ds.invoke_startup()") raise Exception("render_template() called before await ds.invoke_startup()")

Wyświetl plik

@ -118,7 +118,9 @@ class Request:
return dict(parse_qsl(body.decode("utf-8"), keep_blank_values=True)) return dict(parse_qsl(body.decode("utf-8"), keep_blank_values=True))
@classmethod @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""" """Useful for constructing Request objects for tests"""
path, _, query_string = path_with_query_string.partition("?") path, _, query_string = path_with_query_string.partition("?")
scope = { 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 {} headers = headers or {}
await asgi_send( await asgi_send(
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 {} headers = headers or {}
await asgi_send( await asgi_send(
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( await asgi_send(
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"): async def asgi_send(send, content, status, *, headers=None, content_type="text/plain"):
await asgi_start(send, status, headers, content_type) await asgi_start(send, status=status, headers=headers, content_type=content_type)
await send({"type": "http.response.body", "body": content.encode("utf-8")}) 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 {} headers = headers or {}
# Remove any existing content-type header # Remove any existing content-type header
headers = {k: v for k, v in headers.items() if k.lower() != "content-type"} 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( 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 {} headers = headers or {}
if filename: if filename:
@ -270,9 +272,11 @@ async def asgi_send_file(
if first: if first:
await asgi_start( await asgi_start(
send, send,
200, status=200,
headers, headers=headers,
content_type or guess_type(str(filepath))[0] or "text/plain", content_type=content_type
or guess_type(str(filepath))[0]
or "text/plain",
) )
first = False first = False
more_body = True 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) root_path = Path(root_path)
async def inner_static(request, send): async def inner_static(request, send):
@ -292,28 +296,32 @@ def asgi_static(root_path, chunk_size=4096, headers=None, content_type=None):
try: try:
full_path = (root_path / path).resolve().absolute() full_path = (root_path / path).resolve().absolute()
except FileNotFoundError: except FileNotFoundError:
await asgi_send_html(send, "404: Directory not found", 404) await asgi_send_html(send, "404: Directory not found", status=404)
return return
if full_path.is_dir(): 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 return
# Ensure full_path is within root_path to avoid weird "../" tricks # Ensure full_path is within root_path to avoid weird "../" tricks
try: try:
full_path.relative_to(root_path.resolve()) full_path.relative_to(root_path.resolve())
except ValueError: 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 return
try: try:
await asgi_send_file(send, full_path, chunk_size=chunk_size) await asgi_send_file(send, full_path, chunk_size=chunk_size)
except FileNotFoundError: 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
return inner_static return inner_static
class Response: 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.body = body
self.status = status self.status = status
self.headers = headers or {} self.headers = headers or {}
@ -346,6 +354,7 @@ class Response:
self, self,
key, key,
value="", value="",
*,
max_age=None, max_age=None,
expires=None, expires=None,
path="/", path="/",
@ -374,7 +383,7 @@ class Response:
self._set_cookie_headers.append(cookie.output(header="").strip()) self._set_cookie_headers.append(cookie.output(header="").strip())
@classmethod @classmethod
def html(cls, body, status=200, headers=None): def html(cls, body, *, status=200, headers=None):
return cls( return cls(
body, body,
status=status, status=status,
@ -383,7 +392,7 @@ class Response:
) )
@classmethod @classmethod
def text(cls, body, status=200, headers=None): def text(cls, body, *, status=200, headers=None):
return cls( return cls(
str(body), str(body),
status=status, status=status,
@ -392,7 +401,7 @@ class Response:
) )
@classmethod @classmethod
def json(cls, body, status=200, headers=None, default=None): def json(cls, body, *, status=200, headers=None, default=None):
return cls( return cls(
json.dumps(body, default=default), json.dumps(body, default=default),
status=status, status=status,
@ -401,7 +410,7 @@ class Response:
) )
@classmethod @classmethod
def redirect(cls, path, status=302, headers=None): def redirect(cls, path, *, status=302, headers=None):
headers = headers or {} headers = headers or {}
headers["Location"] = path headers["Location"] = path
return cls("", status=status, headers=headers) return cls("", status=status, headers=headers)
@ -412,6 +421,7 @@ class AsgiFileDownload:
self, self,
filepath, filepath,
filename=None, filename=None,
*,
content_type="application/octet-stream", content_type="application/octet-stream",
headers=None, headers=None,
): ):