agentsclimarketplace

Unihttp client

Skill goduni/unihttp/.agents/skills/unihttp-client

Generate a typed, packaged unihttp API client library (SDK) from an OpenAPI 3.x spec or a plain API description. Produces typed DTOs, BaseMethod definitions, a client class, ruff + mypy (strict) config, sensible contract tests, and packaging. Use when the user wants to scaffold, bootstrap, or generate an API client / SDK on top of unihttp.From its SKILL.md

Install
npx -y skills add goduni/unihttp --skill unihttp-client

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

8.9 KB, as published. Nobody here has run it

unihttp client generator

Generate a runnable, packaged API client on top of unihttp from either an OpenAPI 3.x specification or a written API description. The output is an installable Python package with typed models, declarative methods, a client class, ruff + mypy (strict) config, and a small set of common-sense tests.

Always write idiomatic unihttp code — load the companion unihttp skill (or ../unihttp/SKILL.md) for the Wrong/Correct rules. The most important one: import from submodules (unihttp.method, unihttp.markers, unihttp.bind_method), never from the empty top-level unihttp package.

Defaults (override only when the user or spec asks):

  • Serializer: adaptix + stdlib dataclasses.
  • Backend: aiohttp async (AiohttpAsyncClient); use requests (RequestsSyncClient) when the user wants a synchronous client.
  • Layout: one file per method, model, and enum (see references/package-layout.md).
  • Tests: contract tests at the unihttp seam (build_http_request / make_response) — only pytest needed, no HTTP-mock library.

Map OpenAPI constructs with references/openapi-to-unihttp.md.

Workflow

1. Establish the input mode

  • OpenAPI spec (file path, URL, or pasted document): confirm openapi is 3.0.x or 3.1.x. If it is older, ambiguous, or incomplete, say so before generating. Extract: tags / path groups, operationIds, parameters (path/query/header), request bodies, response schemas + status codes, reusable components.schemas, and security schemes. Flag hard constructs early: oneOf, anyOf, allOf, discriminators, multiple media types, file uploads.
  • API description (no spec): gather, per endpoint, the method, URL template, path/query/header params, request body shape, and the response shape. Ask for the missing pieces rather than inventing them.

2. Confirm the essentials with the user (only what you cannot infer)

  • Package/distribution name and import name.
  • Sync or async (default async/aiohttp).
  • Auth scheme (header token, bearer, API key, …) if the spec declares one.
  • Whether to scaffold a fresh project or add the client into an existing repo.

Keep this short — infer sensible defaults and state them rather than interrogating.

3. Plan the package layout

Mirror unihttp's own conventions: src/<import_name>/ layout, hatchling build, ruff.toml + mypy.ini, py.typed. Use one file per symbol: models/<model>.py, enums/<enum>.py, methods/<operation>.py, and a single client.py. Re-export the public surface from __init__.py. For a very small API (a handful of endpoints), it is fine to collapse models into one models.py — use judgement, do not over-fragment a 3-endpoint client.

The full tree and file templates are in references/package-layout.md.

4. Generate DTOs (models and enums)

  • One @dataclass per schema; one enum.StrEnum/IntEnum per enumerated type.
  • Keep idiomatic snake_case Python fields. Preserve wire names via adaptix name_mapping in the client's Retort, not by renaming attributes.
  • Split request and response models when the response adds server-owned fields (id, timestamps, computed values).
  • Reuse shared schemas across operations instead of duplicating classes.
  • Map nullable / oneOf null to T | None; oneOf/anyOf to Union; allOf to composition or a merged dataclass.

5. Generate methods

