Skip to content

max-muoto/senzo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Senzo

Senzo is a Python library for generating typed API clients from OpenAPI 3.0.x specifications. Unlike other client generators, Senzo is designed to support a number of HTTP clients (aiohttp, httpx, requests) for synchronous/async clients as well as several dataclass-like libraries such as Pydantic and msgspec.

Note: Senzo is still in development and is not yet ready for production use.

Installation

uv add senzo

Using Senzo

Senzo is designed to be used programmatically through the Python package as opposed to a CLI tool. One can write a simple Python script in order to generate your client package. For example, if you wanted to build an API client that utilized Pydantic models for serialization and httpx for HTTP requests, you could do the following:

# /// script
# requires-python = ">=3.12"
# dependencies = ["senzo", "pydantic", "httpx"]
# ///
import json
import senzo

from senzo.backends.dataclass.pydantic import PydanticBackend
from senzo.backends.http.httpx import HttpxBackend

with open("openapi.json") as f:
    spec = json.load(f)

tree = senzo.generate_tree(
    spec,
    package_name="my_api",
    dataclass_backend=PydanticBackend(),
    http_backend=HttpxBackend(async_mode=True),
)
senzo.write_package(tree, output_dir="./my_api")

Run the script with:

uv run generate_client.py

Senzo is designed to make it easy to generate synchronous and asynchronous clients for your API. For example, if you wanted to generate a synchronous client in addition to the asynchronous client, you could do the following:

# /// script
# requires-python = ">=3.12"
# dependencies = ["senzo", "pydantic", "httpx"]
# ///
import json
import senzo

from senzo.backends.dataclass.pydantic import PydanticBackend
from senzo.backends.http.httpx import HttpxBackend

with open("openapi.json") as f:
    spec = json.load(f)

tree = senzo.generate_tree(
    spec,
    package_name="my_api",
    dataclass_backend=PydanticBackend(),
    http_backend=HttpxBackend(async_mode=True),
)
senzo.write_package(tree, output_dir="./my_api")

tree = senzo.generate_tree(
    spec,
    package_name="my_sync_api",
    dataclass_backend=PydanticBackend(),
    http_backend=HttpxBackend(async_mode=False),
)
senzo.write_package(tree, output_dir="./my_sync_api")

The generated output is a small importable package (e.g. my_api/) containing:

  • client.py (HTTP client methods)
  • types/models.py (schema models)
  • _base.py (runtime helpers: errors, response wrapper, optional pagination/websocket support)
  • py.typed (PEP 561 marker)

Use the generated client

Exact method names depend on operationId and tag_style.

from my_api import Client

async with Client(base_url="https://api.example.com") as client:
    result = await client.some_operation(...)

Configuration

TagStyle

Controls how operations are organized:

  • TagStyle.FLAT (default): one Client class with all methods
  • TagStyle.FLAT_PREFIXED: one Client, methods prefixed with tag
  • TagStyle.GROUPED: Client exposes sub-clients per tag (uses the first tag only)
import senzo
tree = senzo.generate_tree(spec, tag_style=senzo.TagStyle.GROUPED, ...)

EnumStyle (inline enums)

Given an inline enum in your OpenAPI spec:

status:
  type: string
  enum: ["pending", "approved", "rejected"]
Style Generated Type
EnumStyle.LITERAL (default) Literal["pending", "approved", "rejected"]
EnumStyle.ENUM enum.Enum class
EnumStyle.STR_ENUM StrEnum class (Python 3.11+, recommended)
EnumStyle.STR str (no type safety)

Controls how inline OpenAPI enums (schemas with "enum": [...] but no named component) are represented in type annotations.

from senzo import generate_tree, EnumStyle

tree = generate_tree(spec, enum_style=EnumStyle.LITERAL, ...)

Named enum schemas under components/schemas are generated by the selected dataclass backend.

Backends

Dataclass (model) backends

  • senzo.backends.dataclass.pydantic.PydanticBackend
  • senzo.backends.dataclass.msgspec.MsgspecBackend

These backends control:

  • how schema objects are rendered (Pydantic models vs msgspec structs)
  • how JSON encoding/decoding code is emitted into the client

HTTP backends

  • senzo.backends.http.httpx.HttpxBackend(async_mode=...) (sync or async)
  • senzo.backends.http.aiohttp.AiohttpBackend() (async only)
  • senzo.backends.http.requests.RequestsBackend() (sync only)

Pagination (optional)

Senzo can generate an iterator helper <operation>_iter for operations that match a token-based pagination pattern.

Enable it via:

  • generator config: pagination={operation_id: {...}}, or
  • per-operation OpenAPI extension: x-senzo-pagination: {...}

Fields:

  • items (default "items"): response property containing the item list
  • next_token (default "next_page"): response property containing the next token
  • token_param (default "page_token"): query parameter name for the page token
  • limit_param (default "limit"): query parameter name for the page size

Limitations:

  • requires token_param and limit_param query parameters to exist
  • requires a success response type with items and next_token properties
  • does not paginate operations with a request body

WebSocket (optional; aiohttp backend only)

WebSocket endpoints are generated only when:

  • the OpenAPI operation has x-websocket: true, and
  • the selected HTTP backend supports websockets (currently AiohttpBackend)

Message types can be declared via x-websocket-messages:

paths:
  /ws/chat:
    get:
      operationId: connect_chat
      x-websocket: true
      x-websocket-messages:
        send:
          $ref: "#/components/schemas/ChatMessage"
        receive:
          $ref: "#/components/schemas/ServerEvent"

Hooks (generation-time; optional)

Senzo can bake calls to user-provided hook functions into the generated client. Hooks are configured at generation time using HooksConfig and are referenced by import path.

Supported hook slots:

  • pre_hook(inner, request, info) -> request
  • post_hook(inner, response, info) -> response
  • on_error(inner, error, info) -> None
  • on_result(inner, result, info) -> result

Acknowledgements

Senzo is heavily influenced by Progenitor, an OpenAPI client generator for Rust developed by Oxide.

About

OpenAPI client generator for Python

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages