actors_from_ids plugin hook and datasette.actors_from_ids() method (#2181)

* Prototype of actors_from_ids plugin hook, refs #2180
* datasette-remote-actors example plugin, refs #2180
pull/2182/head
Simon Willison 2023-09-07 21:23:59 -07:00 zatwierdzone przez GitHub
rodzic c26370485a
commit b645174271
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 155 dodań i 0 usunięć

Wyświetl plik

@ -819,6 +819,16 @@ class Datasette:
)
return crumbs
async def actors_from_ids(
self, actor_ids: Iterable[Union[str, int]]
) -> Dict[Union[id, str], Dict]:
result = pm.hook.actors_from_ids(datasette=self, actor_ids=actor_ids)
if result is None:
# Do the default thing
return {actor_id: {"id": actor_id} for actor_id in actor_ids}
result = await await_me_maybe(result)
return result
async def permission_allowed(
self, actor, action, resource=None, default=DEFAULT_NOT_SET
):

Wyświetl plik

@ -94,6 +94,11 @@ def actor_from_request(datasette, request):
"""Return an actor dictionary based on the incoming request"""
@hookspec(firstresult=True)
def actors_from_ids(datasette, actor_ids):
"""Returns a dictionary mapping those IDs to actor dictionaries"""
@hookspec
def filters_from_request(request, database, table, datasette):
"""

Wyświetl plik

@ -322,6 +322,27 @@ await .render_template(template, context=None, request=None)
Renders a `Jinja template <https://jinja.palletsprojects.com/en/2.11.x/>`__ using Datasette's preconfigured instance of Jinja and returns the resulting string. The template will have access to Datasette's default template functions and any functions that have been made available by other plugins.
.. _datasette_actors_from_ids:
await .actors_from_ids(actor_ids)
---------------------------------
``actor_ids`` - list of strings or integers
A list of actor IDs to look up.
Returns a dictionary, where the keys are the IDs passed to it and the values are the corresponding actor dictionaries.
This method is mainly designed to be used with plugins. See the :ref:`plugin_hook_actors_from_ids` documentation for details.
If no plugins that implement that hook are installed, the default return value looks like this:
.. code-block:: json
{
"1": {"id": "1"},
"2": {"id": "2"}
}
.. _datasette_permission_allowed:
await .permission_allowed(actor, action, resource=None, default=...)

Wyświetl plik

@ -1071,6 +1071,63 @@ Instead of returning a dictionary, this function can return an awaitable functio
Examples: `datasette-auth-tokens <https://datasette.io/plugins/datasette-auth-tokens>`_, `datasette-auth-passwords <https://datasette.io/plugins/datasette-auth-passwords>`_
.. _plugin_hook_actors_from_ids:
actors_from_ids(datasette, actor_ids)
-------------------------------------
``datasette`` - :ref:`internals_datasette`
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``, or to execute SQL queries.
``actor_ids`` - list of strings or integers
The actor IDs to look up.
The hook must return a dictionary that maps the incoming actor IDs to their full dictionary representation.
Some plugins that implement social features may store the ID of the :ref:`actor <authentication_actor>` that performed an action - added a comment, bookmarked a table or similar - and then need a way to resolve those IDs into display-friendly actor dictionaries later on.
Unlike other plugin hooks, this only uses the first implementation of the hook to return a result. You can expect users to only have a single plugin installed that implements this hook.
If no plugin is installed, Datasette defaults to returning actors that are just ``{"id": actor_id}``.
The hook can return a dictionary or an awaitable function that then returns a dictionary.
This example implementation returns actors from a database table:
.. code-block:: python
from datasette import hookimpl
@hookimpl
def actors_from_ids(datasette, actor_ids):
db = datasette.get_database("actors")
async def inner():
sql = "select id, name from actors where id in ({})".format(
", ".join("?" for _ in actor_ids)
)
actors = {}
for row in (await db.execute(sql, actor_ids)).rows:
actor = dict(row)
actors[actor["id"]] = actor
return actors
return inner
The returned dictionary from this example looks like this:
.. code-block:: json
{
"1": {"id": "1", "name": "Tony"},
"2": {"id": "2", "name": "Tina"},
}
These IDs could be integers or strings, depending on how the actors used by the Datasette instance are configured.
Example: `datasette-remote-actors <https://github.com/datasette/datasette-remote-actors>`_
.. _plugin_hook_filters_from_request:
filters_from_request(request, database, table, datasette)

Wyświetl plik

@ -1215,3 +1215,65 @@ async def test_hook_register_permissions_allows_identical_duplicates():
await ds.invoke_startup()
# Check that ds.permissions has only one of each
assert len([p for p in ds.permissions.values() if p.abbr == "abbr1"]) == 1
@pytest.mark.asyncio
async def test_hook_actors_from_ids():
# Without the hook should return default {"id": id} list
ds = Datasette()
await ds.invoke_startup()
db = ds.add_memory_database("actors_from_ids")
await db.execute_write(
"create table actors (id text primary key, name text, age int)"
)
await db.execute_write(
"insert into actors (id, name, age) values ('3', 'Cate Blanchett', 52)"
)
await db.execute_write(
"insert into actors (id, name, age) values ('5', 'Rooney Mara', 36)"
)
await db.execute_write(
"insert into actors (id, name, age) values ('7', 'Sarah Paulson', 46)"
)
await db.execute_write(
"insert into actors (id, name, age) values ('9', 'Helena Bonham Carter', 55)"
)
table_names = await db.table_names()
assert table_names == ["actors"]
actors1 = await ds.actors_from_ids(["3", "5", "7"])
assert actors1 == {
"3": {"id": "3"},
"5": {"id": "5"},
"7": {"id": "7"},
}
class ActorsFromIdsPlugin:
__name__ = "ActorsFromIdsPlugin"
@hookimpl
def actors_from_ids(self, datasette, actor_ids):
db = datasette.get_database("actors_from_ids")
async def inner():
sql = "select id, name from actors where id in ({})".format(
", ".join("?" for _ in actor_ids)
)
actors = {}
result = await db.execute(sql, actor_ids)
for row in result.rows:
actor = dict(row)
actors[actor["id"]] = actor
return actors
return inner
try:
pm.register(ActorsFromIdsPlugin(), name="ActorsFromIdsPlugin")
actors2 = await ds.actors_from_ids(["3", "5", "7"])
assert actors2 == {
"3": {"id": "3", "name": "Cate Blanchett"},
"5": {"id": "5", "name": "Rooney Mara"},
"7": {"id": "7", "name": "Sarah Paulson"},
}
finally:
pm.unregister(name="ReturnNothingPlugin")