Skip to main content

gramlot-py-server

For an existing gallery, follow the update procedure: update the active environment, restart and verify. Source checkouts require a separate browser build.

tests Coverage Documentation PyPI License: Apache 2.0

Gramlot describes web interfaces in Python or JavaScript and keeps them bound to application state in the browser. See The Gramlot family for the core and the other repositories.

What this repository is

gramlot-py-server serves Gramlot pages written in Python from a Python web server. One package holds one adapter per framework; each comes with its extra:

Extra Module Names
uvicorn gramlot_py_server.uvicorn Application, create_application(pages, **options): an ASGI application for Uvicorn or any ASGI server
django gramlot_py_server.django Pages: its urlpatterns go into a URLconf
flask gramlot_py_server.flask Pages, mount_pages(app, pages, **options)
fastapi gramlot_py_server.fastapi Pages, mount_pages(app, pages, **options), Application
kajenn gramlot_py_server.kajenn Application: a Kajenn routed application

Every adapter serves one folder of Page modules through the core GramlotFileServer: it sends the bootstrap document, passes the envelopes of POST /gramlot/rpc to GramlotServer.call (the page main, fragments and @endpoint methods), serves the page companions (.css and .js), an optional map of assets and the browser runtime, with a mount prefix and a Content Security Policy of your choice. It does not serve JavaScript pages (gramlot-js-server does) and it does not talk to a database.

Declare Source fragments with @source and mount them with remote; use dataRpc for endpoint values and dataRemote for lazy Data. See the shared authoring guide.

The Kajenn adapter announces the auth capability: it evaluates an auth rule with genro_toolbox.tags_match against the tags of the request's Kajenn avatar, as the genro-routes AuthPlugin does for route entries. Registering the endpoints as genro-routes RouterNodes with the other plugins belongs to genro-kajenn. The other adapters announce no capability: an endpoint with an auth rule answers not_authenticated.

The current release is 0.2.8.1, on PyPI as gramlot-py-server.

Quick start

Python 3.11 or later. Install the package with the extra of your framework, then create a project with gramlot <framework> new <folder>:

python -m pip install "gramlot-py-server[uvicorn]"
gramlot uvicorn new hello
cd hello
uvicorn app:application

Open http://127.0.0.1:8000/. The page shows a field with Ada and the text Hello, Ada. Typing Grace in the field changes the text to Hello, Grace at every keystroke.

The command writes the same page for every framework, pages/index.py:

from gramlot import Page as BasePage


class Page(BasePage):
    title = "Hello"

    def main(self, root):
        pane = root.div(datapath="person")
        pane.html_label("Name", for_="name")
        pane.input(id="name", value="^.name", live=True)
        pane.p("^.greeting")
        pane.dataFormula(".greeting", func="greeting", name="^.name", _init=True)
        pane.dataSetter(".name", "Ada")

and its page module pages/index.js. The formula names its method with func="greeting"; the method is in the Logic export and runs in the browser:

export class Logic {
    greeting(kwargs) { return 'Hello, ' + kwargs.name; }
}

The page has no inline code, so every project sends the strict Content Security Policy profile. Beside pages/ the command writes requirements.txt, with the extra of the framework, and the files of the framework below. The docstring of each file gives the command that starts it, which gramlot also prints.

Uvicorn, gramlot uvicorn new, app.py:

"""Serve the pages with Uvicorn: ``uvicorn app:application``."""
from pathlib import Path

from gramlot_py_server.uvicorn import create_application

PAGES = Path(__file__).resolve().parent / "pages"

application = create_application(
    PAGES,
    content_security_policy="script-src 'nonce-{nonce}'; object-src 'none'; base-uri 'none'",
)

Django, gramlot django new, settings.py and urls.py:

"""Django settings: ``django-admin runserver --settings=settings --pythonpath=.``."""

SECRET_KEY = "development-only-change-me"
DEBUG = True
ALLOWED_HOSTS = ["127.0.0.1", "localhost", "testserver"]
ROOT_URLCONF = "urls"
INSTALLED_APPS: list[str] = []
MIDDLEWARE = ["django.middleware.csrf.CsrfViewMiddleware"]
"""URLconf: the Gramlot pages at the site root."""
from pathlib import Path

from gramlot_py_server.django import Pages

PAGES = Path(__file__).resolve().parent / "pages"

pages = Pages(
    PAGES,
    content_security_policy="script-src 'nonce-{nonce}'; object-src 'none'; base-uri 'none'",
)

urlpatterns = [*pages.urlpatterns]

Flask, gramlot flask new, app.py:

"""Serve the pages inside a Flask app: ``flask --app app run --port 8000``."""
from pathlib import Path

from flask import Flask

from gramlot_py_server.flask import mount_pages

PAGES = Path(__file__).resolve().parent / "pages"

