Skip to main content

Setup alerts on specfic new records.

Project description

datasette-alerts

PyPI Changelog Tests License

Alerts and notifications for Datasette. Supports row-based alerts (new rows in a table) and custom alert types defined by plugins.

How It Works

datasette-alerts uses datasette-cron for scheduling. When an alert is created, a cron task is registered that periodically checks for new data and sends notifications through configured destinations.

Built-in alert types:

  • Cursor alerts — poll a table for rows newer than a timestamp cursor
  • Trigger alerts — SQLite INSERT trigger queues new rows for processing

Custom alert types — plugins can register their own alert logic via the datasette_alerts_register_alert_types hook.

Notifier Plugins

Plugin Description
datasette-alerts-slack Send Slack messages
datasette-alerts-discord Send Discord messages
datasette-alerts-ntfy Send ntfy.sh notifications
datasette-alerts-desktop Local desktop notifications

Writing a Notifier Plugin

Notifier plugins implement the datasette_alerts_register_notifiers hook and return subclasses of Notifier.

from datasette import hookimpl
from datasette_alerts import Notifier, Message
from wtforms import Form, StringField


@hookimpl
def datasette_alerts_register_notifiers(datasette):
    return [MyNotifier()]


class MyNotifier(Notifier):
    slug = "my-notifier"
    name = "My Notifier"
    description = "Send alerts somewhere"

    async def get_config_form(self):
        class ConfigForm(Form):
            webhook_url = StringField("Webhook URL")
        return ConfigForm

    async def send(self, config: dict, message: Message):
        url = config["webhook_url"]
        # deliver message.text (and optionally message.subject)

Notifier API

Notifier (abstract base class)

Property / Method Description
slug Unique identifier (required)
name Display name (required)
description Short description
icon SVG string for the UI
get_config_form() Return a WTForms Form class for destination config
get_config_element() Return a ConfigElement for web component config UI
send(config, message) Deliver a Message to the destination described by config

Message

Message(text: str, *, subject: str | None = None)

Writing a Custom Alert Type

Custom alert types let plugins define their own checking logic. datasette-alerts handles the scheduling, notification delivery, and logging.

Minimal Example

from datasette import hookimpl
from datasette_alerts import AlertType, Message


class MyAlertType(AlertType):
    slug = "my-check"
    name = "My Custom Check"
    description = "Checks something and alerts when it finds results"

    async def check(self, datasette, alert_config, database_name, last_check_at):
        db = datasette.get_database(database_name)
        # Your custom logic here — query tables, check conditions, etc.
        result = await db.execute("SELECT * FROM my_queue WHERE status = 'pending'")
        
        messages = []
        ids = []
        for row in result.rows:
            ids.append(row[0])
            messages.append(Message(f"New item: {row[1]}"))
        
        # Mark as processed
        if ids:
            placeholders = ",".join("?" for _ in ids)
            await db.execute_write(
                f"UPDATE my_queue SET status = 'done' WHERE id IN ({placeholders})",
                ids,
            )
        
        return messages


@hookimpl
def datasette_alerts_register_alert_types(datasette):
    return [MyAlertType()]

AlertType API

AlertType (abstract base class)

Property / Method Description
slug Unique identifier (required)
name Display name (required)
description Short description
icon SVG string for the UI
check(datasette, alert_config, database_name, last_check_at) Run check logic, return list[Message]
get_config_element() Optional web component for alert configuration UI
get_config_form() Optional WTForms form for configuration

check() Method

async def check(
    self,
    datasette,           # Datasette instance
    alert_config: dict,  # from the alert's custom_config column
    database_name: str,  # which database this alert is scoped to
    last_check_at: str | None,  # ISO timestamp of last check, or None on first run
) -> list[Message]:

Return a list of Message objects to send. Empty list means nothing to report. The handler calls check() on the schedule defined by the alert's frequency, sends any returned messages through the alert's subscriptions, and updates last_check_at.

How Custom Alerts Get Scheduled

