Blog
Guides

How to turn a Python script into a web form

DK
Dan Kowalsky
Founder ·

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

  • str is a text box.
  • int and float are number boxes.
  • bool is a checkbox.
  • date and datetime are date and date-time pickers.
  • Literal["a", "b"] or an Enum is a dropdown.
  • list[str] and other lists are a list editor, for adding several values.
  • X | None = None is an optional field.
  • A Pydantic model is a grouped sub-form, for related fields like an address.
  • File is 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 dict inputs. 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.

See how it works