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:
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.
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:
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:
[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.