One BaseMethod[ResponseType] per operation, in methods/<operation>.py:

  • Class name from operationId (PascalCase); else <Verb><Resource> (GetUser, CreateUser, ListUsers).
  • __url__ = the path template, __method__ = the HTTP verb (uppercase).
  • Map parameters to markers (Path/Query/Header), request body to Body, form to Form, file uploads to File + UploadFile. See the mapping table.
  • For paginated list endpoints, model the response envelope as a DTO (not a bare list) or expose page/offset as optional Query fields — see "Pagination" in the mapping reference. datetime/date/UUID/Decimal/Enum fields need no special handling (adaptix loads them from their standard string forms).
  • The generic argument is the response model. Use None ONLY for a genuinely empty body (a real 204 with no content); if the endpoint returns an empty object {} (common for DELETE), use BaseMethod[dict[str, Any]] — None raises TypeLoadError because make_response always deserializes the body.

6. Generate the client

A single *Client subclass in client.py:

  • Inherit AiohttpAsyncClient (async, default) or RequestsSyncClient (sync).
  • Bind each operation with bind_method.
  • Build a Retort (extend DEFAULT_RETORT) carrying any name_mapping, and pass AdaptixDumper(retort) / AdaptixLoader(retort). The mapping must cover the method classes (Body/Query fields), not only response models — for an all-camelCase API use one global name_mapping(map={...}) with no class predicate, which renames bodies, query filters, and models at once.
  • Accept base_url and auth inputs in __init__; default base_url from the spec's servers[0].url so callers can construct the client with no args. Inject auth via a Header field on methods or an auth middleware (AsyncMiddleware for async clients; type next_handler as AsyncHandler).
  • For endpoints needing pre/post logic, write an explicit method using call_method instead of bind_method.

7. Handle auth and errors

  • Simple token/api-key auth: add it as a header (middleware or per-method Header). Leave a TODO for OAuth/refresh flows rather than inventing them.
  • Map documented error status codes to exceptions with AsyncErrorMapperMiddleware / SyncErrorMapperMiddleware, or override handle_error. Keep a small exceptions module if the API has a real error taxonomy.

8. Generate packaging

Produce pyproject.toml (hatchling, src layout), ruff.toml, mypy.ini, py.typed, .gitignore, and a README.md with install + quickstart. Runtime dependency is unihttp with the chosen extras, e.g. unihttp[aiohttp,adaptix]>=0.2.9. Dev dependencies for the default tests are just ruff, mypy, pytest. In ruff.toml use extend-exclude (not exclude) so ruff keeps ignoring .venv. Templates in references/package-layout.md.

9. Generate common-sense tests

Default to contract tests at the unihttp seam — they cover exactly what generated code introduces and avoid flaky HTTP-mock dependencies:

  • Per operation, assert Method(...).build_http_request(dumper) produces the right url / method / query / body (markers wired correctly).
  • Per response model, assert Method(...).make_response(HTTPResponse(...), response_loader=loader) parses into the right DTO. Call it by keyword (response_loader=) — that is how call_method invokes it, so a method overriding make_response with a wrong parameter name is caught instead of silently passing a positional test.
  • These are synchronous and backend-agnostic — only pytest is needed, the same for async (aiohttp) and sync (requests) clients. See references/package-layout.md.

Optionally add one full-pipeline test through a mocked transport, but treat it as an extra. Note that aioresponses lags new aiohttp releases (it breaks on aiohttp 3.14), so pin a compatible pair, prefer respx for httpx clients, or use a local pytest-aiohttp server. Skip property-based / contract fuzzing unless the user asks.

10. Verify before claiming done

Run, from the generated package, and report real output:

ruff check .
ruff format --check .
mypy
pytest

Freshly generated code routinely needs one ruff check --fix + ruff format pass before it is clean (import grouping, long __all__ lines). Run those first, then the gate. Fix what remains. Do not claim the package is correct until these pass (or explicitly report what does not and why).

Output checklist

  • pyproject.toml (+ ruff.toml, mypy.ini, py.typed) when scaffolding a package.
  • A DTO per request/response schema that matters; an enum per enumerated type.
  • A BaseMethod per operation; a single client binding them.
  • A Retort carrying wire-name mappings when the API is not snake_case.
  • Minimal contract tests (seam-level), or a clear note on why tests were skipped.
  • README.md with install and a runnable quickstart (using async with for async clients).
  • A short report of ruff / mypy / pytest results.

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.