# Getting started
# A function and its page
from typing import Annotated
from func_to_web import Max, Min, run
def volume(
width: Annotated[int, Min(1), Max(100)],
height: Annotated[int, Min(1), Max(100)],
depth: int = 10,
) -> float:
"""Multiply three dimensions."""
return width * height * depth
run(volume)
run() serves it at http://127.0.0.1:8000 until you stop it:
/ the index, one entry per function
/volume/ the form
/volume/invoke the execution, for any HTTP client
/doc the contract of every function, in plain text
The form knows that width and height go from 1 to 100 and that depth is 10
unless you change it; the docstring is the description on the page. The same
call from a script:
curl -X POST -H "Content-Type: application/json" \
-d '{"width": 3, "height": 4}' http://127.0.0.1:8000/volume/invoke
{"result": {"type": "text", "value": "120"}}
# Several functions
run() takes a list. WebFunction gives a function a name, a description or a
URL of its own:
from func_to_web import WebFunction, run
def divide(a: float, b: float) -> float:
"""Divide two numbers."""
return a / b
run([volume, WebFunction(divide, name="Divide numbers", slug="division")],
title="Internal tools")
WebFunction(
fn,
name="",
description="",
slug="",
capture_prints=None,
)
Left empty, name and slug come from the function's name and description
from its docstring. The page shows the name with _ as spaces and its first
letter uppercased (blur_image reads Blur image). A slug is one URL segment
of letters, digits, _ and -; doc, static, upload and returns are
taken by the space itself.
WebFunctions prepares a whole space once, to inspect it or mount it in
several applications:
from func_to_web import WebFunction, WebFunctions, app_of
space = WebFunctions((WebFunction(volume), WebFunction(divide)), title="Internal tools")
app = app_of(space)
Both are frozen and can be read: a WebFunction holds its schema
(pytypehint's Signature), its plan (the form, as /doc publishes it) and its
base html; a WebFunctions holds its functions, its title and its
document, the text of /doc.
# Inside FastAPI
app_of() returns the same application run() serves, to mount wherever you
want:
from fastapi import FastAPI
from func_to_web import app_of
app = FastAPI()
app.mount("/tools", app_of([volume, divide]))
Every route is relative, so the space works under any prefix: here the form is
at /tools/volume/. Authentication, middleware and CORS are the host's; see
Security and limits.
# Options
app_of(
fns,
*,
title: str | None = None,
capture_prints: bool | None = None,
max_upload_bytes: int | None = None,
pending_ttl: int | timedelta | None = 3600,
returns_ttl: int | timedelta | None = 3600,
uploads_dir: str | Path | None = None,
returns_dir: str | Path | None = None,
theme: Theme = "system",
) -> Starlette
| Option | What it sets |
|---|---|
title |
The name of the space, in the index and /doc. "FuncToWeb" if omitted. |
theme |
"system", "light" or "dark", for every page of the space. |
capture_prints |
Whether printed lines reach the page; on by default. See HTTP API. |
max_upload_bytes |
The largest file /upload accepts. No limit if omitted. |
pending_ttl |
How long an upload no execution used is kept, in seconds or a timedelta; None keeps it forever. |
returns_ttl |
How long a file the function returned stays downloadable. |
uploads_dir, returns_dir |
Where those files are kept. Also FUNCTOWEB_UPLOADS_DIR and FUNCTOWEB_RETURNS_DIR. |
Every option is checked when the application is built, so a wrong value fails at startup. The four storage options are one per process: the first application that stores files decides them, and a later one asking for others gets a warning.
run() takes the same options, plus the server's:
run(
fns,
*,
title: str | None = None,
capture_prints: bool | None = None,
max_upload_bytes: int | None = None,
pending_ttl: int | timedelta | None = 3600,
returns_ttl: int | timedelta | None = 3600,
uploads_dir: str | Path | None = None,
returns_dir: str | Path | None = None,
theme: Theme = "system",
host: str = "127.0.0.1",
port: int = 8000,
uvicorn_kwargs: dict[str, Any] | None = None,
) -> None
uvicorn_kwargs goes to uvicorn.run(), except app, host and port. On startup run() prints the installed version
(also func_to_web.__version__) and the two storage directories.
# Examples
Every file in examples/ runs on its own
(python examples/basic/hello.py) and teaches one thing:
| Folder | What it shows |
|---|---|
basic/ |
run(), several functions, WebFunction |
types/, validation/ |
every type and constraint |
files/ |
file parameters, reuse and storage |
outputs/, outputs_optional/ |
text, downloads, images and tables |
forms/ |
prefill, hidden fields and OpenForm |
streaming/ |
print() as progress |
fastapi/ |
app_of(), sdk.js and modals |
http/ |
calling a space from a script |
themes/ |
the three themes |
project/ holds small complete applications: a todo
list, the same list stored in a JSON file with
pytypehintstore, users with a
photo, bookings and a gallery of outputs. Only outputs_optional/ and
project/gallery.py need extra libraries (Pillow, matplotlib, pandas, polars or
numpy), and project/todo_stored.py needs pytypehintstore.