Skip to main content

Drafter

A student-friendly, full-stack web development library for Python, following good software engineering principles.

Drafter lets you build interactive websites out of plain Python functions and dataclasses. You write route functions that take the current application state and return pages; Drafter handles rendering, forms, navigation, history, and debugging. The same program can be run as a local development server or compiled to a fully static site (running Python in the browser via Pyodide or Skulpt) that can be hosted anywhere, such as GitHub Pages.

Note: This branch (v2-pyodide) is the Drafter v2 rewrite, currently in beta. See ARCHITECTURE.md for a deep dive into the system design.

Quick start

Install from PyPI:

pip install drafter

Create a file my_site.py:

from drafter import *
from dataclasses import dataclass

@dataclass
class State:
    message: str

@route
def index(state: State) -> Page:
    return Page(state, [
        "Hello, world!",
        state.message,
        Button("Say goodbye", goodbye),
    ])

@route
def goodbye(state: State) -> Page:
    return Page(state, ["Goodbye!"])

start_server(State("Welcome to my site."))

Then run it any of these equivalent ways:

python my_site.py
drafter my_site.py
python -m drafter my_site.py

A local development server starts (by default at http://localhost:8000), your browser opens automatically, and the page hot-reloads whenever you save the file.

How it works

When you run a Drafter program locally, your code actually executes twice:

  1. Host side: Python runs your script normally. When it reaches start_server(...), Drafter starts a local Starlette/uvicorn development server that serves a single page embedding your code, plus a WebSocket connection for hot reload.
  2. Client side: The served page boots a Python-in-the-browser engine (Pyodide by default, or Skulpt) and re-runs your code there. That in-browser run is the one users actually interact with: routes execute, state updates, and the DOM is updated, all inside the browser.

Because everything runs client-side, drafter my_site.py --compile can produce a static build of the same app with no server component at all. This can be deployed on GitHub Pages, Netlify, or any static host.

Running and building

Serve (default mode)

drafter my_site.py [options]

Starts the local development server with file watching, hot reload, and the debug panel enabled.

Compile a static site

drafter my_site.py --compile [options]

Builds a static site (default output: dist/index.html) that boots the browser engine and runs your program. Deploy the output directory to any static host.

Command-line reference

All flags below work with the drafter command (and python -m drafter). Flags are grouped the same way Drafter's configuration system groups them. Run drafter --help for the live list.

General

Flag Description
path (positional) Path to your main entry file (e.g., my_site.py).
--compile Compile the site to static files instead of starting the server.
--config-file PATH Path to a JSON configuration file (repeatable for multiple files). See Configuration files.
--verbose Enable verbose output (prints the final resolved configuration at startup).

App behavior (server and compile modes)

Flag Default Description
--engine {skulpt,pyodide} pyodide Which Python-in-the-browser engine to use.
--prerender-initial-page on Prerender the initial page on startup (useful for SEO and faster first paint).
--asset-directory PATH inferred Directory containing Drafter's static assets. If unspecified, uses the package's bundled assets/ folder, falling back to js/dist/ in a development checkout.
--show-filename-as NAME actual name Display name for the main file in the UI, if different from the real filename.
--mount-drafter-locally off Mount the local Drafter source instead of the installed package (local development).
--override-asset-url URL off Custom base URL for assets. When building, also names the output assets folder.
--site-title TITLE Drafter App Server Browser tab title.
--load-packages-automatically on Automatically load Python packages detected in the student's code, in addition to --system-packages / --project-packages.
--system-packages LIST bakery;pillow Semicolon-separated system packages to load in the browser engine (matplotlib is installed on demand when imported or when MatPlotLibPlot is used).
--project-packages LIST (none) Semicolon-separated project-specific packages to load.
--pyodide-url URL jsDelivr CDN (v0.29.0/full) Custom URL to load Pyodide from.
--pyodide-drafter-path PATH (none) Custom path/URL to the Drafter Pyodide package wheel.

Site rendering and debugging

Flag Default Description
--production off Production mode: disables the debug panel and debug mode.
--subtle-debug-entry off In production, show a subtle control that re-enables the debug panel.
--audit-logging on Enable audit logging of requests/responses.
--no-frame framed Remove the browser-window-style frame around the app.
--theme NAME default Site theme. Bundled themes: default, dark, none.
--server-name NAME MAIN_SERVER Internal server identifier.
--deploy-image-path PATH (empty) Path prefix for images on the deployed site.
--external-pages LIST (none) Semicolon-separated external links (URL or URL Text) shown in the generated site (e.g., a GitHub repo link).
--additional-header-content LIST (none) Semicolon-separated raw HTML strings injected into <head>.
--additional-style-content LIST (none) Semicolon-separated inline CSS strings.
--additional-css-content LIST (none) Semicolon-separated external CSS URLs.
--additional-js-content LIST (none) Semicolon-separated inline JavaScript strings.
--additional-script-content LIST (none) Semicolon-separated external JS URLs.
--use-shadow-dom off Wrap the app in a Shadow DOM to isolate it from page CSS.
--root-element-id ID drafter-root-- ID prefix for Drafter's root element.
--newlines-to-br on Convert newlines in text content to <br> tags.

Development server (serve mode only)

Flag Default Description
--port PORT 8000 Server port.
--host HOST localhost Server host address.
--no-reloader reloader on Disable the file watcher / auto-reload.
--no-open-browser opens Don't automatically open a web browser on start.
--no-inline-py inlined Load user code via an HTTP request instead of inlining it into the HTML.
--no-serve-adjacent-files served Don't serve files from the directory next to the user's script.

Static builder (--compile mode only)

Flag Default Description
--output-directory DIR dist Directory to write the built site into.
--output-filename NAME index.html Name of the main generated HTML file.
--create-404 {always,never,if_missing} if_missing Whether to generate a 404.html.
--zip-output off Zip the output directory after building.
--warn-missing-info on Warn if set_site_information was never called.
--additional-paths LIST (none) Semicolon-separated extra files to bundle into the built site (e.g., files your code opens).
--pyodide-package-style {build,cdn,pypi} pypi Where the built page gets the Drafter Pyodide package: a local build, a CDN, or PyPI.
--shared-runtime off Compile the page to attach to a Drafter host in its parent page, sharing one Pyodide runtime across many embedded demos (iframes); boots its own runtime only as a fallback.

Environment variables

Every setting can also be provided via environment variables. Boolean variables accept 1, true, or yes (case-insensitive) for true; anything else is false. List-valued variables are semicolon-separated.

General

Variable Type Corresponds to
DRAFTER_ENTRY string Path to the main entry file (the positional path argument).
DRAFTER_MODE string start_server (default) or compile_site (same as --compile).
DRAFTER_CONFIG_FILE list Semicolon-separated config file paths.
DRAFTER_VERBOSE bool Verbose output.

App behavior

Variable Type Corresponds to
DRAFTER_ENGINE string --engine
DRAFTER_PRERENDER_INITIAL_PAGE bool --prerender-initial-page
DRAFTER_ASSET_DIRECTORY string --asset-directory
DRAFTER_SHOW_FILENAME_AS string --show-filename-as
DRAFTER_MOUNT_DRAFTER_LOCALLY bool --mount-drafter-locally
DRAFTER_OVERRIDE_ASSET_URL string --override-asset-url
DRAFTER_SITE_TITLE string --site-title
DRAFTER_LOAD_PACKAGES_AUTOMATICALLY bool --load-packages-automatically
DRAFTER_SYSTEM_PACKAGES list --system-packages
DRAFTER_PROJECT_PACKAGES list --project-packages
DRAFTER_PYODIDE_DRAFTER_PATH string --pyodide-drafter-path

Site rendering and debugging

Variable Type Corresponds to
DRAFTER_SERVER_NAME string --server-name
DRAFTER_IN_DEBUG_MODE bool Debug mode (inverse of --production).
DRAFTER_ENABLE_SUBTLE_DEBUG_ENTRY bool --subtle-debug-entry
DRAFTER_ENABLE_AUDIT_LOGGING bool --audit-logging
DRAFTER_FRAMED string Whether the app is framed (inverse of --no-frame).
DRAFTER_THEME string --theme
DRAFTER_DEPLOY_IMAGE_PATH string --deploy-image-path
DRAFTER_EXTERNAL_PAGES list --external-pages
DRAFTER_ADDITIONAL_HEADER_CONTENT list --additional-header-content
DRAFTER_ADDITIONAL_STYLE_CONTENT list --additional-style-content
DRAFTER_ADDITIONAL_CSS_CONTENT list --additional-css-content
DRAFTER_ADDITIONAL_JS_CONTENT list --additional-js-content
DRAFTER_ADDITIONAL_SCRIPT_CONTENT list --additional-script-content
DRAFTER_USE_SHADOW_DOM bool --use-shadow-dom
DRAFTER_ROOT_ELEMENT_ID string --root-element-id
DRAFTER_NEWLINES_TO_BR bool --newlines-to-br

Development server

Variable Type Corresponds to
DRAFTER_PORT int --port (raises an error if not a valid integer).
DRAFTER_HOST string --host
DRAFTER_USE_RELOADER bool Inverse of --no-reloader.
DRAFTER_OPEN_BROWSER bool Inverse of --no-open-browser.
DRAFTER_INLINE_PY bool Inverse of --no-inline-py.
DRAFTER_SERVE_ADJACENT_FILES bool Inverse of --no-serve-adjacent-files.

Static builder

Variable Type Corresponds to
DRAFTER_OUTPUT_DIRECTORY string --output-directory
DRAFTER_OUTPUT_FILENAME string --output-filename
DRAFTER_CREATE_404 string --create-404
DRAFTER_ZIP_OUTPUT bool --zip-output
DRAFTER_WARN_MISSING_INFO bool --warn-missing-info
DRAFTER_ADDITIONAL_PATHS list --additional-paths
DRAFTER_PYODIDE_PACKAGE_STYLE string --pyodide-package-style
DRAFTER_SHARED_RUNTIME bool --shared-runtime

Tooling-only variables

Variable Used by Description
DRAFTER_MKDOCS_DEV docs build Set to 1 by drafter-docs --dev; makes the MkDocs codeblock plugin compile embedded demos with the local package build instead of PyPI.
DRAFTER_MKDOCS_API docs build Set to 1 by drafter-docs --api; generates the full per-module API reference (mkdocstrings), which is slow. Omitted builds emit a placeholder API page instead.
SKULPT_DIR JS build scripts Path to a local Skulpt build; read from a top-level .env file by npm run dev / npm run update-skulpt / npm run precompile to copy skulpt.js and skulpt-stdlib.js into the assets.

Configuration files

Instead of (or in addition to) flags and environment variables, you can put settings in a JSON file and pass it with --config-file (repeatable; later files override earlier ones) or DRAFTER_CONFIG_FILE (semicolon-separated). The file is keyed by configuration section, with keys matching the dataclass field names:

{
    "bootstrap": { "verbose": true },
    "app_common": { "engine": "pyodide", "site_title": "My Site" },
    "client_server": { "theme": "dark", "in_debug_mode": false },
    "app_server": { "port": 8080, "open_browser": false },
    "app_builder": { "output_directory": "public", "zip_output": true }
}

The five sections are bootstrap, app_common, client_server, app_server, and app_builder, matching the flag groups above.

Precedence (lowest to highest):

  1. Defaults defined in the code
  2. Environment variables
  3. Command-line arguments
  4. Configuration files
  5. Arguments passed to start_server(...) and imperative configuration calls in your code (e.g., set_website_title(...))

Configuring from code

start_server() accepts configuration keyword arguments directly, which override everything else:

start_server(State(...), site_title="My Site", theme="dark", port=8080)

Commonly used parameters: site_title, theme, framed, in_debug_mode, engine, port, host, open_browser, use_reloader, inline_py, prerender_initial_page, asset_directory, show_filename_as, information, verbose. Any other configuration field can be passed as an extra keyword argument. (The v1 parameters cdn_skulpt, cdn_skulpt_std, and cdn_skulpt_drafter are accepted but ignored, with a warning.)

There are also imperative helpers (importable from drafter) that reconfigure the running site:

Function Effect
set_website_title(title) Set the browser tab title.
set_website_style(style) / set_website_theme(theme) Set the theme ("default", "dark", "none"; None means "none").
set_website_framed(framed) Toggle the browser-window frame around the app.
hide_debug_information() / show_debug_information() Toggle the debug panel.
add_website_header(html) Inject raw HTML into <head>.
add_website_css(selector, css) Add a CSS rule (or raw CSS if only one argument is given).
set_site_information(author, description, sources, planning, links) Attach site metadata shown on the About page.
deploy_site(image_folder) Prepare for deployment (hides debug information).

Development

This section is for working on Drafter itself. The v2 rewrite lives on the v2-pyodide branch:

git checkout v2-pyodide

Setup (Python via uv)

  1. Prereqs: Python 3.10+ (3.11+ recommended), Node.js 18+ with npm, and uv. On Windows PowerShell:

    winget install astral-sh.uv
    
  2. Clone:

    git clone https://github.com/drafter-edu/drafter.git
    cd drafter
    
  3. Create the Python environment and install deps (reads pyproject/uv.lock):

    uv sync
    
  4. Install JS deps once (for builds/watchers):

    cd js
    npm install
    cd ..
    
  5. Run an example (uses uv's virtual env automatically):

    uv run examples\shop.py
    

    The examples/ directory contains dozens of small apps exercising individual features. Pass --engine skulpt if you need the Skulpt engine explicitly (the default is Pyodide).

Repository layout

Path Contents
src/drafter/ The Python package: components, payloads, router, client server, bridge, builder, config system.
js/ The TypeScript client (bridge, debug panel, engines integration), built with tsup into js/dist/.
examples/ Runnable example applications.
tests/ Python test suite (pytest).
docs/ MkDocs documentation sources (see also docs_legacy/ for the retired v1 pages).
tools/ Maintenance scripts.
Justfile Common dev recipes (just --list).

Wheels bundle js/dist/ into drafter/assets/ at build time (see pyproject.toml), so JS must be built before packaging.

Watch JS assets

To iterate on the JS client and have changes flow into the Python package automatically:

  1. In one terminal, run the JS watcher. This rebuilds the TypeScript bridge on every save:

    cd js
    npm run dev
    
  2. In another terminal, run the JS precompiler. This rebuilds the Skulpt-compiled version of the Drafter Python library on every save:

    cd js
    npm run precompile:watch
    
  3. In another terminal, run a local Drafter app so you can see live reloads:

    uv run examples\simplest.py
    

Notes:

  • The watcher writes bundles to js/dist/, which the dev server serves from /assets and includes in its file watcher, so connected browsers auto-reload.
  • If you maintain a local Skulpt build, set SKULPT_DIR in a top-level .env to copy skulpt.js and skulpt-stdlib.js into the assets on startup (npm run dev does this automatically; npm run update-skulpt runs it standalone).

Other useful JS scripts (run from js/): npm run build (production build), npm run precompile (one-shot minified Skulpt precompile), and npm run playground (build and serve a standalone playground at http://localhost:8777).

Run tests

  • JS tests (fast unit tests are the default; test:integration runs the real-Pyodide suites serially with a large heap; test:skulpt runs the legacy Skulpt suites, which are currently known-failing and not part of the regular baseline):

    cd js
    npm run test
    npm run test:integration
    
  • Python tests (uses uv env):

    uv run pytest --verbose --color=yes -vv
    
  • Or run everything plus formatting and linting via just:

    just validate
    

Build and serve docs

Use the Drafter docs wrapper instead of calling MkDocs directly (it is installed as the drafter-docs script and forwards all other arguments to mkdocs):

uv run drafter-docs build
uv run drafter-docs serve

Optional dev mode:

uv run drafter-docs build --dev
uv run drafter-docs serve --dev

In dev mode, the MkDocs Drafter codeblock plugin compiles embedded demos with Pyodide package style build (local package build) instead of the default pypi.

Full API reference:

uv run drafter-docs build --api

Rendering the per-module API reference through mkdocstrings dominates build time, so it is skipped by default and replaced with a placeholder page. Pass --api (combinable with --dev) when you need the real thing.

License

MIT — see LICENSE.txt.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

drafter-2.0.3.tar.gz (23.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

drafter-2.0.3-py3-none-any.whl (5.9 MB view details)

Uploaded Python 3

File details

Details for the file drafter-2.0.3.tar.gz.

File metadata

  • Download URL: drafter-2.0.3.tar.gz
  • Upload date:
  • Size: 23.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for drafter-2.0.3.tar.gz
Algorithm Hash digest
SHA256 8d8aab07b893ef8815b0cf8df006c851c2c59931dc3cc1b3b5810297336cf6b6
MD5 8ba512c3a2e46876a07f35390291b4b4
BLAKE2b-256 e67d28dca7d28cfa1be51030897088658900e34f08773979e393f80d7c346da9

See more details on using hashes here.

File details

Details for the file drafter-2.0.3-py3-none-any.whl.

File metadata

  • Download URL: drafter-2.0.3-py3-none-any.whl
  • Upload date:
  • Size: 5.9 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for drafter-2.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 0f5d2f3a9f345d564d6fbc05592781388ff60aa0047262ff6f4b2e41dbeff16d
MD5 ce481c4caacc31ec29189765b7a3e54a
BLAKE2b-256 f611eeeaf375156ef7264597356176bc264b19417a7690b07c3db3a6941132eb

See more details on using hashes here.

Release history Release notifications | RSS feed

2.1.0

2 files

This release

2.0.3 This release

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

1.9.5

2 files

1.9.4

2 files

1.9.3

2 files

1.9.2

2 files

1.9.1

2 files

1.9.0

2 files

1.8.9

2 files

1.8.7

2 files

1.8.6

2 files

1.8.5

2 files

1.8.4

2 files

1.8.3

2 files

1.8.2

2 files

1.8.1

2 files

1.8.0

2 files

1.7.3

2 files

1.7.2

2 files

1.7.1

2 files

1.7.0

2 files

1.6.1

2 files

1.6.0

2 files

1.5.7

2 files

1.5.6

2 files

1.5.5

2 files

1.5.4

2 files

1.5.3

2 files

1.5.2

2 files

1.5.1

2 files

1.5.0

2 files

1.4.2

2 files

1.4.0

2 files

1.3.1

2 files

1.3.0

2 files

1.2.1

2 files

1.2.0

2 files

1.1.4

2 files

1.1.3

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.5

2 files

1.0.4

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

0.2.0

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

0.0.2

2 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