Skip to main content
Version: 1.0.0

SDK Python: write a handler

Use a package-installed handler​

A package's proposed handler uses the consuming project's .venv interpreter, where touba sdk install python installs the SDK. Install its business dependencies in that environment too.

Touba sets PYTHONDONTWRITEBYTECODE=1 for handler processes and launches Python host modules with -I -B. For a custom handler using -I or -E, add -B explicitly: those modes ignore PYTHON* variables. For separate tests:

.venv/bin/python -B -m unittest discover -s tests

On Windows, use .venv/Scripts/python.exe. Caches from external tools must stay outside the installed packages/ copy, whose inventory is verified on each preparation.

Goal: declare a tool in Touba, generate its Python handler and run it through the Core. No model or external service is needed.

1. Install the SDK​

In an empty project directory:

touba init --language python
touba sdk install python

Python 3.11+ with uv, or venv and pip, is required. The bundled SDK is installed offline in .venv. Activate it using source .venv/bin/activate on macOS/Linux or .venv\Scripts\Activate.ps1 on Windows.

2. Declare the Touba contract​

Create the src/sdk directories, then this file:

src/sdk/handlers.tb
module sdk.handlers
tool Normalize
takes value as Text
gives Text
strategy Run
takes value as Text
gives Text
uses Normalize
do
Normalize
with value as value
into result
give result

The business contract stays in Touba. Python implements the tool.

3. Generate and inspect the binding​

Pass the host decisions explicitly:

touba prepare --mode write --host-decisions-json '{"bindings":{"tool:sdk.handlers/Normalize":{"grants":[],"effect_contract":{"kind":"none"}}}}'

This tool only transforms text: no grants or external effects. For network, file or secret access, declare the actual requirements and review the corresponding host decisions. The SDK never grants them on your behalf.

prepare generates handlers/sdk/handlers/normalize.handler.py, its .handler.contract.test.py and the manifest. It reads contracts without importing or executing your Python code.

4. Write the business function​

Replace only the body of run in the generated handler:

async def run(request, context):
return request.strip().upper()

Keep the generated constants and define_tool call. A synchronous def run(request, context) works too. Here request is the text itself; multiple Touba parameters would arrive as a dictionary with their declared names.

5. Test and run​

Check the admission contract:

python handlers/sdk/handlers/normalize.handler.contract.test.py

Add your own business tests too: generated tests do not prove that the transformation meets your requirements.

run.py
from touba_sdk import ToubaEngine

engine = ToubaEngine(project_root=".")
result = engine.run(
source="src/sdk/handlers.tb",
manifest=".touba/build/dev/touba.manifest.json",
inputs={"value": " invoice 42 "},
)
assert result.status == "success", result
assert result.result == "INVOICE 42", result
print(result.result)

Run python run.py. The expected output is INVOICE 42. The Core checks types, tool admission and tracing regardless of the handler's host language.

6. Lock the files​

To separate business logic, add this file beside the handler:

handlers/sdk/handlers/helpers.py
def normalize(value):
return value.strip().upper()

Add from helpers import normalize to the handler and use return normalize(request) inside run. Touba follows this local import. Create the Python package identity at the project root:

pyproject.toml
[project]
name = "business-handlers"
version = "0.1.0"

The SDK is already installed from its local wheel. Declare other dependencies in pyproject.toml and retain your manager's lockfile, as described in the supply-chain guide.

touba compile src/sdk/handlers.tb --out program.air.json
touba supply-chain generate --air program.air.json \
--manifest .touba/build/dev/touba.manifest.json --out touba.lock.json
touba supply-chain validate --air program.air.json \
--manifest .touba/build/dev/touba.manifest.json --lock touba.lock.json

The lock includes the handler, helpers.py and pyproject.toml. Changing helpers.py after generation makes validation fail, before handler dispatch when the lock is required at runtime. Inventory generation executes no project Python function.

Review remains pending and SDK identity remains unknown without explicit metadata. These expected warnings do not qualify the project for production. Review the actual lock and dependencies before recording an approval.

For a handler with effects​

Use the appropriate effect contract and return a receipt correlated with the actual execution, never a receipt fabricated before the operation. The Python helpers create_tool_result, create_effect_receipt and create_compensation_receipt cover the same envelopes as JavaScript. A receipt alone does not guarantee external-service idempotency.

See the Python SDK for secrets, errors, receipts and current scope, and the approval tutorial for governed resumes.