Fix blacken-docs errors and warnings, refs #1718

api-extras
Simon Willison 2022-04-24 08:51:09 -07:00
rodzic 36573638b0
commit 92b26673d8
4 zmienionych plików z 289 dodań i 141 usunięć

Wyświetl plik

@ -381,11 +381,10 @@ Authentication plugins can set signed ``ds_actor`` cookies themselves like so:
.. code-block:: python
response = Response.redirect("/")
response.set_cookie("ds_actor", datasette.sign({
"a": {
"id": "cleopaws"
}
}, "actor"))
response.set_cookie(
"ds_actor",
datasette.sign({"a": {"id": "cleopaws"}}, "actor"),
)
Note that you need to pass ``"actor"`` as the namespace to :ref:`datasette_sign`.
@ -412,12 +411,16 @@ To include an expiry, add a ``"e"`` key to the cookie value containing a `base62
expires_at = int(time.time()) + (24 * 60 * 60)
response = Response.redirect("/")
response.set_cookie("ds_actor", datasette.sign({
"a": {
"id": "cleopaws"
},
"e": baseconv.base62.encode(expires_at),
}, "actor"))
response.set_cookie(
"ds_actor",
datasette.sign(
{
"a": {"id": "cleopaws"},
"e": baseconv.base62.encode(expires_at),
},
"actor",
),
)
The resulting cookie will encode data that looks something like this:

Wyświetl plik

@ -70,10 +70,10 @@ And a class method that can be used to create fake request objects for use in te
from datasette import Request
from pprint import pprint
request = Request.fake("/fixtures/facetable/", url_vars={
"database": "fixtures",
"table": "facetable"
})
request = Request.fake(
"/fixtures/facetable/",
url_vars={"database": "fixtures", "table": "facetable"},
)
pprint(request.scope)
This outputs::
@ -146,7 +146,7 @@ For example:
response = Response(
"<xml>This is XML</xml>",
content_type="application/xml; charset=utf-8"
content_type="application/xml; charset=utf-8",
)
The quickest way to create responses is using the ``Response.text(...)``, ``Response.html(...)``, ``Response.json(...)`` or ``Response.redirect(...)`` helper methods:
@ -157,9 +157,13 @@ The quickest way to create responses is using the ``Response.text(...)``, ``Resp
html_response = Response.html("This is HTML")
json_response = Response.json({"this_is": "json"})
text_response = Response.text("This will become utf-8 encoded text")
text_response = Response.text(
"This will become utf-8 encoded text"
)
# Redirects are served as 302, unless you pass status=301:
redirect_response = Response.redirect("https://latest.datasette.io/")
redirect_response = Response.redirect(
"https://latest.datasette.io/"
)
Each of these responses will use the correct corresponding content-type - ``text/html; charset=utf-8``, ``application/json; charset=utf-8`` or ``text/plain; charset=utf-8`` respectively.
@ -207,13 +211,17 @@ To set cookies on the response, use the ``response.set_cookie(...)`` method. The
httponly=False,
samesite="lax",
):
...
You can use this with :ref:`datasette.sign() <datasette_sign>` to set signed cookies. Here's how you would set the :ref:`ds_actor cookie <authentication_ds_actor>` for use with Datasette :ref:`authentication <authentication>`:
.. code-block:: python
response = Response.redirect("/")
response.set_cookie("ds_actor", datasette.sign({"a": {"id": "cleopaws"}}, "actor"))
response.set_cookie(
"ds_actor",
datasette.sign({"a": {"id": "cleopaws"}}, "actor"),
)
return response
.. _internals_datasette:
@ -236,13 +244,16 @@ You can create your own instance of this - for example to help write tests for a
datasette = Datasette(files=["/path/to/my-database.db"])
# Pass metadata as a JSON dictionary like this
datasette = Datasette(files=["/path/to/my-database.db"], metadata={
"databases": {
"my-database": {
"description": "This is my database"
datasette = Datasette(
files=["/path/to/my-database.db"],
metadata={
"databases": {
"my-database": {
"description": "This is my database"
}
}
}
})
},
)
Constructor parameters include:
@ -345,7 +356,7 @@ This is useful when you need to check multiple permissions at once. For example,
("view-table", (database, table)),
("view-database", database),
"view-instance",
]
],
)
.. _datasette_check_visibilty:
@ -406,11 +417,13 @@ The ``db`` parameter should be an instance of the ``datasette.database.Database`
from datasette.database import Database
datasette.add_database(Database(
datasette,
path="path/to/my-new-database.db",
is_mutable=True
))
datasette.add_database(
Database(
datasette,
path="path/to/my-new-database.db",
is_mutable=True,
)
)
This will add a mutable database and serve it at ``/my-new-database``.
@ -418,8 +431,12 @@ This will add a mutable database and serve it at ``/my-new-database``.
.. code-block:: python
db = datasette.add_database(Database(datasette, memory_name="statistics"))
await db.execute_write("CREATE TABLE foo(id integer primary key)")
db = datasette.add_database(
Database(datasette, memory_name="statistics")
)
await db.execute_write(
"CREATE TABLE foo(id integer primary key)"
)
.. _datasette_add_memory_database:
@ -438,10 +455,9 @@ This is a shortcut for the following:
from datasette.database import Database
datasette.add_database(Database(
datasette,
memory_name="statistics"
))
datasette.add_database(
Database(datasette, memory_name="statistics")
)
Using either of these pattern will result in the in-memory database being served at ``/statistics``.
@ -516,7 +532,9 @@ Returns the absolute URL for the given path, including the protocol and host. Fo
.. code-block:: python
absolute_url = datasette.absolute_url(request, "/dbname/table.json")
absolute_url = datasette.absolute_url(
request, "/dbname/table.json"
)
# Would return "http://localhost:8001/dbname/table.json"
The current request object is used to determine the hostname and protocol that should be used for the returned URL. The :ref:`setting_force_https_urls` configuration setting is taken into account.
@ -578,7 +596,9 @@ These methods can be used with :ref:`internals_datasette_urls` - for example:
table_json = (
await datasette.client.get(
datasette.urls.table("fixtures", "facetable", format="json")
datasette.urls.table(
"fixtures", "facetable", format="json"
)
)
).json()
@ -754,6 +774,7 @@ Example usage:
"select sqlite_version()"
).fetchall()[0][0]
version = await db.execute_fn(get_version)
.. _database_execute_write:
@ -789,7 +810,7 @@ Like ``execute_write()`` but uses the ``sqlite3`` `conn.executemany() <https://d
await db.execute_write_many(
"insert into characters (id, name) values (?, ?)",
[(1, "Melanie"), (2, "Selma"), (2, "Viktor")]
[(1, "Melanie"), (2, "Selma"), (2, "Viktor")],
)
.. _database_execute_write_fn:
@ -811,10 +832,15 @@ For example:
def delete_and_return_count(conn):
conn.execute("delete from some_table where id > 5")
return conn.execute("select count(*) from some_table").fetchone()[0]
return conn.execute(
"select count(*) from some_table"
).fetchone()[0]
try:
num_rows_left = await database.execute_write_fn(delete_and_return_count)
num_rows_left = await database.execute_write_fn(
delete_and_return_count
)
except Exception as e:
print("An error occurred:", e)
@ -1021,6 +1047,7 @@ This example uses trace to record the start, end and duration of any HTTP GET re
from datasette.tracer import trace
import httpx
async def fetch_url(url):
with trace("fetch-url", url=url):
async with httpx.AsyncClient() as client:
@ -1051,9 +1078,9 @@ This example uses the :ref:`register_routes() <plugin_register_routes>` plugin h
from datasette import hookimpl
from datasette import tracer
@hookimpl
def register_routes():
async def parallel_queries(datasette):
db = datasette.get_database()
with tracer.trace_child_tasks():
@ -1061,7 +1088,12 @@ This example uses the :ref:`register_routes() <plugin_register_routes>` plugin h
db.execute("select 1"),
db.execute("select 2"),
)
return Response.json({"one": one.single_value(), "two": two.single_value()})
return Response.json(
{
"one": one.single_value(),
"two": two.single_value(),
}
)
return [
(r"/parallel-queries$", parallel_queries),

Wyświetl plik

@ -446,7 +446,7 @@ Most of the HTML pages served by Datasette provide a mechanism for discovering t
You can find this near the top of the source code of those pages, looking like this:
.. code-block:: python
.. code-block:: html
<link rel="alternate"
type="application/json+datasette"

Wyświetl plik

@ -44,9 +44,12 @@ aggregates and collations. For example:
from datasette import hookimpl
import random
@hookimpl
def prepare_connection(conn):
conn.create_function('random_integer', 2, random.randint)
conn.create_function(
"random_integer", 2, random.randint
)
This registers a SQL function called ``random_integer`` which takes two
arguments and can be called like this::
@ -72,9 +75,10 @@ example:
from datasette import hookimpl
@hookimpl
def prepare_jinja2_environment(env):
env.filters['uppercase'] = lambda u: u.upper()
env.filters["uppercase"] = lambda u: u.upper()
You can now use this filter in your custom templates like so::
@ -127,9 +131,7 @@ Here's an example plugin that adds a ``"user_agent"`` variable to the template c
@hookimpl
def extra_template_vars(request):
return {
"user_agent": request.headers.get("user-agent")
}
return {"user_agent": request.headers.get("user-agent")}
This example returns an awaitable function which adds a list of ``hidden_table_names`` to the context:
@ -140,9 +142,12 @@ This example returns an awaitable function which adds a list of ``hidden_table_n
async def hidden_table_names():
if database:
db = datasette.databases[database]
return {"hidden_table_names": await db.hidden_table_names()}
return {
"hidden_table_names": await db.hidden_table_names()
}
else:
return {}
return hidden_table_names
And here's an example which adds a ``sql_first(sql_query)`` function which executes a SQL statement and returns the first column of the first row of results:
@ -152,8 +157,15 @@ And here's an example which adds a ``sql_first(sql_query)`` function which execu
@hookimpl
def extra_template_vars(datasette, database):
async def sql_first(sql, dbname=None):
dbname = dbname or database or next(iter(datasette.databases.keys()))
return (await datasette.execute(dbname, sql)).rows[0][0]
dbname = (
dbname
or database
or next(iter(datasette.databases.keys()))
)
return (await datasette.execute(dbname, sql)).rows[
0
][0]
return {"sql_first": sql_first}
You can then use the new function in a template like so::
@ -178,6 +190,7 @@ This can be a list of URLs:
from datasette import hookimpl
@hookimpl
def extra_css_urls():
return [
@ -191,10 +204,12 @@ Or a list of dictionaries defining both a URL and an
@hookimpl
def extra_css_urls():
return [{
"url": "https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css",
"sri": "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4",
}]
return [
{
"url": "https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css",
"sri": "sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4",
}
]
This function can also return an awaitable function, useful if it needs to run any async code:
@ -204,7 +219,9 @@ This function can also return an awaitable function, useful if it needs to run a
def extra_css_urls(datasette):
async def inner():
db = datasette.get_database()
results = await db.execute("select url from css_files")
results = await db.execute(
"select url from css_files"
)
return [r[0] for r in results]
return inner
@ -225,12 +242,15 @@ return a list of URLs, a list of dictionaries or an awaitable function that retu
from datasette import hookimpl
@hookimpl
def extra_js_urls():
return [{
"url": "https://code.jquery.com/jquery-3.3.1.slim.min.js",
"sri": "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo",
}]
return [
{
"url": "https://code.jquery.com/jquery-3.3.1.slim.min.js",
"sri": "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo",
}
]
You can also return URLs to files from your plugin's ``static/`` directory, if
you have one:
@ -239,9 +259,7 @@ you have one:
@hookimpl
def extra_js_urls():
return [
"/-/static-plugins/your-plugin/app.js"
]
return ["/-/static-plugins/your-plugin/app.js"]
Note that `your-plugin` here should be the hyphenated plugin name - the name that is displayed in the list on the `/-/plugins` debug page.
@ -251,9 +269,11 @@ If your code uses `JavaScript modules <https://developer.mozilla.org/en-US/docs/
@hookimpl
def extra_js_urls():
return [{
"url": "/-/static-plugins/your-plugin/app.js",
"module": True
return [
{
"url": "/-/static-plugins/your-plugin/app.js",
"module": True,
}
]
Examples: `datasette-cluster-map <https://datasette.io/plugins/datasette-cluster-map>`_, `datasette-vega <https://datasette.io/plugins/datasette-vega>`_
@ -281,7 +301,7 @@ Use a dictionary if you want to specify that the code should be placed in a ``<s
def extra_body_script():
return {
"module": True,
"script": "console.log('Your JavaScript goes here...')"
"script": "console.log('Your JavaScript goes here...')",
}
This will add the following to the end of your page:
@ -311,7 +331,9 @@ Let's say you want to build a plugin that adds a ``datasette publish my_hosting_
.. code-block:: python
from datasette import hookimpl
from datasette.publish.common import add_common_publish_arguments_and_options
from datasette.publish.common import (
add_common_publish_arguments_and_options,
)
import click
@ -345,7 +367,7 @@ Let's say you want to build a plugin that adds a ``datasette publish my_hosting_
about_url,
api_key,
):
# Your implementation goes here
...
Examples: `datasette-publish-fly <https://datasette.io/plugins/datasette-publish-fly>`_, `datasette-publish-vercel <https://datasette.io/plugins/datasette-publish-vercel>`_
@ -400,7 +422,9 @@ If the value matches that pattern, the plugin returns an HTML link element:
if not isinstance(value, str):
return None
stripped = value.strip()
if not stripped.startswith("{") and stripped.endswith("}"):
if not stripped.startswith("{") and stripped.endswith(
"}"
):
return None
try:
data = json.loads(value)
@ -412,14 +436,18 @@ If the value matches that pattern, the plugin returns an HTML link element:
return None
href = data["href"]
if not (
href.startswith("/") or href.startswith("http://")
href.startswith("/")
or href.startswith("http://")
or href.startswith("https://")
):
return None
return markupsafe.Markup('<a href="{href}">{label}</a>'.format(
href=markupsafe.escape(data["href"]),
label=markupsafe.escape(data["label"] or "") or "&nbsp;"
))
return markupsafe.Markup(
'<a href="{href}">{label}</a>'.format(
href=markupsafe.escape(data["href"]),
label=markupsafe.escape(data["label"] or "")
or "&nbsp;",
)
)
Examples: `datasette-render-binary <https://datasette.io/plugins/datasette-render-binary>`_, `datasette-render-markdown <https://datasette.io/plugins/datasette-render-markdown>`__, `datasette-json-html <https://datasette.io/plugins/datasette-json-html>`__
@ -516,7 +544,7 @@ Here is a more complex example:
return Response(
"\n".join(lines),
content_type="text/plain; charset=utf-8",
headers={"x-sqlite-version": result.first()[0]}
headers={"x-sqlite-version": result.first()[0]},
)
And here is an example ``can_render`` function which returns ``True`` only if the query results contain the columns ``atom_id``, ``atom_title`` and ``atom_updated``:
@ -524,7 +552,11 @@ And here is an example ``can_render`` function which returns ``True`` only if th
.. code-block:: python
def can_render_demo(columns):
return {"atom_id", "atom_title", "atom_updated"}.issubset(columns)
return {
"atom_id",
"atom_title",
"atom_updated",
}.issubset(columns)
Examples: `datasette-atom <https://datasette.io/plugins/datasette-atom>`_, `datasette-ics <https://datasette.io/plugins/datasette-ics>`_, `datasette-geojson <https://datasette.io/plugins/datasette-geojson>`__
@ -548,16 +580,14 @@ Return a list of ``(regex, view_function)`` pairs, something like this:
async def hello_from(request):
name = request.url_vars["name"]
return Response.html("Hello from {}".format(
html.escape(name)
))
return Response.html(
"Hello from {}".format(html.escape(name))
)
@hookimpl
def register_routes():
return [
(r"^/hello-from/(?P<name>.*)$", hello_from)
]
return [(r"^/hello-from/(?P<name>.*)$", hello_from)]
The view functions can take a number of different optional arguments. The corresponding argument will be passed to your function depending on its named parameters - a form of dependency injection.
@ -606,10 +636,13 @@ This example registers a new ``datasette verify file1.db file2.db`` command that
import click
import sqlite3
@hookimpl
def register_commands(cli):
@cli.command()
@click.argument("files", type=click.Path(exists=True), nargs=-1)
@click.argument(
"files", type=click.Path(exists=True), nargs=-1
)
def verify(files):
"Verify that files can be opened by Datasette"
for file in files:
@ -617,7 +650,9 @@ This example registers a new ``datasette verify file1.db file2.db`` command that
try:
conn.execute("select * from sqlite_master")
except sqlite3.DatabaseError:
raise click.ClickException("Invalid database: {}".format(file))
raise click.ClickException(
"Invalid database: {}".format(file)
)
The new command can then be executed like so::
@ -656,15 +691,18 @@ Each Facet subclass implements a new type of facet operation. The class should l
async def suggest(self):
# Use self.sql and self.params to suggest some facets
suggested_facets = []
suggested_facets.append({
"name": column, # Or other unique name
# Construct the URL that will enable this facet:
"toggle_url": self.ds.absolute_url(
self.request, path_with_added_args(
self.request, {"_facet": column}
)
),
})
suggested_facets.append(
{
"name": column, # Or other unique name
# Construct the URL that will enable this facet:
"toggle_url": self.ds.absolute_url(
self.request,
path_with_added_args(
self.request, {"_facet": column}
),
),
}
)
return suggested_facets
async def facet_results(self):
@ -678,18 +716,25 @@ Each Facet subclass implements a new type of facet operation. The class should l
try:
facet_results_values = []
# More calculations...
facet_results_values.append({
"value": value,
"label": label,
"count": count,
"toggle_url": self.ds.absolute_url(self.request, toggle_path),
"selected": selected,
})
facet_results.append({
"name": column,
"results": facet_results_values,
"truncated": len(facet_rows_results) > facet_size,
})
facet_results_values.append(
{
"value": value,
"label": label,
"count": count,
"toggle_url": self.ds.absolute_url(
self.request, toggle_path
),
"selected": selected,
}
)
facet_results.append(
{
"name": column,
"results": facet_results_values,
"truncated": len(facet_rows_results)
> facet_size,
}
)
except QueryInterrupted:
facets_timed_out.append(column)
@ -728,21 +773,33 @@ This example plugin adds a ``x-databases`` HTTP header listing the currently att
def asgi_wrapper(datasette):
def wrap_with_databases_header(app):
@wraps(app)
async def add_x_databases_header(scope, receive, send):
async def add_x_databases_header(
scope, receive, send
):
async def wrapped_send(event):
if event["type"] == "http.response.start":
original_headers = event.get("headers") or []
original_headers = (
event.get("headers") or []
)
event = {
"type": event["type"],
"status": event["status"],
"headers": original_headers + [
[b"x-databases",
", ".join(datasette.databases.keys()).encode("utf-8")]
"headers": original_headers
+ [
[
b"x-databases",
", ".join(
datasette.databases.keys()
).encode("utf-8"),
]
],
}
await send(event)
await app(scope, receive, wrapped_send)
return add_x_databases_header
return wrap_with_databases_header
Examples: `datasette-cors <https://datasette.io/plugins/datasette-cors>`__, `datasette-pyinstrument <https://datasette.io/plugins/datasette-pyinstrument>`__
@ -759,7 +816,9 @@ This hook fires when the Datasette application server first starts up. You can i
@hookimpl
def startup(datasette):
config = datasette.plugin_config("my-plugin") or {}
assert "required-setting" in config, "my-plugin requires setting required-setting"
assert (
"required-setting" in config
), "my-plugin requires setting required-setting"
Or you can return an async function which will be awaited on startup. Use this option if you need to make any database queries:
@ -770,9 +829,12 @@ Or you can return an async function which will be awaited on startup. Use this o
async def inner():
db = datasette.get_database()
if "my_table" not in await db.table_names():
await db.execute_write("""
await db.execute_write(
"""
create table my_table (mycol text)
""")
"""
)
return inner
Potential use-cases:
@ -815,6 +877,7 @@ Ues this hook to return a dictionary of additional :ref:`canned query <canned_qu
from datasette import hookimpl
@hookimpl
def canned_queries(datasette, database):
if database == "mydb":
@ -830,15 +893,20 @@ The hook can alternatively return an awaitable function that returns a list. Her
from datasette import hookimpl
@hookimpl
def canned_queries(datasette, database):
async def inner():
db = datasette.get_database(database)
if await db.table_exists("saved_queries"):
results = await db.execute("select name, sql from saved_queries")
return {result["name"]: {
"sql": result["sql"]
} for result in results}
results = await db.execute(
"select name, sql from saved_queries"
)
return {
result["name"]: {"sql": result["sql"]}
for result in results
}
return inner
The actor parameter can be used to include the currently authenticated actor in your decision. Here's an example that returns saved queries that were saved by that actor:
@ -847,19 +915,23 @@ The actor parameter can be used to include the currently authenticated actor in
from datasette import hookimpl
@hookimpl
def canned_queries(datasette, database, actor):
async def inner():
db = datasette.get_database(database)
if actor is not None and await db.table_exists("saved_queries"):
if actor is not None and await db.table_exists(
"saved_queries"
):
results = await db.execute(
"select name, sql from saved_queries where actor_id = :id", {
"id": actor["id"]
}
"select name, sql from saved_queries where actor_id = :id",
{"id": actor["id"]},
)
return {result["name"]: {
"sql": result["sql"]
} for result in results}
return {
result["name"]: {"sql": result["sql"]}
for result in results
}
return inner
Example: `datasette-saved-queries <https://datasette.io/plugins/datasette-saved-queries>`__
@ -888,9 +960,12 @@ Here's an example that authenticates the actor based on an incoming API key:
SECRET_KEY = "this-is-a-secret"
@hookimpl
def actor_from_request(datasette, request):
authorization = request.headers.get("authorization") or ""
authorization = (
request.headers.get("authorization") or ""
)
expected = "Bearer {}".format(SECRET_KEY)
if secrets.compare_digest(authorization, expected):
@ -906,6 +981,7 @@ Instead of returning a dictionary, this function can return an awaitable functio
from datasette import hookimpl
@hookimpl
def actor_from_request(datasette, request):
async def inner():
@ -914,7 +990,8 @@ Instead of returning a dictionary, this function can return an awaitable functio
return None
# Look up ?_token=xxx in sessions table
result = await datasette.get_database().execute(
"select count(*) from sessions where token = ?", [token]
"select count(*) from sessions where token = ?",
[token],
)
if result.first()[0]:
return {"token": token}
@ -952,7 +1029,7 @@ The hook should return an instance of ``datasette.filters.FilterArguments`` whic
where_clauses=["id > :max_id"],
params={"max_id": 5},
human_descriptions=["max_id is greater than 5"],
extra_context={}
extra_context={},
)
The arguments to the ``FilterArguments`` class constructor are as follows:
@ -973,10 +1050,13 @@ This example plugin causes 0 results to be returned if ``?_nothing=1`` is added
from datasette import hookimpl
from datasette.filters import FilterArguments
@hookimpl
def filters_from_request(self, request):
if request.args.get("_nothing"):
return FilterArguments(["1 = 0"], human_descriptions=["NOTHING"])
return FilterArguments(
["1 = 0"], human_descriptions=["NOTHING"]
)
Example: `datasette-leaflet-freedraw <https://datasette.io/plugins/datasette-leaflet-freedraw>`_
@ -1006,6 +1086,7 @@ Here's an example plugin which randomly selects if a permission should be allowe
from datasette import hookimpl
import random
@hookimpl
def permission_allowed(action):
if action != "view-instance":
@ -1024,11 +1105,16 @@ Here's an example that allows users to view the ``admin_log`` table only if thei
async def inner():
if action == "execute-sql" and resource == "staff":
return False
if action == "view-table" and resource == ("staff", "admin_log"):
if action == "view-table" and resource == (
"staff",
"admin_log",
):
if not actor:
return False
user_id = actor["id"]
return await datasette.get_database("staff").execute(
return await datasette.get_database(
"staff"
).execute(
"select count(*) from admin_users where user_id = :user_id",
{"user_id": user_id},
)
@ -1059,18 +1145,21 @@ This example registers two new magic parameters: ``:_request_http_version`` retu
from uuid import uuid4
def uuid(key, request):
if key == "new":
return str(uuid4())
else:
raise KeyError
def request(key, request):
if key == "http_version":
return request.scope["http_version"]
else:
raise KeyError
@hookimpl
def register_magic_parameters(datasette):
return [
@ -1103,9 +1192,12 @@ This example returns a redirect to a ``/-/login`` page:
from datasette import hookimpl
from urllib.parse import urlencode
@hookimpl
def forbidden(request, message):
return Response.redirect("/-/login?=" + urlencode({"message": message}))
return Response.redirect(
"/-/login?=" + urlencode({"message": message})
)
The function can alternatively return an awaitable function if it needs to make any asynchronous method calls. This example renders a template:
@ -1114,10 +1206,15 @@ The function can alternatively return an awaitable function if it needs to make
from datasette import hookimpl
from datasette.utils.asgi import Response
@hookimpl
def forbidden(datasette):
async def inner():
return Response.html(await datasette.render_template("forbidden.html"))
return Response.html(
await datasette.render_template(
"forbidden.html"
)
)
return inner
@ -1147,11 +1244,17 @@ This example adds a new menu item but only if the signed in user is ``"root"``:
from datasette import hookimpl
@hookimpl
def menu_links(datasette, actor):
if actor and actor.get("id") == "root":
return [
{"href": datasette.urls.path("/-/edit-schema"), "label": "Edit schema"},
{
"href": datasette.urls.path(
"/-/edit-schema"
),
"label": "Edit schema",
},
]
Using :ref:`internals_datasette_urls` here ensures that links in the menu will take the :ref:`setting_base_url` setting into account.
@ -1188,13 +1291,20 @@ This example adds a new table action if the signed in user is ``"root"``:
from datasette import hookimpl
@hookimpl
def table_actions(datasette, actor):
if actor and actor.get("id") == "root":
return [{
"href": datasette.urls.path("/-/edit-schema/{}/{}".format(database, table)),
"label": "Edit schema for this table",
}]
return [
{
"href": datasette.urls.path(
"/-/edit-schema/{}/{}".format(
database, table
)
),
"label": "Edit schema for this table",
}
]
Example: `datasette-graphql <https://datasette.io/plugins/datasette-graphql>`_
@ -1238,6 +1348,7 @@ This example will disable CSRF protection for that specific URL path:
from datasette import hookimpl
@hookimpl
def skip_csrf(scope):
return scope["path"] == "/submit-comment"
@ -1278,7 +1389,9 @@ This hook is responsible for returning a dictionary corresponding to Datasette :
"description": get_instance_description(datasette),
"databases": [],
}
for db_name, db_data_dict in get_my_database_meta(datasette, database, table, key):
for db_name, db_data_dict in get_my_database_meta(
datasette, database, table, key
):
metadata["databases"][db_name] = db_data_dict
# whatever we return here will be merged with any other plugins using this hook and
# will be overwritten by a local metadata.yaml if one exists!