shiftlabs
Type hints and a testing kit for shiftlabs workflows written in Python, and shiftlabs.client
for Python apps that call the shiftlabs API.
uv add --dev shiftlabs # or: pip install shiftlabs
from shiftlabs import Context, workflow
async def charge(invoice_id: str) -> dict[str, str]:
...
@workflow(payload={"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]})
async def refund_check(ctx: Context) -> dict[str, str]:
charged = await ctx.step.run("charge", charge, ctx.payload["id"], retries={"limit": 3, "delay": "10 seconds"})
ctx.log.info("charged", {"id": charged["id"]})
return charged
For workflows, the package is for your editor, type checker and tests. You don't need it at run time: the platform
adds the same shiftlabs module to every Python workflow when you deploy, at the version the platform runs, and never
adds shiftlabs.testing or shiftlabs.client. An app that calls the API with shiftlabs.client installs it as a
dependency.
Run npx shiftlabs types to write shiftlabs_types.pyi with your workspace's secret, var, agent and workflow names.
After that, ctx.secrets.NAME and ctx.vars.NAME only accept those names, and so do the client's agent and workflow
arguments.
Testing
shiftlabs.testing.run_workflow runs a workflow inside a test, the way the platform runs it, with no deploy. Steps
run at once: retries don't wait, FatalError fails a step at its first attempt, and sleeps return at once. Waits are
answered by step name from events, approvals, asks, calls and starts, next to payload, secrets, vars
and state. A missing event returns None, a missing approval is denied, and a missing ask or call fails its step.
The payload is checked against the workflow's schema, as a webhook's is. ctx.secrets.NAME reads a stand-in, as on
the platform: give a secret as {"value": …, "readable": True} to reveal it, or {"value": …, "hmac": True} to
sign with it.
from unittest.mock import AsyncMock, patch
from shiftlabs.testing import run_workflow
import refund_check
def test_refund_check_charges_once() -> None:
with patch.object(refund_check, "charge", AsyncMock(return_value={"id": "ch_123"})) as charge:
result = run_workflow(refund_check.refund_check, payload={"id": "ch_123"})
assert result.status == "completed", result.error
assert result.output == {"id": "ch_123"}
charge.assert_awaited_once_with("ch_123")
It returns a RunResult with status, output, error, steps, logs, state and started. Then it runs the
workflow a second time on the stored step results, as a run that resumes after a sleep does, and raises
AssertionError("code outside a step changed on replay: …") if that calls other steps, such as a step named from
time.time(). replay=False skips that check. It mocks no network: use unittest.mock or your HTTP client's mocks.
run_workflow runs the workflow in an event loop of its own, so sync and async tests can call it.
Client
shiftlabs.client calls the shiftlabs API from a Python app: chat with agents as your users and stream their answers,
ask for JSON answers, run workflows, read runs, send events, answer approvals and mint chat tokens. Shiftlabs waits
for each call; AsyncShiftlabs has the same members for asyncio. Both use only the standard library.
import os
from shiftlabs.client import Shiftlabs
sl = Shiftlabs(api_key=os.environ["SHIFTLABS_API_KEY"])
for event in sl.agents.chat("billing", person="user_ann").send("Where is my refund?"):
if event["type"] == "text":
print(event["delta"], end="")
started = sl.workflows.run("stripe-refund", {"invoiceId": "in_123"})
print(sl.runs.get(started.run_id)["run"]["status"])
Failures raise shiftlabs.client.ShiftlabsError, whose code, hint and to_dict() match the CLI's --json errors.
See the client docs.
Docs: docs.shiftlabs.org.
Metadata
Release files for shiftlabs 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| shiftlabs-0.3.0.tar.gz | 46.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| shiftlabs-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 95.1 kB
Release files / shiftlabs-0.3.0.tar.gz
| Download URL | shiftlabs-0.3.0.tar.gz |
|---|---|
| Size | 46.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
55fdd274072854acddf335027a5db6b08d1208e51be813e75e03335533f1d433
|
|
BLAKE2b-256 checksum How to use checksums |
747a2c8451f98af05a3d5fa101b1b692b2fa17c034f54bdc1f702e70b4e5afe4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / shiftlabs-0.3.0-py3-none-any.whl
| Download URL | shiftlabs-0.3.0-py3-none-any.whl |
|---|---|
| Size | 48.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0765df08ab9e1417819d1bb24c44dab8c44f963e64f5e053f93498d6afbe8150
|
|
BLAKE2b-256 checksum How to use checksums |
82f703ad221ef185a565b8651144adc415e97eb26989a6a91b8791840a81447c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for shiftlabs 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.