How to turn a Python script into a web form
A script that takes arguments on the command line is hard to hand to anyone who doesn't use a terminal. With Callables, you don't build a form for it. The form comes from the function itself: its parameters, their types and their defaults.
Start with a function
Put the work in a function with typed parameters and add @deploy:
from datetime import date
from typing import Literal
from callables import deploy
@deploy(title="Closed deals")
def closed_deals(
region: Literal["NA", "EMEA", "APAC"] = "NA",
since: date | None = None,
min_value: int = 0,
include_renewals: bool = False,
) -> list[dict]:
"""Deals closed in a region, newest first. Amounts are in USD."""
...
Push the repo, and your teammates get a page titled "Closed deals", with the docstring's first paragraph as its description, and a form with four fields:
- Region: a dropdown with NA, EMEA and APAC, set to NA.
- Since: an optional date picker.
- Min value: a number box set to 0.
- Include renewals: a checkbox.
Labels come from the parameter names, so min_value shows as "Min value". Choose names that read well to the person filling in the form.
How each type hint shows up
stris a text box.intandfloatare number boxes.boolis a checkbox.dateanddatetimeare date and date-time pickers.Literal["a", "b"]or anEnumis a dropdown.list[str]and other lists are a list editor, for adding several values.X | None = Noneis an optional field.- A Pydantic model is a grouped sub-form, for related fields like an address.
Fileis a file upload. See file uploads.
A parameter with a default is optional and prefilled. A parameter without one is required.
Inputs arrive validated
Callables checks every input before your function runs, so you don't write parsing code. A date arrives as a datetime.date, a number as an int, and a Pydantic model as an instance. If someone leaves a required field empty or types text into a number box, they see what to fix and your code never runs.
For rules the types can't express, raise a ValueError with a message for the person:
if since and since > date.today():
raise ValueError("Since can't be in the future.")
They see exactly that message, so write it the way you'd say it to them.
Tips for forms people get right
- Prefer dropdowns to free text. If only a few values are valid, use
Literal. Nobody can type "emea " by mistake. - Give defaults wherever you can. Most people should be able to run it without changing anything.
- Avoid
dictinputs. They show as a raw JSON box. Use a Pydantic model for structured input instead. - Annotate the return type too. It decides how the result is shown. See showing results.
Before you push, call check lists anything the form can't handle, like *args or an unsupported type, and names the parameter to fix.
Turn your scripts into tools your team can run
Add @deploy to a Python function and push. Your team gets a form, schedules and run history.