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.1a10.tar.gz (150.0 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.1a10-py3-none-any.whl (161.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: datasette_alerts-0.0.1a10.tar.gz
  • Upload date:
  • Size: 150.0 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.1a10.tar.gz
Algorithm Hash digest
SHA256 b5122358ee8c00ca72fd4db1f6817563e438278f92fd02c353c4f116f97870b4
MD5 4551bfc6e552837c52ac8e4298bb5d1f
BLAKE2b-256 2ba168617bf36175f627eadf48f44f19395b7fbec496fcdf0708f45631ad2698

See more details on using hashes here.

Provenance

The following attestation bundles were made for datasette_alerts-0.0.1a10.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.1a10-py3-none-any.whl.

File metadata

File hashes

Hashes for datasette_alerts-0.0.1a10-py3-none-any.whl
Algorithm Hash digest
SHA256 6a60540646c263afd57499af0d5900fed286f2238da3df2a464404393feb3b37
MD5 4bd7d52827369c098948ce9fcdebf011
BLAKE2b-256 2f505bc5d55edbb7fb46f394394e13e849ebff223a3edde7b601b8b92bf8c01d

See more details on using hashes here.

Provenance

The following attestation bundles were made for datasette_alerts-0.0.1a10-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