Files
python-sdk/examples/client.py
T
zeekay 241ca6dc59 the front door tells the truth about the client behind it
README: PyPI serves 3.2.1 and this tree is 3.2.11, so the install line is the
git one. Between the two the default version left the operation ids — 3.2.1
carries get_v1_keys/get_v1_tools (182 api modules, 2172 models) and this carries
get_keys/get_tools — so `pip install hanzoai` above a `get_keys()` quickstart
hands the reader a client that cannot run it. Counts are measured off the pinned
document: 1814 paths, 2479 operations, 191 tags + default_api = 192 API classes
over 2460 models, 2659 importable modules.

Auth was wrong everywhere and it was the same root cause: the document declares
no securityScheme, so every operation got an empty auth_settings and
Configuration(access_token=…) is read by nothing. Serializing get_keys from such
a Configuration produces a request with no Authorization header at all — the
five flows have been calling anonymously and reading the refusal as a bad key.
examples/client.py now passes the bearer through ApiClient's header_name/value
pair, verified against a local server that echoes what it received, and a smoke
test pins both halves so the day cloud emits the scheme, the test fails and the
header moves back into Configuration.

The examples gate resolved class names and stopped there, which is how `money`
and `tools` once passed it while every call in them named a method that no
longer existed. It now walks the syntax tree for attribute accesses on anything
bound to a *Api and asks the class: ten names resolve, and a stale get_v1_tools
fails in the same second the import passes.

Deleted five example scripts that imported hanzoai.Hanzo, hanzoai.cluster,
hanzoai.agents and hanzoai.completion — none of which exist — or were dict
literals captioned "in practice this would be called through the MCP interface";
compileall passed all five, because syntax is not a claim. Deleted three docs
that were fabrication rather than drift and that nothing rendered: FEATURES.md,
QUICKSTART.md, GPT5_ORCHESTRATION.md.

pkg/hanzo-agent said `pip install hanzoai` and `from hanzoai import Agent,
Runner`; the client has no Agent. Its own `full` extra resolved to
hanzoai[web3,tee,marketplace,cli], extras the client does not declare, so it
installed the client and none of the extensions. pyproject said BSD-3-Clause
while LICENSE is the Apache 2.0 text. test_smoke imported tracker_api and no
/v1/tracker path is emitted any more, so the suite carried a failing test naming
a module the client does not have; it reads analytics_api now, off the client.
2026-08-14 14:31:08 -07:00

69 lines
2.5 KiB
Python

"""The one place an example learns where the API is and who it is.
Every flow imports this and nothing else builds a client, so there is a single
answer to "which base URL?" and "which env var?" across all five.
Run any flow from the repo root::
python -m examples.hello
"""
from __future__ import annotations
import os
import sys
from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.exceptions import ApiException
#: Default host. ``HANZO_BASE_URL`` overrides it (staging, a local cloud, a tunnel).
BASE_URL = os.environ.get("HANZO_BASE_URL", "https://api.hanzo.ai")
#: ``zen4`` is the flagship the spec documents as its own example value.
MODEL = os.environ.get("HANZO_MODEL", "zen4")
def api_key() -> str:
"""Fail loudly and early when the key is absent.
Without this the SDK sends an unauthenticated request and the flow dies on a
401 several frames deep, which reads like an API bug rather than an unset
shell variable.
"""
key = os.environ.get("HANZO_API_KEY")
if not key:
raise SystemExit("HANZO_API_KEY is not set — export an IAM JWT or a pk-/sk- cloud key")
return key
def client() -> ApiClient:
"""An ApiClient bound to the configured host, carrying the key as a bearer.
The header is set here rather than through ``Configuration(access_token=…)``,
and that is a correction, not a preference. The document declares no security
scheme, so the generator wrote an empty ``auth_settings`` into every
operation, ``Configuration.auth_settings()`` returns ``{}``, and
``access_token`` is read by nothing. Measured on this tree: serializing
``get_keys`` from a Configuration built with ``access_token="sk-test"``
produces a request with no ``Authorization`` header at all. Every flow built
that way calls anonymously and collects a 403 that reads like a key problem.
``header_name``/``header_value`` is the generated client's own way to send a
header the document did not describe, so it stays the one way here until the
document carries the scheme.
"""
return ApiClient(Configuration(host=BASE_URL), "Authorization", f"Bearer {api_key()}")
def run(main) -> None:
"""Invoke a flow and report an API failure the way a caller can act on.
ApiException stringifies to the status line alone; the server's explanation
is in ``.body``, which is the part worth printing.
"""
try:
main()
except ApiException as e:
print(f"HTTP {e.status}: {e.body}", file=sys.stderr)
raise SystemExit(1) from e