When a custom alert is created (via the API or a plugin's own routes):

  1. A row is inserted into datasette_alerts_alerts with alert_type="custom:{slug}"
  2. A cron task is registered with datasette-cron at the alert's frequency
  3. Each tick, the custom_alert_handler looks up the AlertType by slug via the plugin hook
  4. Calls check() with the alert's config and database
  5. Sends any returned messages to all subscriptions
  6. Updates last_check_at and logs the check

Creating Custom Alerts Programmatically

from datasette_alerts.internal_db import InternalDB, NewAlertRouteParameters, NewSubscription

internal_db = InternalDB(datasette.get_internal_database())

params = NewAlertRouteParameters(
    database_name="mydb",
    alert_type="custom:my-check",
    frequency="+1 second",
    custom_config={"key": "value"},
    subscriptions=[
        NewSubscription(destination_id="dest-id", meta={"aggregate": True})
    ],
)
alert_id = await internal_db.new_alert(params)

# Register the cron task
from datasette_alerts import _register_cron_task_for_alert
from types import SimpleNamespace

await _register_cron_task_for_alert(datasette, SimpleNamespace(
    id=alert_id,
    alert_type="custom:my-check",
    frequency="+1 second",
))

Public API

send_to_destination()

Send a message through a configured destination without creating an alert:

from datasette_alerts import send_to_destination, Message

await send_to_destination(datasette, destination_id, Message("Hello!"))

Raises DestinationNotFound or NotifierNotFound on errors.

trigger_alert_check()

Trigger an immediate check for an alert, outside its normal schedule:

from datasette_alerts import trigger_alert_check

await trigger_alert_check(datasette, alert_id)

API Endpoint: List Alert Types

GET /-/{database}/datasette-alerts/api/alert-types

Returns registered custom alert types with their slug, name, description, and config element info.

Data Models

Query results from InternalDB return typed dataclasses:

from datasette_alerts.models import AlertRecord, AlertForCheck, AlertDetail

AlertRecord

Returned by get_all_alerts(). Fields: id, database_name, table_name, id_columns, timestamp_column, frequency, alert_type, custom_config, last_check_at.

AlertForCheck

Returned by get_alert_for_check(). Same as AlertRecord plus cursor.

AlertDetail

Returned by get_alert_detail(). Full alert info including nested subscriptions: list[SubscriptionDetail] and logs: list[AlertLogEntry], plus custom_config, last_check_at, next_deadline, alert_created_at.

AlertCleanupInfo

Returned by delete_alert(). Fields: alert_type, database_name, table_name.

Config: WTForms vs Web Components

There are two ways to provide a destination (or alert type) configuration UI:

WTForms (simple)

Override get_config_form() and return a WTForms Form class.

ConfigElement (rich UI)

Return a ConfigElement that declares a custom HTML element:

from datasette_alerts import ConfigElement

def get_config_element(self):
    return ConfigElement(
        tag="my-config-form",
        scripts=["/-/my-plugin/config.js"],
    )

Web component contract:

  • Inputs: config property, datasette-base-url attribute, database-name attribute
  • Output: config-change CustomEvent with detail: { config: {...}, valid: boolean }

Sidebar Integration

If datasette-sidebar is installed, datasette-alerts automatically registers itself as a sidebar app.

Development

just dev           # start dev server
just test          # run tests (uv run pytest)
just format        # format code (backend + frontend)
just check         # lint + type check (backend + frontend)
just frontend      # build frontend
just frontend-dev  # start Vite dev server

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

datasette_alerts-0.0.1a8.tar.gz (150.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

datasette_alerts-0.0.1a8-py3-none-any.whl (161.5 kB view details)

Uploaded Python 3

File details

Details for the file datasette_alerts-0.0.1a8.tar.gz.

File metadata

  • Download URL: datasette_alerts-0.0.1a8.tar.gz
  • Upload date:
  • Size: 150.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for datasette_alerts-0.0.1a8.tar.gz
Algorithm Hash digest
SHA256 4e5770308c36710004b797e383247b9accba607f543547e506008927e28c20b1
MD5 23f3ccabdf46686b9202cc8105c28d77
BLAKE2b-256 7d6eb602020c0b76d6bef54ff969be903377a1bf7344a10f63628f326809c361

See more details on using hashes here.

Provenance

The following attestation bundles were made for datasette_alerts-0.0.1a8.tar.gz:

Publisher: publish.yml on datasette/datasette-alerts

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file datasette_alerts-0.0.1a8-py3-none-any.whl.

File metadata

File hashes

Hashes for datasette_alerts-0.0.1a8-py3-none-any.whl
Algorithm Hash digest
SHA256 3c7a5b1d2773e8564117c7e732867c49802431675ae19c3f7f571bca360faadc
MD5 3329f6ca58b7f98421de860d933eb838
BLAKE2b-256 42a7efd24e6cb3ad35859923eb81390d3946f4dedd8cf14afde5a546308b6fc2

See more details on using hashes here.

Provenance

The following attestation bundles were made for datasette_alerts-0.0.1a8-py3-none-any.whl:

Publisher: publish.yml on datasette/datasette-alerts

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page