shiftlabs Start free
Menu Python packages

Python packages

List the packages at the top of a Python workflow. They're built once, when you deploy, and every run of that version has them.

What you'll build

repo-stars, a Python workflow that checks its payload with pydantic and reads a GitHub repository's star count with httpx. You deploy it with one call, wait for its packages to build, and run it.

  1. List the packages

    A # /// script block at the top of the file.

  2. Deploy and wait for the build

    A new set of packages builds once, usually within a minute.

  3. Run it

    Start a run with a payload, then read its result.

Write the workflow

dependencies lists package names, each with optional extras and version specifiers, such as pydantic>=2 or httpx[http2]==0.28.*. URLs, paths and environment markers are refused. A payload that doesn't match the pydantic model is refused before a run starts.

pythonrepo_stars.py
# /// script
# dependencies = ["httpx", "pydantic>=2"]
# ///
import httpx
from pydantic import BaseModel
from shiftlabs import workflow


class Repo(BaseModel):
    repo: str


@workflow(payload=Repo)
async def repo_stars(ctx):
    repo = Repo.model_validate(ctx.payload).repo

    async def fetch_stars():
        async with httpx.AsyncClient() as http:
            r = await http.get(f"https://api.github.com/repos/{repo}")
            r.raise_for_status()
            return r.json()["stargazers_count"]

    stars = await ctx.step.run("fetch stars", fetch_stars)
    return {"repo": repo, "stars": stars}

Use async HTTP clients such as httpx's AsyncClient. Requests go through the egress gateway like fetch, so they count toward your outbound requests. A file next to the workflow, such as helpers.py, can be imported too; list packages only in the workflow file's own block.

Deploy and wait

Send the file with content-type: text/x-python. If the same set of packages was built before, the deploy goes live at once and answers with the version. A new set answers 202 with a deployId; poll it until status is live or failed.

shellcurl
# Answers 202 {"status": "building", "deployId", "packages", "poll"}, or,
# for packages built before, {"versionHash", "sizeBytes", "warnings", …}.
curl -X PUT https://api.shiftlabs.org/v1/workflows/repo-stars \
  -H "authorization: Bearer $SHIFTLABS_API_KEY" \
  -H "content-type: text/x-python" \
  --data-binary @repo_stars.py

# Answers {"id", "workflow", "status", "packages", "result", …}: status is
# building, live or failed, and result is the deploy's answer once it ends.
curl https://api.shiftlabs.org/v1/deploys/<deployId> \
  -H "authorization: Bearer $SHIFTLABS_API_KEY"
  • A version keeps the packages it was deployed with. Deploying the same code with the same dependencies reuses the build. To get the newest versions the ranges allow, deploy with ?upgrade=1.
  • Pure-Python packages work as published. A compiled one, such as numpy, needs a build for the Python runtime shiftlabs runs. Without one, the build fails and result says why.
  • A workflow lists at most 30 packages. A workspace can start 20 new builds in 24 hours and have 5 deploys waiting on builds; past either, the deploy gets 429 with Retry-After. See Rate limits.
  • A failed build is tried again only after 10 minutes, so deploying the same set at once again answers the same failure.

Run it and check it worked

shellcurl
# Answers 202 {"runId", "versionHash"}.
curl -X POST https://api.shiftlabs.org/v1/workflows/repo-stars/runs \
  -H "authorization: Bearer $SHIFTLABS_API_KEY" \
  -H "content-type: application/json" \
  -d '{"repo": "python/cpython"}'

# Answers {"run", "instance"}: run.status is completed once it's done, and
# instance.output is {"repo": "python/cpython", "stars": …}.
curl https://api.shiftlabs.org/v1/runs/<runId> \
  -H "authorization: Bearer $SHIFTLABS_API_KEY"

# A payload the model refuses: 400 {"error": "payload does not match the
# schema", "detail"}, and no run.
curl -X POST https://api.shiftlabs.org/v1/workflows/repo-stars/runs \
  -H "authorization: Bearer $SHIFTLABS_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name": "cpython"}'

GitHub allows 60 unauthenticated requests an hour from one IP address. For more, put a token in a secret for api.github.com and send f"Bearer {ctx.secrets.GITHUB_TOKEN}" as the authorization header. The gateway puts the real token in place of the stand-in.

Reference