Skip to main content

gramlot-py-server

tests Coverage Documentation 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 urls 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 FileHost: it sends the bootstrap document, answers the main and remote Source requests, serves the page companions (.css and _aux.js) 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.

Quick start

Python 3.11 or later. Install the package with the extra of your framework:

python -m pip install "gramlot-py-server[uvicorn]"

Create a folder pages and write pages/hello.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", "'Hello, ' + name", name="^.name", _init=True)
        pane.dataSetter(".name", "Ada")

The formula of this page is inline code, so every example below sends the permissive Content Security Policy profile. Each one lives in examples/, beside a folder pages; the docstring of each file gives the command that starts it. The page opens at http://127.0.0.1:8000/hello, on port 5000 for the Flask development server. FastAPI needs a server: install gramlot-py-server[fastapi,uvicorn].

Uvicorn, examples/uvicorn/app.py:

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

from gramlot_py_server.uvicorn import create_application

PAGES = Path(__file__).resolve().parents[1] / "pages"

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

Django, examples/django/settings.py and examples/django/urls.py:

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

SECRET_KEY = "example-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 of the example: the Gramlot pages at the site root."""
from pathlib import Path

from django.urls import include, path

from gramlot_py_server.django import Pages

PAGES = Path(__file__).resolve().parents[1] / "pages"

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

urlpatterns = [path("", include(pages.urls))]

Flask, examples/flask/app.py:

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

from flask import Flask

from gramlot_py_server.flask import mount_pages

PAGES = Path(__file__).resolve().parents[1] / "pages"

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

FastAPI, examples/fastapi/app.py:

"""Serve the example pages inside a FastAPI app: ``fastapi dev app.py`` or ``uvicorn app:app``."""
from pathlib import Path

from fastapi import FastAPI

from gramlot_py_server.fastapi import mount_pages

PAGES = Path(__file__).resolve().parents[1] / "pages"

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

Kajenn, examples/kajenn/config.py (the page opens at http://127.0.0.1:8000/pages/hello):

"""Kajenn site of the example: ``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().parents[1] / "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}' 'unsafe-eval'; object-src 'none'; base-uri 'none'"
            ),
        ).request(body="raw")

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 tests tests/<framework>/test_<framework>_examples.py serve these examples in CI, and tests/test_readme.py checks that this README shows the same files.

Next steps

Compatibility

Verified
Gramlot core 0.2.1 (PyPI gramlot)
Python 3.11 and 3.12 in CI; 3.12.12 locally
Frameworks Uvicorn 0.54.0, Django 6.1.1 with asgiref 3.12.1, Flask 3.1.3, FastAPI 0.142.2, Kajenn 0.1.0
Browsers not run for this package; the core runtime it serves, unchanged since 0.2.0, passed the core 0.2.0 qualification on Chromium 153, WebKit 26.6 and Firefox 155

Tests and contributing

python -m pip install -e ".[uvicorn,django,flask,fastapi,kajenn,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

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, 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.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.1
File Size Uploaded
gramlot_py_server-0.2.1.tar.gz 1.2 MB Details

Built distribution (wheel)

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

Total release size: 1.2 MB

Release files / gramlot_py_server-0.2.1.tar.gz

Download URL gramlot_py_server-0.2.1.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
ca7942af7b181ece62958bcb3d1727321ea53a714dabba6879d24fe8baf1b741
BLAKE2b-256 checksum
How to use checksums
4363b8a86b696b134cf74f143a414a76bcf0f5b2f091b2230d082a341bf9176f
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 2, 2026.

Transparency log

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

Download URL gramlot_py_server-0.2.1-py3-none-any.whl
Size 22.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
356b681c2d73c3780799287db8c1da8ce76fa935b25f43698c184355600a7b40
BLAKE2b-256 checksum
How to use checksums
74aae3c3d49ec8e80e916349149b8b9da0297a6ec5a378b6f182e2e9db12d4c6
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 2, 2026.

Transparency log

Release history Release notifications | RSS feed

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

This release

0.2.1 This release

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