app = Flask(__name__)
mount_pages(
    app,
    PAGES,
    content_security_policy="script-src 'nonce-{nonce}'; object-src 'none'; base-uri 'none'",
)

FastAPI, gramlot fastapi new, app.py; requirements.txt installs FastAPI with Uvicorn:

"""Serve the pages inside a FastAPI app: ``uvicorn app:app``."""
from pathlib import Path

from fastapi import FastAPI

from gramlot_py_server.fastapi import mount_pages

PAGES = Path(__file__).resolve().parent / "pages"

app = FastAPI()
mount_pages(
    app,
    PAGES,
    content_security_policy="script-src 'nonce-{nonce}'; object-src 'none'; base-uri 'none'",
)

Kajenn, gramlot kajenn new, config.py; the page opens at http://127.0.0.1:8000/pages/:

"""Kajenn site: ``kajenn serve config.py --port 8000``, pages under ``/pages/``."""
from pathlib import Path

from kajenn.config.templates import DefaultConfiguration

from gramlot_py_server.kajenn import Application

PAGES = Path(__file__).resolve().parent / "pages"


class Site(DefaultConfiguration):
    def applications_section(self, cfg):
        cfg.applications().application(
            code="pages",
            mount="pages",
            app_class=Application,
            pages=PAGES,
            content_security_policy=(
                "script-src 'nonce-{nonce}'; object-src 'none'; base-uri 'none'"
            ),
        ).request(body="raw")

The tests tests/<framework>/test_<framework>_project.py create each project with gramlot <framework> new and serve it, and tests/test_readme.py checks that this README shows the same files. A project that already exists, such as a Django site, needs no new: the guide of its framework shows how to add the pages to it.

Next steps

Compatibility

Verified
Gramlot core 0.2.17 (PyPI gramlot)
Gallery gramlot-examples 0.2.12 (extra gallery)
Python 3.11 and 3.12 in CI; 3.11.11 and 3.12.9 locally
Frameworks Uvicorn 0.54.0, Django 6.1.2 with asgiref 3.12.1, Flask 3.1.3, FastAPI 0.143.1, Kajenn 0.5.0
Browsers Chromium 153 in CI; Chromium 153 and WebKit 26.6 locally, with Playwright 1.63.0

Tests and contributing

python -m pip install -e ".[uvicorn,django,flask,fastapi,kajenn,gallery,test]"
python -m pytest -q                      # every adapter
python -m pytest -q tests/flask          # one adapter, with its extra only
python scripts/check_docs.py             # paired guides and Sphinx build

The browser checks need Node.js and Playwright; PLAYWRIGHT_ENTRY is the path of playwright/index.mjs:

node scripts/verify_browser.mjs "$(command -v python)" "$PLAYWRIGHT_ENTRY" chromium
node scripts/verify_gallery_browser.mjs "$(command -v python)" "$PLAYWRIGHT_ENTRY" chromium
python scripts/verify_install.py flask dist/gramlot_py_server-*.whl "$PLAYWRIGHT_ENTRY"

CI runs each adapter with its own extra against the released core and, as an informational job, every adapter against the core's main checkout; it runs the browser checks in Chromium and a clean install of each environment from the built wheel, and builds the documentation; coverage goes to Codecov with one flag per framework. See CONTRIBUTING.md and AGENTS.md; the internal notes are in docs/internal/.

Apache License 2.0. Copyright 2026 Softwell S.r.l. See LICENSE and NOTICE.

Metadata

Release files for gramlot-py-server 0.2.8.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gramlot-py-server 0.2.8.1
File Size Uploaded
gramlot_py_server-0.2.8.1.tar.gz 105.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gramlot-py-server 0.2.8.1
File Interpreter ABI Platform
gramlot_py_server-0.2.8.1-py3-none-any.whl Python 3 none any Details

Total release size: 145.9 kB

Release files / gramlot_py_server-0.2.8.1.tar.gz

Download URL gramlot_py_server-0.2.8.1.tar.gz
Size 105.3 kB
Tags Source
SHA-256 checksum
How to use checksums
ccc398add703a6053aa79fbc7d3af9affff11459f5b5cfd63bff90f0d5662da0
BLAKE2b-256 checksum
How to use checksums
5fbe83535190cec9dae7a46cf0550a967e2cac76ab9d338a9dc8f9119f33bce1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / gramlot_py_server-0.2.8.1-py3-none-any.whl

Download URL gramlot_py_server-0.2.8.1-py3-none-any.whl
Size 40.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ecf962b0e5c69a6e211adf60a81ea2102a2cc26ae9d663c814a383b064fe6b02
BLAKE2b-256 checksum
How to use checksums
d23b94a56f01f27fd75d7d7ce5fb904adf932ca2fbcd987f8f4a212a9aa6fad2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.8.1 This release

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page