mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Magic parameters for canned queries
Closes #842 Includes a new plugin hook, register_magic_parameters()
This commit is contained in:
parent
4b142862f2
commit
563f5a2d3a
14 changed files with 477 additions and 167 deletions
|
|
@ -132,7 +132,7 @@ class Database:
|
|||
with sqlite_timelimit(conn, time_limit_ms):
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(sql, params or {})
|
||||
cursor.execute(sql, params if params is not None else {})
|
||||
max_returned_rows = self.ds.max_returned_rows
|
||||
if max_returned_rows == page_size:
|
||||
max_returned_rows += 1
|
||||
|
|
|
|||
55
datasette/default_magic_parameters.py
Normal file
55
datasette/default_magic_parameters.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
from datasette import hookimpl
|
||||
from datasette.utils import escape_fts
|
||||
import datetime
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
def header(key, request):
|
||||
key = key.replace("_", "-").encode("utf-8")
|
||||
headers_dict = dict(request.scope["headers"])
|
||||
return headers_dict[key].decode("utf-8")
|
||||
|
||||
|
||||
def actor(key, request):
|
||||
if request.actor is None:
|
||||
raise KeyError
|
||||
return request.actor[key]
|
||||
|
||||
|
||||
def cookie(key, request):
|
||||
return request.cookies[key]
|
||||
|
||||
|
||||
def timestamp(key, request):
|
||||
if key == "epoch":
|
||||
return int(time.time())
|
||||
elif key == "date_utc":
|
||||
return datetime.datetime.utcnow().date().isoformat()
|
||||
elif key == "datetime_utc":
|
||||
return datetime.datetime.utcnow().strftime(r"%Y-%m-%dT%H:%M:%S") + "Z"
|
||||
else:
|
||||
raise KeyError
|
||||
|
||||
|
||||
def random(key, request):
|
||||
if key.startswith("chars_") and key.split("chars_")[-1].isdigit():
|
||||
num_chars = int(key.split("chars_")[-1])
|
||||
if num_chars % 2 == 1:
|
||||
urandom_len = (num_chars + 1) / 2
|
||||
else:
|
||||
urandom_len = num_chars / 2
|
||||
return os.urandom(int(urandom_len)).hex()[:num_chars]
|
||||
else:
|
||||
raise KeyError
|
||||
|
||||
|
||||
@hookimpl
|
||||
def register_magic_parameters():
|
||||
return [
|
||||
("header", header),
|
||||
("actor", actor),
|
||||
("cookie", cookie),
|
||||
("timestamp", timestamp),
|
||||
("random", random),
|
||||
]
|
||||
|
|
@ -83,3 +83,8 @@ def permission_allowed(datasette, actor, action, resource):
|
|||
@hookspec
|
||||
def canned_queries(datasette, database, actor):
|
||||
"Return a dictonary of canned query definitions or an awaitable function that returns them"
|
||||
|
||||
|
||||
@hookspec
|
||||
def register_magic_parameters(datasette):
|
||||
"Return a list of (name, function) magic parameter functions"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ DEFAULT_PLUGINS = (
|
|||
"datasette.sql_functions",
|
||||
"datasette.actor_auth_cookie",
|
||||
"datasette.default_permissions",
|
||||
"datasette.default_magic_parameters",
|
||||
)
|
||||
|
||||
pm = pluggy.PluginManager("datasette")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import base64
|
|||
import click
|
||||
import hashlib
|
||||
import inspect
|
||||
import itertools
|
||||
import json
|
||||
import mergedeep
|
||||
import os
|
||||
|
|
@ -17,6 +18,7 @@ import urllib
|
|||
import numbers
|
||||
import yaml
|
||||
from .shutil_backport import copytree
|
||||
from ..plugins import pm
|
||||
|
||||
try:
|
||||
import pysqlite3 as sqlite3
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import itertools
|
||||
import jinja2
|
||||
|
||||
from datasette.utils import (
|
||||
|
|
@ -165,11 +166,12 @@ class QueryView(DataView):
|
|||
named_parameter_values = {
|
||||
named_parameter: params.get(named_parameter) or ""
|
||||
for named_parameter in named_parameters
|
||||
if not named_parameter.startswith("_")
|
||||
}
|
||||
|
||||
# Set to blank string if missing from params
|
||||
for named_parameter in named_parameters:
|
||||
if named_parameter not in params:
|
||||
if named_parameter not in params and not named_parameter.startswith("_"):
|
||||
params[named_parameter] = ""
|
||||
|
||||
extra_args = {}
|
||||
|
|
@ -184,9 +186,13 @@ class QueryView(DataView):
|
|||
if write:
|
||||
if request.method == "POST":
|
||||
params = await request.post_vars()
|
||||
if canned_query:
|
||||
params_for_query = MagicParameters(params, request, self.ds)
|
||||
else:
|
||||
params_for_query = params
|
||||
try:
|
||||
cursor = await self.ds.databases[database].execute_write(
|
||||
sql, params, block=True
|
||||
sql, params_for_query, block=True
|
||||
)
|
||||
message = metadata.get(
|
||||
"on_success_message"
|
||||
|
|
@ -227,8 +233,12 @@ class QueryView(DataView):
|
|||
templates,
|
||||
)
|
||||
else: # Not a write
|
||||
if canned_query:
|
||||
params_for_query = MagicParameters(params, request, self.ds)
|
||||
else:
|
||||
params_for_query = params
|
||||
results = await self.ds.execute(
|
||||
database, sql, params, truncate=True, **extra_args
|
||||
database, sql, params_for_query, truncate=True, **extra_args
|
||||
)
|
||||
columns = [r[0] for r in results.description]
|
||||
|
||||
|
|
@ -298,3 +308,25 @@ class QueryView(DataView):
|
|||
extra_template,
|
||||
templates,
|
||||
)
|
||||
|
||||
|
||||
class MagicParameters(dict):
|
||||
def __init__(self, data, request, datasette):
|
||||
super().__init__(data)
|
||||
self._request = request
|
||||
self._magics = dict(
|
||||
itertools.chain.from_iterable(
|
||||
pm.hook.register_magic_parameters(datasette=datasette)
|
||||
)
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key.startswith("_") and key.count("_") >= 2:
|
||||
prefix, suffix = key[1:].split("_", 1)
|
||||
if prefix in self._magics:
|
||||
try:
|
||||
return self._magics[prefix](suffix, self._request)
|
||||
except KeyError:
|
||||
return super().__getitem__(key)
|
||||
else:
|
||||
return super().__getitem__(key)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue