mirror of
https://github.com/simonw/datasette.git
synced 2026-09-27 20:34:08 +02:00
Compare commits
1 commit
main
...
claude/asy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a7b027b60 |
13 changed files with 90 additions and 76 deletions
|
|
@ -472,15 +472,15 @@ class Datasette:
|
||||||
self._settings = dict(DEFAULT_SETTINGS, **(config_settings), **(settings or {}))
|
self._settings = dict(DEFAULT_SETTINGS, **(config_settings), **(settings or {}))
|
||||||
self.renderers = {} # File extension -> (renderer, can_render) functions
|
self.renderers = {} # File extension -> (renderer, can_render) functions
|
||||||
self.version_note = version_note
|
self.version_note = version_note
|
||||||
if self.setting("num_sql_threads") == 0:
|
if self._setting_sync("num_sql_threads") == 0:
|
||||||
self.executor = None
|
self.executor = None
|
||||||
else:
|
else:
|
||||||
self.executor = futures.ThreadPoolExecutor(
|
self.executor = futures.ThreadPoolExecutor(
|
||||||
max_workers=self.setting("num_sql_threads")
|
max_workers=self._setting_sync("num_sql_threads")
|
||||||
)
|
)
|
||||||
self.max_returned_rows = self.setting("max_returned_rows")
|
self.max_returned_rows = self._setting_sync("max_returned_rows")
|
||||||
self.sql_time_limit_ms = self.setting("sql_time_limit_ms")
|
self.sql_time_limit_ms = self._setting_sync("sql_time_limit_ms")
|
||||||
self.page_size = self.setting("default_page_size")
|
self.page_size = self._setting_sync("default_page_size")
|
||||||
# Execute plugins in constructor, to ensure they are available
|
# Execute plugins in constructor, to ensure they are available
|
||||||
# when the rest of `datasette inspect` executes
|
# when the rest of `datasette inspect` executes
|
||||||
if self.plugins_dir:
|
if self.plugins_dir:
|
||||||
|
|
@ -790,12 +790,16 @@ class Datasette:
|
||||||
new_databases.pop(name)
|
new_databases.pop(name)
|
||||||
self.databases = new_databases
|
self.databases = new_databases
|
||||||
|
|
||||||
def setting(self, key):
|
async def setting(self, key):
|
||||||
return self._settings.get(key, None)
|
return self._settings.get(key, None)
|
||||||
|
|
||||||
def settings_dict(self):
|
def _setting_sync(self, key):
|
||||||
|
# Synchronous access for contexts that cannot await (threads, sync callbacks)
|
||||||
|
return self._settings.get(key, None)
|
||||||
|
|
||||||
|
async def settings_dict(self):
|
||||||
# Returns a fully resolved settings dictionary, useful for templates
|
# Returns a fully resolved settings dictionary, useful for templates
|
||||||
return {option.name: self.setting(option.name) for option in SETTINGS}
|
return {option.name: await self.setting(option.name) for option in SETTINGS}
|
||||||
|
|
||||||
def _metadata_recursive_update(self, orig, updated):
|
def _metadata_recursive_update(self, orig, updated):
|
||||||
if not isinstance(orig, dict) or not isinstance(updated, dict):
|
if not isinstance(orig, dict) or not isinstance(updated, dict):
|
||||||
|
|
@ -919,7 +923,7 @@ class Datasette:
|
||||||
def get_internal_database(self):
|
def get_internal_database(self):
|
||||||
return self._internal_database
|
return self._internal_database
|
||||||
|
|
||||||
def plugin_config(self, plugin_name, database=None, table=None, fallback=True):
|
async 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"""
|
||||||
if database is None and table is None:
|
if database is None and table is None:
|
||||||
config = self._plugin_config_top(plugin_name)
|
config = self._plugin_config_top(plugin_name)
|
||||||
|
|
@ -928,6 +932,15 @@ class Datasette:
|
||||||
|
|
||||||
return resolve_env_secrets(config, os.environ)
|
return resolve_env_secrets(config, os.environ)
|
||||||
|
|
||||||
|
def _plugin_config_sync(self, plugin_name, database=None, table=None, fallback=True):
|
||||||
|
"""Synchronous access for contexts that cannot await (threads, sync callbacks)"""
|
||||||
|
if database is None and table is None:
|
||||||
|
config = self._plugin_config_top(plugin_name)
|
||||||
|
else:
|
||||||
|
config = self._plugin_config_nested(plugin_name, database, table, fallback)
|
||||||
|
|
||||||
|
return resolve_env_secrets(config, os.environ)
|
||||||
|
|
||||||
def _plugin_config_top(self, plugin_name):
|
def _plugin_config_top(self, plugin_name):
|
||||||
"""Returns any top-level plugin configuration for the specified plugin."""
|
"""Returns any top-level plugin configuration for the specified plugin."""
|
||||||
return ((self.config or {}).get("plugins") or {}).get(plugin_name)
|
return ((self.config or {}).get("plugins") or {}).get(plugin_name)
|
||||||
|
|
@ -1003,8 +1016,8 @@ class Datasette:
|
||||||
conn.execute("SELECT load_extension(?, ?)", [path, entrypoint])
|
conn.execute("SELECT load_extension(?, ?)", [path, entrypoint])
|
||||||
else:
|
else:
|
||||||
conn.execute("SELECT load_extension(?)", [extension])
|
conn.execute("SELECT load_extension(?)", [extension])
|
||||||
if self.setting("cache_size_kb"):
|
if self._setting_sync("cache_size_kb"):
|
||||||
conn.execute(f"PRAGMA cache_size=-{self.setting('cache_size_kb')}")
|
conn.execute(f"PRAGMA cache_size=-{self._setting_sync('cache_size_kb')}")
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
if database != INTERNAL_DB_NAME:
|
if database != INTERNAL_DB_NAME:
|
||||||
pm.hook.prepare_connection(conn=conn, database=database, datasette=self)
|
pm.hook.prepare_connection(conn=conn, database=database, datasette=self)
|
||||||
|
|
@ -1517,7 +1530,7 @@ class Datasette:
|
||||||
|
|
||||||
def absolute_url(self, request, path):
|
def absolute_url(self, request, path):
|
||||||
url = urllib.parse.urljoin(request.url, path)
|
url = urllib.parse.urljoin(request.url, path)
|
||||||
if url.startswith("http://") and self.setting("force_https_urls"):
|
if url.startswith("http://") and self._setting_sync("force_https_urls"):
|
||||||
url = "https://" + url[len("http://") :]
|
url = "https://" + url[len("http://") :]
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
@ -1632,7 +1645,7 @@ class Datasette:
|
||||||
]
|
]
|
||||||
|
|
||||||
def _threads(self):
|
def _threads(self):
|
||||||
if self.setting("num_sql_threads") == 0:
|
if self._setting_sync("num_sql_threads") == 0:
|
||||||
return {"num_threads": 0, "threads": []}
|
return {"num_threads": 0, "threads": []}
|
||||||
threads = list(threading.enumerate())
|
threads = list(threading.enumerate())
|
||||||
d = {
|
d = {
|
||||||
|
|
@ -1790,13 +1803,13 @@ class Datasette:
|
||||||
"extra_js_urls": await self._asset_urls(
|
"extra_js_urls": await self._asset_urls(
|
||||||
"extra_js_urls", template, context, request, view_name
|
"extra_js_urls", template, context, request, view_name
|
||||||
),
|
),
|
||||||
"base_url": self.setting("base_url"),
|
"base_url": await self.setting("base_url"),
|
||||||
"csrftoken": request.scope["csrftoken"] if request else lambda: "",
|
"csrftoken": request.scope["csrftoken"] if request else lambda: "",
|
||||||
"datasette_version": __version__,
|
"datasette_version": __version__,
|
||||||
},
|
},
|
||||||
**extra_template_vars,
|
**extra_template_vars,
|
||||||
}
|
}
|
||||||
if request and request.args.get("_context") and self.setting("template_debug"):
|
if request and request.args.get("_context") and await self.setting("template_debug"):
|
||||||
return "<pre>{}</pre>".format(
|
return "<pre>{}</pre>".format(
|
||||||
escape(json.dumps(template_context, default=repr, indent=4))
|
escape(json.dumps(template_context, default=repr, indent=4))
|
||||||
)
|
)
|
||||||
|
|
@ -2110,7 +2123,7 @@ class Datasette:
|
||||||
),
|
),
|
||||||
send_csrf_failed=custom_csrf_error,
|
send_csrf_failed=custom_csrf_error,
|
||||||
)
|
)
|
||||||
if self.setting("trace_debug"):
|
if self._setting_sync("trace_debug"):
|
||||||
asgi = AsgiTracer(asgi)
|
asgi = AsgiTracer(asgi)
|
||||||
asgi = AsgiLifespan(asgi)
|
asgi = AsgiLifespan(asgi)
|
||||||
asgi = AsgiRunOnFirstRequest(asgi, on_startup=[setup_db, self.invoke_startup])
|
asgi = AsgiRunOnFirstRequest(asgi, on_startup=[setup_db, self.invoke_startup])
|
||||||
|
|
@ -2135,7 +2148,7 @@ class DatasetteRouter:
|
||||||
|
|
||||||
async def route_path(self, scope, receive, send, path):
|
async def route_path(self, scope, receive, send, path):
|
||||||
# Strip off base_url if present before routing
|
# Strip off base_url if present before routing
|
||||||
base_url = self.ds.setting("base_url")
|
base_url = await self.ds.setting("base_url")
|
||||||
if base_url != "/" and path.startswith(base_url):
|
if base_url != "/" and path.startswith(base_url):
|
||||||
path = "/" + path[len(base_url) :]
|
path = "/" + path[len(base_url) :]
|
||||||
scope = dict(scope, route_path=path)
|
scope = dict(scope, route_path=path)
|
||||||
|
|
@ -2151,7 +2164,7 @@ class DatasetteRouter:
|
||||||
scope_modifications = {}
|
scope_modifications = {}
|
||||||
# Apply force_https_urls, if set
|
# Apply force_https_urls, if set
|
||||||
if (
|
if (
|
||||||
self.ds.setting("force_https_urls")
|
await self.ds.setting("force_https_urls")
|
||||||
and scope["type"] == "http"
|
and scope["type"] == "http"
|
||||||
and scope.get("scheme") != "https"
|
and scope.get("scheme") != "https"
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ async def default_allow_sql_check(
|
||||||
unless explicitly allowed by config or other rules.
|
unless explicitly allowed by config or other rules.
|
||||||
"""
|
"""
|
||||||
if action == "execute-sql":
|
if action == "execute-sql":
|
||||||
if not datasette.setting("default_allow_sql"):
|
if not await datasette.setting("default_allow_sql"):
|
||||||
return PermissionSQL.deny(reason="default_allow_sql is false")
|
return PermissionSQL.deny(reason="default_allow_sql is false")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ from datasette import hookimpl
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(specname="actor_from_request")
|
@hookimpl(specname="actor_from_request")
|
||||||
def actor_from_signed_api_token(datasette: "Datasette", request) -> Optional[dict]:
|
async def actor_from_signed_api_token(datasette: "Datasette", request) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
Authenticate requests using signed API tokens (dstok_ prefix).
|
Authenticate requests using signed API tokens (dstok_ prefix).
|
||||||
|
|
||||||
|
|
@ -33,10 +33,10 @@ def actor_from_signed_api_token(datasette: "Datasette", request) -> Optional[dic
|
||||||
prefix = "dstok_"
|
prefix = "dstok_"
|
||||||
|
|
||||||
# Check if tokens are enabled
|
# Check if tokens are enabled
|
||||||
if not datasette.setting("allow_signed_tokens"):
|
if not await datasette.setting("allow_signed_tokens"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
max_signed_tokens_ttl = datasette.setting("max_signed_tokens_ttl")
|
max_signed_tokens_ttl = await datasette.setting("max_signed_tokens_ttl")
|
||||||
|
|
||||||
# Get authorization header
|
# Get authorization header
|
||||||
authorization = request.headers.get("authorization")
|
authorization = request.headers.get("authorization")
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,9 @@ class Facet:
|
||||||
# [('_foo', 'bar'), ('_foo', '2'), ('empty', '')]
|
# [('_foo', 'bar'), ('_foo', '2'), ('empty', '')]
|
||||||
return urllib.parse.parse_qsl(self.request.query_string, keep_blank_values=True)
|
return urllib.parse.parse_qsl(self.request.query_string, keep_blank_values=True)
|
||||||
|
|
||||||
def get_facet_size(self):
|
async def get_facet_size(self):
|
||||||
facet_size = self.ds.setting("default_facet_size")
|
facet_size = await self.ds.setting("default_facet_size")
|
||||||
max_returned_rows = self.ds.setting("max_returned_rows")
|
max_returned_rows = await self.ds.setting("max_returned_rows")
|
||||||
table_facet_size = None
|
table_facet_size = None
|
||||||
if self.table:
|
if self.table:
|
||||||
config_facet_size = (
|
config_facet_size = (
|
||||||
|
|
@ -154,7 +154,7 @@ class ColumnFacet(Facet):
|
||||||
async def suggest(self):
|
async def suggest(self):
|
||||||
row_count = await self.get_row_count()
|
row_count = await self.get_row_count()
|
||||||
columns = await self.get_columns(self.sql, self.params)
|
columns = await self.get_columns(self.sql, self.params)
|
||||||
facet_size = self.get_facet_size()
|
facet_size = await self.get_facet_size()
|
||||||
suggested_facets = []
|
suggested_facets = []
|
||||||
already_enabled = [c["config"]["simple"] for c in self.get_configs()]
|
already_enabled = [c["config"]["simple"] for c in self.get_configs()]
|
||||||
for column in columns:
|
for column in columns:
|
||||||
|
|
@ -179,7 +179,7 @@ class ColumnFacet(Facet):
|
||||||
suggested_facet_sql,
|
suggested_facet_sql,
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting("facet_suggest_time_limit_ms"),
|
custom_time_limit=await self.ds.setting("facet_suggest_time_limit_ms"),
|
||||||
)
|
)
|
||||||
num_distinct_values = len(distinct_values)
|
num_distinct_values = len(distinct_values)
|
||||||
if (
|
if (
|
||||||
|
|
@ -222,7 +222,7 @@ class ColumnFacet(Facet):
|
||||||
|
|
||||||
qs_pairs = self.get_querystring_pairs()
|
qs_pairs = self.get_querystring_pairs()
|
||||||
|
|
||||||
facet_size = self.get_facet_size()
|
facet_size = await self.get_facet_size()
|
||||||
for source_and_config in self.get_configs():
|
for source_and_config in self.get_configs():
|
||||||
config = source_and_config["config"]
|
config = source_and_config["config"]
|
||||||
source = source_and_config["source"]
|
source = source_and_config["source"]
|
||||||
|
|
@ -242,7 +242,7 @@ class ColumnFacet(Facet):
|
||||||
facet_sql,
|
facet_sql,
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting("facet_time_limit_ms"),
|
custom_time_limit=await self.ds.setting("facet_time_limit_ms"),
|
||||||
)
|
)
|
||||||
facet_results_values = []
|
facet_results_values = []
|
||||||
facet_results.append(
|
facet_results.append(
|
||||||
|
|
@ -333,7 +333,7 @@ class ArrayFacet(Facet):
|
||||||
suggested_facet_sql,
|
suggested_facet_sql,
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting("facet_suggest_time_limit_ms"),
|
custom_time_limit=await self.ds.setting("facet_suggest_time_limit_ms"),
|
||||||
log_sql_errors=False,
|
log_sql_errors=False,
|
||||||
)
|
)
|
||||||
types = tuple(r[0] for r in results.rows)
|
types = tuple(r[0] for r in results.rows)
|
||||||
|
|
@ -352,7 +352,7 @@ class ArrayFacet(Facet):
|
||||||
).format(column=escape_sqlite(column), sql=self.sql),
|
).format(column=escape_sqlite(column), sql=self.sql),
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting(
|
custom_time_limit=await self.ds.setting(
|
||||||
"facet_suggest_time_limit_ms"
|
"facet_suggest_time_limit_ms"
|
||||||
),
|
),
|
||||||
log_sql_errors=False,
|
log_sql_errors=False,
|
||||||
|
|
@ -384,7 +384,7 @@ class ArrayFacet(Facet):
|
||||||
facet_results = []
|
facet_results = []
|
||||||
facets_timed_out = []
|
facets_timed_out = []
|
||||||
|
|
||||||
facet_size = self.get_facet_size()
|
facet_size = await self.get_facet_size()
|
||||||
for source_and_config in self.get_configs():
|
for source_and_config in self.get_configs():
|
||||||
config = source_and_config["config"]
|
config = source_and_config["config"]
|
||||||
source = source_and_config["source"]
|
source = source_and_config["source"]
|
||||||
|
|
@ -420,7 +420,7 @@ class ArrayFacet(Facet):
|
||||||
facet_sql,
|
facet_sql,
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting("facet_time_limit_ms"),
|
custom_time_limit=await self.ds.setting("facet_time_limit_ms"),
|
||||||
)
|
)
|
||||||
facet_results_values = []
|
facet_results_values = []
|
||||||
facet_results.append(
|
facet_results.append(
|
||||||
|
|
@ -491,7 +491,7 @@ class DateFacet(Facet):
|
||||||
suggested_facet_sql,
|
suggested_facet_sql,
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting("facet_suggest_time_limit_ms"),
|
custom_time_limit=await self.ds.setting("facet_suggest_time_limit_ms"),
|
||||||
log_sql_errors=False,
|
log_sql_errors=False,
|
||||||
)
|
)
|
||||||
values = tuple(r[0] for r in results.rows)
|
values = tuple(r[0] for r in results.rows)
|
||||||
|
|
@ -518,7 +518,7 @@ class DateFacet(Facet):
|
||||||
facet_results = []
|
facet_results = []
|
||||||
facets_timed_out = []
|
facets_timed_out = []
|
||||||
args = dict(self.get_querystring_pairs())
|
args = dict(self.get_querystring_pairs())
|
||||||
facet_size = self.get_facet_size()
|
facet_size = await self.get_facet_size()
|
||||||
for source_and_config in self.get_configs():
|
for source_and_config in self.get_configs():
|
||||||
config = source_and_config["config"]
|
config = source_and_config["config"]
|
||||||
source = source_and_config["source"]
|
source = source_and_config["source"]
|
||||||
|
|
@ -539,7 +539,7 @@ class DateFacet(Facet):
|
||||||
facet_sql,
|
facet_sql,
|
||||||
self.params,
|
self.params,
|
||||||
truncate=False,
|
truncate=False,
|
||||||
custom_time_limit=self.ds.setting("facet_time_limit_ms"),
|
custom_time_limit=await self.ds.setting("facet_time_limit_ms"),
|
||||||
)
|
)
|
||||||
facet_results_values = []
|
facet_results_values = []
|
||||||
facet_results.append(
|
facet_results.append(
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class Urls:
|
||||||
if not isinstance(path, PrefixedUrlString):
|
if not isinstance(path, PrefixedUrlString):
|
||||||
if path.startswith("/"):
|
if path.startswith("/"):
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
path = self.ds.setting("base_url") + path
|
path = self.ds._setting_sync("base_url") + path
|
||||||
if format is not None:
|
if format is not None:
|
||||||
path = path_with_format(path=path, format=format)
|
path = path_with_format(path=path, format=format)
|
||||||
return PrefixedUrlString(path)
|
return PrefixedUrlString(path)
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,7 @@ class DataView(BaseView):
|
||||||
if key not in ("_labels", "_facet", "_size")
|
if key not in ("_labels", "_facet", "_size")
|
||||||
]
|
]
|
||||||
+ [("_size", "max")],
|
+ [("_size", "max")],
|
||||||
"settings": self.ds.settings_dict(),
|
"settings": await self.ds.settings_dict(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if "metadata" not in context:
|
if "metadata" not in context:
|
||||||
|
|
@ -385,7 +385,7 @@ class DataView(BaseView):
|
||||||
|
|
||||||
ttl = request.args.get("_ttl", None)
|
ttl = request.args.get("_ttl", None)
|
||||||
if ttl is None or not ttl.isdigit():
|
if ttl is None or not ttl.isdigit():
|
||||||
ttl = self.ds.setting("default_cache_ttl")
|
ttl = await self.ds.setting("default_cache_ttl")
|
||||||
|
|
||||||
return self.set_response_headers(r, ttl)
|
return self.set_response_headers(r, ttl)
|
||||||
|
|
||||||
|
|
@ -428,7 +428,7 @@ async def stream_csv(datasette, fetch_data, request, database):
|
||||||
request = Request(new_scope, receive)
|
request = Request(new_scope, receive)
|
||||||
if stream:
|
if stream:
|
||||||
# Some quick soundness checks
|
# Some quick soundness checks
|
||||||
if not datasette.setting("allow_csv_stream"):
|
if not await datasette.setting("allow_csv_stream"):
|
||||||
raise BadRequest("CSV streaming is disabled")
|
raise BadRequest("CSV streaming is disabled")
|
||||||
if request.args.get("_next"):
|
if request.args.get("_next"):
|
||||||
raise BadRequest("_next not allowed for CSV streaming")
|
raise BadRequest("_next not allowed for CSV streaming")
|
||||||
|
|
@ -477,7 +477,7 @@ async def stream_csv(datasette, fetch_data, request, database):
|
||||||
|
|
||||||
async def stream_fn(r):
|
async def stream_fn(r):
|
||||||
nonlocal data, trace
|
nonlocal data, trace
|
||||||
limited_writer = LimitedWriter(r, datasette.setting("max_csv_mb"))
|
limited_writer = LimitedWriter(r, await datasette.setting("max_csv_mb"))
|
||||||
if trace:
|
if trace:
|
||||||
await limited_writer.write(preamble)
|
await limited_writer.write(preamble)
|
||||||
writer = csv.writer(EscapeHtmlWriter(limited_writer))
|
writer = csv.writer(EscapeHtmlWriter(limited_writer))
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ class DatabaseView(View):
|
||||||
show_hidden=request.args.get("_show_hidden"),
|
show_hidden=request.args.get("_show_hidden"),
|
||||||
editable=True,
|
editable=True,
|
||||||
count_limit=db.count_limit,
|
count_limit=db.count_limit,
|
||||||
allow_download=datasette.setting("allow_download")
|
allow_download=await datasette.setting("allow_download")
|
||||||
and not db.is_mutable
|
and not db.is_mutable
|
||||||
and not db.is_memory,
|
and not db.is_memory,
|
||||||
attached_databases=attached_databases,
|
attached_databases=attached_databases,
|
||||||
|
|
@ -390,7 +390,7 @@ async def database_download(request, datasette):
|
||||||
|
|
||||||
if db.is_memory:
|
if db.is_memory:
|
||||||
raise DatasetteError("Cannot download in-memory databases", status=404)
|
raise DatasetteError("Cannot download in-memory databases", status=404)
|
||||||
if not datasette.setting("allow_download") or db.is_mutable:
|
if not await datasette.setting("allow_download") or db.is_mutable:
|
||||||
raise Forbidden("Database download is forbidden")
|
raise Forbidden("Database download is forbidden")
|
||||||
if not db.path:
|
if not db.path:
|
||||||
raise DatasetteError("Cannot download database", status=404)
|
raise DatasetteError("Cannot download database", status=404)
|
||||||
|
|
@ -1190,7 +1190,7 @@ async def _table_columns(datasette, database_name):
|
||||||
|
|
||||||
async def display_rows(datasette, database, request, rows, columns):
|
async def display_rows(datasette, database, request, rows, columns):
|
||||||
display_rows = []
|
display_rows = []
|
||||||
truncate_cells = datasette.setting("truncate_cells_html")
|
truncate_cells = await datasette.setting("truncate_cells_html")
|
||||||
for row in rows:
|
for row in rows:
|
||||||
display_row = []
|
display_row = []
|
||||||
for column, value in zip(columns, row):
|
for column, value in zip(columns, row):
|
||||||
|
|
|
||||||
|
|
@ -620,8 +620,8 @@ class CreateTokenView(BaseView):
|
||||||
name = "create_token"
|
name = "create_token"
|
||||||
has_json_alternate = False
|
has_json_alternate = False
|
||||||
|
|
||||||
def check_permission(self, request):
|
async def check_permission(self, request):
|
||||||
if not self.ds.setting("allow_signed_tokens"):
|
if not await self.ds.setting("allow_signed_tokens"):
|
||||||
raise Forbidden("Signed tokens are not enabled for this Datasette instance")
|
raise Forbidden("Signed tokens are not enabled for this Datasette instance")
|
||||||
if not request.actor:
|
if not request.actor:
|
||||||
raise Forbidden("You must be logged in to create a token")
|
raise Forbidden("You must be logged in to create a token")
|
||||||
|
|
@ -635,7 +635,7 @@ class CreateTokenView(BaseView):
|
||||||
)
|
)
|
||||||
|
|
||||||
async def shared(self, request):
|
async def shared(self, request):
|
||||||
self.check_permission(request)
|
await self.check_permission(request)
|
||||||
# Build list of databases and tables the user has permission to view
|
# Build list of databases and tables the user has permission to view
|
||||||
db_page = await self.ds.allowed_resources("view-database", request.actor)
|
db_page = await self.ds.allowed_resources("view-database", request.actor)
|
||||||
allowed_databases = [r async for r in db_page.all()]
|
allowed_databases = [r async for r in db_page.all()]
|
||||||
|
|
@ -681,13 +681,13 @@ class CreateTokenView(BaseView):
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
self.check_permission(request)
|
await self.check_permission(request)
|
||||||
return await self.render(
|
return await self.render(
|
||||||
["create_token.html"], request, await self.shared(request)
|
["create_token.html"], request, await self.shared(request)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def post(self, request):
|
async def post(self, request):
|
||||||
self.check_permission(request)
|
await self.check_permission(request)
|
||||||
post = await request.post_vars()
|
post = await request.post_vars()
|
||||||
errors = []
|
errors = []
|
||||||
expires_after = None
|
expires_after = None
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ async def display_columns_and_rows(
|
||||||
}
|
}
|
||||||
|
|
||||||
cell_rows = []
|
cell_rows = []
|
||||||
base_url = datasette.setting("base_url")
|
base_url = await datasette.setting("base_url")
|
||||||
for row in rows:
|
for row in rows:
|
||||||
cells = []
|
cells = []
|
||||||
# Unless we are a view, the first column is a link - either to the rowid
|
# Unless we are a view, the first column is a link - either to the rowid
|
||||||
|
|
@ -389,7 +389,7 @@ class TableInsertView(BaseView):
|
||||||
return _errors(['"rows" must be a list of dictionaries'])
|
return _errors(['"rows" must be a list of dictionaries'])
|
||||||
|
|
||||||
# Does this exceed max_insert_rows?
|
# Does this exceed max_insert_rows?
|
||||||
max_insert_rows = self.ds.setting("max_insert_rows")
|
max_insert_rows = await self.ds.setting("max_insert_rows")
|
||||||
if len(rows) > max_insert_rows:
|
if len(rows) > max_insert_rows:
|
||||||
return _errors(
|
return _errors(
|
||||||
["Too many rows, maximum allowed is {}".format(max_insert_rows)]
|
["Too many rows, maximum allowed is {}".format(max_insert_rows)]
|
||||||
|
|
@ -771,7 +771,7 @@ async def table_view(datasette, request):
|
||||||
# Cache TTL header
|
# Cache TTL header
|
||||||
ttl = request.args.get("_ttl", None)
|
ttl = request.args.get("_ttl", None)
|
||||||
if ttl is None or not ttl.isdigit():
|
if ttl is None or not ttl.isdigit():
|
||||||
ttl = datasette.setting("default_cache_ttl")
|
ttl = await datasette.setting("default_cache_ttl")
|
||||||
|
|
||||||
if datasette.cache_headers and response.status == 200:
|
if datasette.cache_headers and response.status == 200:
|
||||||
ttl = int(ttl)
|
ttl = int(ttl)
|
||||||
|
|
@ -919,11 +919,11 @@ async def table_view_traced(datasette, request):
|
||||||
append_querystring=append_querystring,
|
append_querystring=append_querystring,
|
||||||
path_with_replaced_args=path_with_replaced_args,
|
path_with_replaced_args=path_with_replaced_args,
|
||||||
fix_path=datasette.urls.path,
|
fix_path=datasette.urls.path,
|
||||||
settings=datasette.settings_dict(),
|
settings=await datasette.settings_dict(),
|
||||||
# TODO: review up all of these hacks:
|
# TODO: review up all of these hacks:
|
||||||
alternate_url_json=alternate_url_json,
|
alternate_url_json=alternate_url_json,
|
||||||
datasette_allow_facet=(
|
datasette_allow_facet=(
|
||||||
"true" if datasette.setting("allow_facet") else "false"
|
"true" if await datasette.setting("allow_facet") else "false"
|
||||||
),
|
),
|
||||||
is_sortable=any(c["sortable"] for c in data["display_columns"]),
|
is_sortable=any(c["sortable"] for c in data["display_columns"]),
|
||||||
allow_execute_sql=await datasette.allowed(
|
allow_execute_sql=await datasette.allowed(
|
||||||
|
|
@ -1377,8 +1377,8 @@ async def table_view_data(
|
||||||
suggested_facets = []
|
suggested_facets = []
|
||||||
# Calculate suggested facets
|
# Calculate suggested facets
|
||||||
if (
|
if (
|
||||||
datasette.setting("suggest_facets")
|
await datasette.setting("suggest_facets")
|
||||||
and datasette.setting("allow_facet")
|
and await datasette.setting("allow_facet")
|
||||||
and not _next
|
and not _next
|
||||||
and not nofacet
|
and not nofacet
|
||||||
and not nosuggest
|
and not nosuggest
|
||||||
|
|
@ -1390,7 +1390,7 @@ async def table_view_data(
|
||||||
return suggested_facets
|
return suggested_facets
|
||||||
|
|
||||||
# Faceting
|
# Faceting
|
||||||
if not datasette.setting("allow_facet") and any(
|
if not await datasette.setting("allow_facet") and any(
|
||||||
arg.startswith("_facet") for arg in request.args
|
arg.startswith("_facet") for arg in request.args
|
||||||
):
|
):
|
||||||
raise BadRequest("_facet= is not allowed")
|
raise BadRequest("_facet= is not allowed")
|
||||||
|
|
@ -1477,7 +1477,7 @@ async def table_view_data(
|
||||||
results.description,
|
results.description,
|
||||||
rows,
|
rows,
|
||||||
link_column=not is_view,
|
link_column=not is_view,
|
||||||
truncate_cells=datasette.setting("truncate_cells_html"),
|
truncate_cells=await datasette.setting("truncate_cells_html"),
|
||||||
sortable_columns=sortable_columns,
|
sortable_columns=sortable_columns,
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ def prepare_connection(conn, database, datasette):
|
||||||
|
|
||||||
def prepare_connection_args():
|
def prepare_connection_args():
|
||||||
return 'database={}, datasette.plugin_config("name-of-plugin")={}'.format(
|
return 'database={}, datasette.plugin_config("name-of-plugin")={}'.format(
|
||||||
database, datasette.plugin_config("name-of-plugin")
|
database, datasette._plugin_config_sync("name-of-plugin")
|
||||||
)
|
)
|
||||||
|
|
||||||
conn.create_function("prepare_connection_args", 0, prepare_connection_args)
|
conn.create_function("prepare_connection_args", 0, prepare_connection_args)
|
||||||
|
|
@ -84,7 +84,7 @@ def extra_body_script(
|
||||||
"template": template,
|
"template": template,
|
||||||
"database": database,
|
"database": database,
|
||||||
"table": table,
|
"table": table,
|
||||||
"config": datasette.plugin_config(
|
"config": await datasette.plugin_config(
|
||||||
"name-of-plugin",
|
"name-of-plugin",
|
||||||
database=database,
|
database=database,
|
||||||
table=table,
|
table=table,
|
||||||
|
|
@ -113,7 +113,7 @@ def render_cell(row, value, column, table, database, datasette, request):
|
||||||
"column": column,
|
"column": column,
|
||||||
"table": table,
|
"table": table,
|
||||||
"database": database,
|
"database": database,
|
||||||
"config": datasette.plugin_config(
|
"config": await datasette.plugin_config(
|
||||||
"name-of-plugin",
|
"name-of-plugin",
|
||||||
database=database,
|
database=database,
|
||||||
table=table,
|
table=table,
|
||||||
|
|
@ -453,8 +453,8 @@ def skip_csrf(scope):
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def register_actions(datasette):
|
def register_actions(datasette):
|
||||||
extras_old = datasette.plugin_config("datasette-register-permissions") or {}
|
extras_old = datasette._plugin_config_sync("datasette-register-permissions") or {}
|
||||||
extras_new = datasette.plugin_config("datasette-register-actions") or {}
|
extras_new = datasette._plugin_config_sync("datasette-register-actions") or {}
|
||||||
|
|
||||||
actions = [
|
actions = [
|
||||||
Action(
|
Action(
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ def table_actions(datasette, database, table, actor, request):
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def register_routes(datasette):
|
def register_routes(datasette):
|
||||||
config = datasette.plugin_config("register-route-demo")
|
config = datasette._plugin_config_sync("register-route-demo")
|
||||||
if not config:
|
if not config:
|
||||||
return
|
return
|
||||||
path = config["path"]
|
path = config["path"]
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,9 @@ def test_sign_unsign(datasette, value, namespace):
|
||||||
("allow_csv_stream", True),
|
("allow_csv_stream", True),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_datasette_setting(datasette, setting, expected):
|
@pytest.mark.asyncio
|
||||||
assert datasette.setting(setting) == expected
|
async def test_datasette_setting(datasette, setting, expected):
|
||||||
|
assert await datasette.setting(setting) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
|
||||||
|
|
@ -224,26 +224,26 @@ async def test_hook_render_cell_async(ds_client, path):
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_plugin_config(ds_client):
|
async def test_plugin_config(ds_client):
|
||||||
assert {"depth": "table"} == ds_client.ds.plugin_config(
|
assert {"depth": "table"} == await ds_client.ds.plugin_config(
|
||||||
"name-of-plugin", database="fixtures", table="sortable"
|
"name-of-plugin", database="fixtures", table="sortable"
|
||||||
)
|
)
|
||||||
assert {"depth": "database"} == ds_client.ds.plugin_config(
|
assert {"depth": "database"} == await ds_client.ds.plugin_config(
|
||||||
"name-of-plugin", database="fixtures", table="unknown_table"
|
"name-of-plugin", database="fixtures", table="unknown_table"
|
||||||
)
|
)
|
||||||
assert {"depth": "database"} == ds_client.ds.plugin_config(
|
assert {"depth": "database"} == await ds_client.ds.plugin_config(
|
||||||
"name-of-plugin", database="fixtures"
|
"name-of-plugin", database="fixtures"
|
||||||
)
|
)
|
||||||
assert {"depth": "root"} == ds_client.ds.plugin_config(
|
assert {"depth": "root"} == await ds_client.ds.plugin_config(
|
||||||
"name-of-plugin", database="unknown_database"
|
"name-of-plugin", database="unknown_database"
|
||||||
)
|
)
|
||||||
assert {"depth": "root"} == ds_client.ds.plugin_config("name-of-plugin")
|
assert {"depth": "root"} == await ds_client.ds.plugin_config("name-of-plugin")
|
||||||
assert None is ds_client.ds.plugin_config("unknown-plugin")
|
assert None is await ds_client.ds.plugin_config("unknown-plugin")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_plugin_config_env(ds_client, monkeypatch):
|
async def test_plugin_config_env(ds_client, monkeypatch):
|
||||||
monkeypatch.setenv("FOO_ENV", "FROM_ENVIRONMENT")
|
monkeypatch.setenv("FOO_ENV", "FROM_ENVIRONMENT")
|
||||||
assert ds_client.ds.plugin_config("env-plugin") == {"foo": "FROM_ENVIRONMENT"}
|
assert await ds_client.ds.plugin_config("env-plugin") == {"foo": "FROM_ENVIRONMENT"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
|
@ -252,13 +252,13 @@ async def test_plugin_config_env_from_config(monkeypatch):
|
||||||
datasette = Datasette(
|
datasette = Datasette(
|
||||||
config={"plugins": {"env-plugin": {"setting": {"$env": "FOO_ENV"}}}}
|
config={"plugins": {"env-plugin": {"setting": {"$env": "FOO_ENV"}}}}
|
||||||
)
|
)
|
||||||
assert datasette.plugin_config("env-plugin") == {"setting": "FROM_ENVIRONMENT_2"}
|
assert await datasette.plugin_config("env-plugin") == {"setting": "FROM_ENVIRONMENT_2"}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_plugin_config_env_from_list(ds_client):
|
async def test_plugin_config_env_from_list(ds_client):
|
||||||
os.environ["FOO_ENV"] = "FROM_ENVIRONMENT"
|
os.environ["FOO_ENV"] = "FROM_ENVIRONMENT"
|
||||||
assert [{"in_a_list": "FROM_ENVIRONMENT"}] == ds_client.ds.plugin_config(
|
assert [{"in_a_list": "FROM_ENVIRONMENT"}] == await ds_client.ds.plugin_config(
|
||||||
"env-plugin-list"
|
"env-plugin-list"
|
||||||
)
|
)
|
||||||
del os.environ["FOO_ENV"]
|
del os.environ["FOO_ENV"]
|
||||||
|
|
@ -268,7 +268,7 @@ async def test_plugin_config_env_from_list(ds_client):
|
||||||
async def test_plugin_config_file(ds_client):
|
async def test_plugin_config_file(ds_client):
|
||||||
with open(TEMP_PLUGIN_SECRET_FILE, "w") as fp:
|
with open(TEMP_PLUGIN_SECRET_FILE, "w") as fp:
|
||||||
fp.write("FROM_FILE")
|
fp.write("FROM_FILE")
|
||||||
assert {"foo": "FROM_FILE"} == ds_client.ds.plugin_config("file-plugin")
|
assert {"foo": "FROM_FILE"} == await ds_client.ds.plugin_config("file-plugin")
|
||||||
os.remove(TEMP_PLUGIN_SECRET_FILE)
|
os.remove(TEMP_PLUGIN_SECRET_FILE)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue