Blog
Guides

How to share Python scripts with non-technical teammates

DK
Dan Kowalsky
Founder ·

It usually starts with a message like "can you run that thing again?" You wrote a script that exports signups, refunds an order or cleans up a spreadsheet. It works, and now the people who need it most can't run it themselves. So you run it for them, every time.

This guide covers what non-technical teammates actually need from a script, the common ways to share one, and how to get your code ready before anyone else touches it.

What non-technical users need

Before choosing a tool, it helps to know what "usable" means for someone who doesn't write code:

  • A form, not a terminal. No installing Python, no virtual environments, no command-line arguments.
  • Inputs they can't get wrong. A dropdown of regions beats a text box where "emea", "EMEA " and "Europe" all mean something different.
  • Results they can read. A table they can download, or a short summary, not a wall of printed dictionaries.
  • A record of what happened. Who ran it, with which inputs, and what came back, so nobody has to ask "did that go through?"
  • No secrets in their hands. API keys should never be pasted into a script, a notebook or a chat message to make it work.

Keep this list in mind as you weigh the options below.

The usual options

Send them the script

The quickest option, and it rarely survives the first try. Your teammate needs the right Python version, the right packages and a terminal, and any API keys end up in a file on their laptop. When something breaks, you're debugging their machine over a video call.

Share a notebook

Google Colab or a shared Jupyter notebook removes the install step. But a notebook shows all of your code, so it's easy to edit a cell by accident or run cells out of order. Keys usually end up in the notebook too, and there's no record of who ran what.

Build a small web app

Streamlit, Gradio or a small Flask app give people a real interface, and they work well. The cost is that you now own a web app: hosting, sign-in, deploys, and an interface to update every time the script changes. That's worth it for one important tool, and a lot of work for your fifth.

Wrap it in a Slack command

Great for one-line actions like "refund order 1234". It gets awkward fast with several inputs, file uploads or results that are really tables.

Use an internal tool builder

Tools like Retool are powerful when you need a custom dashboard. They usually mean rebuilding your logic in their editor or exposing it as an API first, so your Python stops being the source of truth.

Keep running it yourself

It works, and it makes you the bottleneck. Every request waits until you have time, and you're the only one who knows how it works.

We compare Callables with some of these in more depth: Zapier, Power Automate, and Windmill and Airplane.

Get the script ready first

Whatever you choose, the script itself needs a few changes before other people can use it. Here's a typical starting point:

# export_signups.py
import sys
import requests

TOKEN = "paste-your-token-here"
region = sys.argv[1]
days = int(sys.argv[2])

resp = requests.get("https://crm.example.com/api/signups",
                    params={"region": region, "days": days},
                    headers={"Authorization": f"Bearer {TOKEN}"})
print(resp.json())

It works if you know to run python export_signups.py EMEA 7. For anyone else, it needs these five changes:

  1. Wrap it in a function with typed inputs. Parameter names and types tell people (and tools) what to enter. days: int = 7 documents itself; sys.argv[2] doesn't.
  2. Use choices instead of free text. If only a few values are valid, say so with Literal["NA", "EMEA", "APAC"].
  3. Give sensible defaults. Most people should be able to run it without changing anything.
  4. Return something readable. Return clean rows with friendly column names instead of printing raw JSON.
  5. Fail with a message a person can act on. "No signups in EMEA in 7 days. Try more days." beats a stack trace.

And keep the token out of the code: read it from an environment variable.

Here's the same script with those changes:

import os
from typing import Literal

import requests


def export_signups(
    region: Literal["NA", "EMEA", "APAC"] = "NA",
    days: int = 7,
    include_trials: bool = False,
) -> list[dict]:
    """Signups for a region over the last few days, newest first."""
    token = os.environ["CRM_TOKEN"]
    resp = requests.get(
        "https://crm.example.com/api/signups",
        params={"region": region, "days": days},
        headers={"Authorization": f"Bearer {token}"},
        timeout=30,
    )
    resp.raise_for_status()
    signups = resp.json()["signups"]
    rows = [r for r in signups if include_trials or r["plan"] != "trial"]
    if not rows:
        raise ValueError(
            f"No signups in {region} in {days} days. Try more days."
        )
    return [
        {
            "Name": r["name"],
            "Email": r["email"],
            "Plan": r["plan"],
            "Signed up": r["created_at"][:10],
        }
        for r in rows
    ]

This version is easier to share whichever option you pick. It's also easier to test, and easier to read six months from now.

Sharing it with Callables

We built Callables for this situation: Python you already have, used by people who don't write code. Add @deploy to the function, declare the secret it needs, and push the repo to GitHub:

from typing import Literal

from callables import deploy


@deploy(title="Export signups", secrets=["CRM_TOKEN"])
def export_signups(
    region: Literal["NA", "EMEA", "APAC"] = "NA",
    days: int = 7,
    include_trials: bool = False,
) -> list[dict]:
    ...  # unchanged

Your teammates get a page with a form built from the function:

  • Region is a dropdown, Days is a number box set to 7, and Include trials is a checkbox. The labels come from the parameter names.
  • The docstring becomes the description under the title.
  • The result shows as a table with a CSV download, because the function returns a list of rows.
  • Every run is saved with its inputs, logs and result, so people can check what happened or run it again.
  • The CRM token is set once in the web app and passed to the code when it runs. The people running it never see it.

You decide who can run each function, and anyone who can run it can put it on a schedule, like every Monday at 9am. When you change the code and push, the form updates on its own. There's no front end to maintain.

A checklist before you share

  • The work happens in a function with typed, well-named inputs.
  • Known choices use Literal or an Enum, not free text.
  • Every input has a sensible default where one exists.
  • The function returns rows, a summary or a file, not printed output.
  • Errors say what to change.
  • Keys come from environment variables, never from the code or the inputs.

Get those right and handing a script to a teammate stops being a favor you do for them every week.

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