Skip to content

Commit

Permalink
Use dataclasses for tracking events, refs #2240
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Jan 31, 2024
1 parent 959e020 commit f003284
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 0 deletions.
19 changes: 19 additions & 0 deletions datasette/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from jinja2.environment import Template
from jinja2.exceptions import TemplateNotFound

from .events import Event
from .views import Context
from .views.base import ureg
from .views.database import database_download, DatabaseView, TableCreateView
Expand Down Expand Up @@ -436,6 +437,13 @@ def __init__(
self._root_token = secrets.token_hex(32)
self.client = DatasetteClient(self)

# Register all event classes
event_classes = []
for hook in pm.hook.register_events(datasette=self):
if hook:
event_classes.extend(hook)
self.event_classes = tuple(event_classes)

def get_jinja_environment(self, request: Request = None) -> Environment:
environment = self._jinja_env
if request:
Expand Down Expand Up @@ -873,6 +881,17 @@ async def actors_from_ids(
result = await await_me_maybe(result)
return result

async def track_event(self, event: Event):
assert isinstance(event, self.event_classes), "Invalid event type: {}".format(
type(event)
)
properties = dataclasses.asdict(event)
actor = properties.pop("actor")
for hook in pm.hook.track_event(
datasette=self, name=event.name, actor=actor, properties=properties
):
await await_me_maybe(hook)

async def permission_allowed(
self, actor, action, resource=None, default=DEFAULT_NOT_SET
):
Expand Down
30 changes: 30 additions & 0 deletions datasette/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from abc import ABC, abstractproperty
from dataclasses import dataclass
from datasette.hookspecs import hookimpl


@dataclass
class Event(ABC):
@abstractproperty
def name(self):
pass

actor: dict


@dataclass
class LogoutEvent(Event):
name = "logout"


@dataclass
class CreateTableEvent(Event):
name = "create-table"
database: str
table: str
schema: str


@hookimpl
def register_events():
return [LogoutEvent, CreateTableEvent]
10 changes: 10 additions & 0 deletions datasette/hookspecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ def handle_exception(datasette, request, exception):
"""Handle an uncaught exception. Can return a Response or None."""


@hookspec
def track_event(datasette, name, actor, properties):
"""Respond to a named event tracked by Datasette"""


@hookspec
def register_events(datasette):
"""Return a list of Event subclasses to use with track_event()"""


@hookspec
def top_homepage(datasette, request):
"""HTML to include at the top of the homepage"""
Expand Down
1 change: 1 addition & 0 deletions datasette/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"datasette.default_menu_links",
"datasette.handle_exception",
"datasette.forbidden",
"datasette.events",
)

pm = pluggy.PluginManager("datasette")
Expand Down
6 changes: 6 additions & 0 deletions datasette/views/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import sqlite_utils
import textwrap

from datasette.events import CreateTableEvent
from datasette.database import QueryInterrupted
from datasette.utils import (
add_cors_headers,
Expand Down Expand Up @@ -969,6 +970,11 @@ def create_table(conn):
}
if rows:
details["row_count"] = len(rows)
await self.ds.track_event(
CreateTableEvent(
request.actor, database=db.name, table=table_name, schema=schema
)
)
return Response.json(details, status=201)


Expand Down
2 changes: 2 additions & 0 deletions datasette/views/special.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from datasette.events import LogoutEvent
from datasette.utils.asgi import Response, Forbidden
from datasette.utils import (
actor_matches_allow,
Expand Down Expand Up @@ -105,6 +106,7 @@ async def post(self, request):
response = Response.redirect(self.ds.urls.instance())
response.set_cookie("ds_actor", "", expires=0, max_age=0)
self.ds.add_message(request, "You are now logged out", self.ds.WARNING)
await self.ds.track_event(LogoutEvent(request.actor))
return response


Expand Down

0 comments on commit f003284

Please sign in to comment.