mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
Name the generator we actually run
Our SDKs are generated by openapi-generator on our own CI. The docs said a hosted third-party service, which we retired: hanzoai/openapi's LLM.md and its regenerate-sdks.yml both already said so, while its README still called that service "canonical" and told a reader to fetch an API key for it out of KMS. Two sources disagreeing is how somebody follows the wrong one, and the wrong one here sends them looking for a credential that should not exist. The same claim had spread to what customers and agents read: five pages on docs.hanzo.ai and six agent skills advertised a vendor we do not use, and hip-0040 -- the standard for multi-language SDKs -- argued FOR it by name. The argument survives without the brand, because it was never about the vendor: one spec beats N hand-written clients. python-sdk's mock script also pulled that vendor's fork of prism-cli; it takes upstream @stoplight/prism-cli now. What is deliberately left alone is every occurrence that is not about us: a filament material in a 3D-printer integration, a browser of that name in three user-agent parsers, tokenizer vocabulary where the string is a token id, and citations recording where Mux, Perigon and Stigg publish their own specs. Renaming any of those breaks something real.
This commit is contained in:
+3
-3
@@ -1,4 +1,4 @@
|
||||
File generated from our OpenAPI spec by Stainless.
|
||||
Generated from our OpenAPI spec.
|
||||
|
||||
This directory can be used to store example files demonstrating usage of this SDK.
|
||||
It is ignored by Stainless code generation and its content (other than this keep file) won't be touched.
|
||||
This directory holds example files demonstrating usage of this SDK. Code
|
||||
generation ignores it, so nothing here is overwritten by a regeneration.
|
||||
@@ -0,0 +1,101 @@
|
||||
# hanzo-tasks
|
||||
|
||||
Durable activities on the native Hanzo Tasks engine.
|
||||
|
||||
The engine is Hanzo's own (`hanzoai/tasks`), reached over JSON at
|
||||
`api.hanzo.ai/v1/tasks`. It speaks no protobuf and no gRPC, and this package
|
||||
depends on no workflow runtime — dispatching work and running a worker are both
|
||||
plain HTTP over `httpx`.
|
||||
|
||||
```bash
|
||||
pip install hanzo-tasks
|
||||
```
|
||||
|
||||
## Dispatch
|
||||
|
||||
```python
|
||||
from hanzo_tasks import Tasks
|
||||
|
||||
async with Tasks.from_env() as tasks:
|
||||
activity = await tasks.dispatch(
|
||||
"render",
|
||||
input={"scene": 3},
|
||||
task_queue="gpu",
|
||||
heartbeat_timeout="60s",
|
||||
)
|
||||
print(activity.id, activity.run_id, activity.status)
|
||||
```
|
||||
|
||||
`from_env()` reads `HANZO_API_KEY`, `HANZO_TASKS_URL` and
|
||||
`HANZO_TASKS_NAMESPACE`. The org is never named in a request: IAM validates the
|
||||
bearer token and the edge mints the org from the validated claim, so a caller
|
||||
cannot reach another tenant's shard by asking to.
|
||||
|
||||
## Run a worker
|
||||
|
||||
The worker **polls**. It never accepts a push, so it needs no inbound address
|
||||
and runs behind NAT.
|
||||
|
||||
```python
|
||||
from hanzo_tasks import Tasks, Worker
|
||||
|
||||
tasks = Tasks.from_env()
|
||||
worker = Worker(tasks, task_queue="gpu")
|
||||
|
||||
|
||||
@worker.handler("render")
|
||||
async def render(activity):
|
||||
return {"frames": 120}
|
||||
|
||||
|
||||
await worker.run()
|
||||
```
|
||||
|
||||
Return a value to complete the activity; raise to fail it with that cause.
|
||||
Handlers may be async or plain functions.
|
||||
|
||||
## What the engine does, so you don't
|
||||
|
||||
These are properties of the engine, not of this client. There is deliberately
|
||||
no second copy of them here, because a second copy can disagree.
|
||||
|
||||
- **A dead worker's work comes back.** The engine reaps expired leases before
|
||||
every claim, so an activity whose claimant stopped heartbeating returns to
|
||||
`SCHEDULED` and the next poll picks it up. No client-side timer.
|
||||
- **Two workers never claim the same activity.** Claims are serialized per
|
||||
namespace.
|
||||
- **The lease is the activity's own heartbeat timeout**, else the worker's
|
||||
`lease_seconds`, else the engine default. `Worker` heartbeats at a third of
|
||||
the window the server actually granted, so a beat can be missed without
|
||||
losing the claim.
|
||||
- **An empty queue is not an error.** It is `204`, and `claim()` returns
|
||||
`None`.
|
||||
- **Retries are the engine's.** Pass a `RetryPolicy` to `dispatch()`; the
|
||||
engine counts attempts and fails the activity when they are exhausted.
|
||||
|
||||
## Reading state
|
||||
|
||||
```python
|
||||
page = await tasks.activities(page_size=50)
|
||||
for activity in page:
|
||||
print(activity.id, activity.status, activity.attempt)
|
||||
|
||||
activity = await tasks.describe(id, run_id)
|
||||
events, cursor = await tasks.history(id, run_id)
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
The engine reports `{"error": ..., "code": <int>}` — `code` is a number here,
|
||||
not the `status` string the rest of the Hanzo API uses.
|
||||
|
||||
| Exception | Meaning |
|
||||
|---|---|
|
||||
| `Denied` | 401/403 — no validated principal, or one carrying no org |
|
||||
| `NotFound` | 404 — no such activity in this namespace |
|
||||
| `Terminal` | 409 — already completed, failed or canceled |
|
||||
| `TasksError` | anything else, carrying `.code` |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -27,7 +27,7 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any, Dict, List, Final, Tuple, Union
|
||||
from functools import lru_cache # noqa: TID251 - not using Stainless SDK
|
||||
from functools import lru_cache # noqa: TID251 - hand-written package, not the generated client
|
||||
|
||||
# Type aliases with full annotations
|
||||
ASTDict = Dict[str, Any]
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
# Regenerate the Hanzo Python client (`hanzoai`) from the unified OpenAPI spec.
|
||||
#
|
||||
# The ONE way: hanzoai/openapi `hanzo.yaml` is the single source of truth. The
|
||||
# `hanzoai` package under pkg/ is generated from it with openapi-generator — no
|
||||
# Stainless. Other pkg/hanzo-* packages are hand-written and untouched here.
|
||||
# `hanzoai` package under pkg/ is generated from it with openapi-generator, run
|
||||
# on our own CI. Other pkg/hanzo-* packages are hand-written and untouched here.
|
||||
#
|
||||
# ./scripts/generate.sh # pulls spec from hanzoai/openapi@main
|
||||
# SPEC=/path/to/hanzo.yaml ./scripts/generate.sh # local spec override
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ echo "==> Starting mock server with URL ${URL}"
|
||||
|
||||
# Run prism mock on the given spec
|
||||
if [ "$1" == "--daemon" ]; then
|
||||
npm exec --package=@stainless-api/[email protected] -- prism mock "$URL" &> .prism.log &
|
||||
npm exec --package=@stoplight/[email protected] -- prism mock "$URL" &> .prism.log &
|
||||
|
||||
# Wait for server to come online
|
||||
echo -n "Waiting for server"
|
||||
@@ -37,5 +37,5 @@ if [ "$1" == "--daemon" ]; then
|
||||
|
||||
echo
|
||||
else
|
||||
npm exec --package=@stainless-api/[email protected] -- prism mock "$URL"
|
||||
npm exec --package=@stoplight/[email protected] -- prism mock "$URL"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user