An admin framework over FastAPI

Panel, plugins and replicas — from one settings object

You give it settings; keepup gives you an application with a panel, a plugin runtime, users and roles, a database layer, an audit, metrics and coordination between replicas already wired.

  • version 0.5.0
  • Python 3.11+
  • Apache-2.0
  • PostgreSQL · SQLite
pip install keepup-admin
pip install "keepup-admin[panel,postgres]"

Installed as keepup-admin, imported as keepup. The base is the constructor: the kernel, the plugin catalogue, the service registry, the lifecycle and the transport seam, with no library of anybody else's, not even FastAPI. Capabilities arrive as extras; the full panel is keepup-admin[panel,postgres].

main.py
import os

from keepup import KeepupSettings, create_app

PLUGINS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "plugins")

app = create_app(KeepupSettings(
    title="Your product",
    plugins_dir=PLUGINS,
    static_mounts=(),  # The application has no static files of its own: the panel shell ships with keepup-http.
))
$ SECRET_KEY=… uvicorn main:app → the panel at /selfcare

What is inside

What gets written again every time is already written

Eight parts usually assembled around FastAPI by hand — as a set of distributions, with one place to configure them. The base pulls in no library of anybody else's, not even FastAPI: every capability declares its own. The full panel is pip install "keepup-admin[panel,postgres]".

appAssembly

create_app(settings) is a lazy wrapper: FastAPI, middleware, lifespan and mounts arrive with keepup-http.

extPlugins

Features arrive as plugins; routes are data, not decorators.

uiPanel

The /selfcare shell and the sections are keepup-http, the themes keepup-ui, the section catalogue and its grants keepup-modules; eight sections out of the box.

idUsers

Local accounts, roles, panel sessions, sign-in through an external OIDC provider.

dbDatabase

keepup-db: SQLAlchemy with a pool; a table is declared once for PostgreSQL and SQLite, no migrations.

n×Replicas

keepup-cluster: distributed locks, a message bus between processes, a cluster registry.

⏱Background jobs

keepup-tasks: one scheduler per process; its jobs are visible in the panel and can be run from there.

obsObservability

keepup-audit, keepup-metrics, keepup-log-shipping: an audit of incoming requests, an event log, Prometheus metrics, log shipping.

How it is shaped

The framework carries no product

Anything it cannot know arrives in KeepupSettings: your name and version, where your logs are shipped, which fields of a request are secret, what a password must look like, which pages are closed, which origins may read your answers.

What is public is declared: the names in __all__ are the contract, and a test holds the package to it. Everything else is internal and may change without notice.

Your applicationmain.py · KeepupSettings
Your pluginsplugins/*.py · config/modules.json
keepupkernel · settings · roles · locks · bus · cache
keepup-db · keepup-auth · keepup-httpFastAPI · Starlette · SQLAlchemy · APScheduler · PyJWT
keepup-postgreskeepup-sqlite — for development
plugins/hello.py
from keepup.plugins.base import BasePlugin

class HelloPlugin(BasePlugin):
    def __init__(self, config=None):
        super().__init__("hello", "Hello", config or {})

    async def initialize(self) -> bool:
        return True

    def get_handlers(self):
        return {}

    def get_api_routes(self):
        return [{"path": "/api/hello", "methods": ["GET"],
                 "handler": self.hello}]

    async def hello(self, current_user: dict, **kwargs):
        return {"hello": current_user["username"]}

Closed by default

Where a default would have to guess, it closes

  • Without SECRET_KEY the application does not start — there is no signing key in the source.
  • The first administrator's password is set by whoever deploys; otherwise it is random and printed to the log once.
  • CORS is closed until the application names origins; /metrics needs credentials.
  • The audit keeps field names, not values.
  • An unknown route parameter is answered with 400, not silently ignored.
  • Sign-in through a provider refuses an account the application has never seen, until it decides otherwise.

Next

Where to go