Flows
Offer next actions on each result row, with their inputs filled in from that row.
A flow chains functions together. Each row of a table result, or a single record result, offers next actions with their inputs filled in from that row. Every step is still an ordinary function: it can be run on its own, scheduled or shared separately.
from typing import Literal
from callables import Group, Next
users = Group("users", title="Users")
@users.deploy(title="Make admin")
def make_admin(user_id: int) -> str: ...
@users.deploy(title="Subscription requirement")
def set_subscription(
user_id: int,
required: Literal["Required", "Not required"],
) -> str: ...
@users.deploy(
title="Find user",
next=[
make_admin,
Next(set_subscription, label="Change subscription"),
],
)
def search(query: str) -> list[dict]:
return [{"user_id": 42, "name": "Ann Lee"}]How inputs are filled
- An action's inputs are filled from result fields with the same name.
Next(action, user_id="id")fillsuser_idfrom a field with a different name, hereid.label=changes the button text.- Inputs the row doesn't fill are left for the person to complete.
What next accepts
- Deployed functions, as in
next=[make_admin]. - Names, for a function defined further down:
"make_admin", or"users.make_admin"for another group's action. Next(...), for a label or field mapping.
In the web app
Each result row gets a button per next action. It opens that action's form with the row's values filled in, and the new run links back to the one it came from. People only see next actions they're allowed to run, so you can let a whole team search users and only a few people make admins. See sharing and access.
In a terminal
call users search --query ann lists the rows, asks which one and what to do next, prompts for any inputs the row didn't fill, and runs it. Press Enter to stop. Scripts and --json get plain output with no prompts.
For a worked example, see build an internal admin panel in Python.