gramlot-py-server
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
- The guides on Read the Docs, sources in docs/ and the concise view
in docs_llm/:
- common: Introduction, Writing pages for these hosts, Troubleshooting;
- Uvicorn: Tutorial, Configuration, Deployment, Reference;
- Django, Flask, FastAPI, Kajenn.
- The core: The Gramlot family, Classes, repository and server adapters (the shared adapter contract: mount prefix, companions, CSP profiles), Writing pages.
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)
| File | Size | Uploaded | |
|---|---|---|---|
| gramlot_py_server-0.2.1.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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