Skip to main content
skill CX Codex

python · finom — Claude Code skill: typed Python client from a Vovk API

Turns Vovk.ts schemas into a typed Python package: RPC functions, TypedDict shapes, JSON Lines streaming. Useful when the API exists but the Python client does not.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add finom/vovk --skill python -a claude-code
Open on GitHub

What python does

The skill drives vovk-python against the same .vovk-schema/ artifacts the TypeScript client uses. One schema, two languages. The py template writes a full PyPI-ready package: __init__.py holding RPC functions and TypedDict definitions, api_client.py, py.typed, schema.json, and pyproject.toml on the hatchling backend. Pick pySrc instead and you get .py files only, ready to drop into an existing project.

Generated methods are static and snake_case; type names are PascalCase. Argument order comes from the generator: body, files for multipart, query, params, then the trailing headers, api_root and disable_client_validation. Procedures with an iteration schema produce a sync generator on the client — pull with next(), drain with list(), stop early with break.

Set clientTemplateDefs.py.outputConfig.origin before generating. Calls go to an absolute URL, so without an origin every call needs api_root, and vovk generate warns about it. The generator is sync and backed by requests; under asyncio you run the stream in a thread. One-off calls to /api/... are outside this skill — plain REST, so curl or httpx covers them.

What it helps with

  • generate a Python package from Vovk API schemas
  • get field autocomplete in the editor via TypedDict
  • consume a JSON Lines stream as a sync generator
  • bake the production URL into the generated client
  • produce a package ready for PyPI

Example prompts for the agent

  • Generate the Python client from the schema with vovk-python into ./dist_python
  • Add the py template to vovk.config.mjs and set the production origin
  • Stream tokens from the complete procedure and collect them into a list

How to install python

1Skills CLI (Vercel)

npx skills add finom/vovk --skill python -a claude-code

2Manually: put the files in place

~/.claude/skills/python/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/python/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about python

How do I install the python skill in Claude Code?

Run npx skills add finom/vovk --skill python -a claude-code. The skill file lands at ~/.claude/skills/python/SKILL.md.

What does the python skill do?

It generates a typed Python package from .vovk-schema/ artifacts: RPC functions, TypedDict types, an HTTP client and build files. The pySrc template emits source files only, for dropping into an existing project.

How is this different from calling the API with requests?

A single call to /api/... is easier with curl or httpx, since Vovk endpoints are plain REST. Reach for the skill when you want generated typed client code.

Source text

SKILL.md · MIT
---
name: python
description: The Vovk.ts Python client — generating a typed Python package from a Vovk API with `vovk-python`, the `py` / `pySrc` templates, `TypedDict` shapes, JSON + JSON Lines consumption, client-side validation, and PyPI publishing. Use whenever the user wants a typed Python SDK, ongoing Python integration, or PyPI publishing — phrasings like "generate a Python client / SDK", "ship a Python package for my API", "use vovk-python", "pySrc template", "python client streaming", "publish to PyPI". Does NOT cover controller / procedure authoring → hand off to `procedure` skill. Does NOT cover `vovk bundle` in general → hand off to `bundle` skill. Does NOT cover JSON Lines server-side → hand off to `jsonlines` skill. **Does NOT cover one-off HTTP calls** — Vovk endpoints are plain REST, so a `curl`, `httpx`, or `requests` call against `/api/...` is the simpler answer for "I just want to hit my API from a Python script / CLI / shell" use cases. Reach for this skill only when the user actually wants generated typed client code.
---

# Vovk.ts Python client

`vovk-python` generates typed Python package from same `.vovk-schema/` artifacts driving TS client. One source of truth, multi-language.

## Install + generate

```bash
npm i -D vovk-python
```

### Standalone package

Emits complete Python package (PyPI-ready). Canonical output dir `./dist_python` (hello-world convention):

```bash
npx vovk generate --from py --out ./dist_python
```

Output:

```
dist_python/
  src/<package_name>/__init__.py    # RPC functions + TypedDict definitions
  src/<package_name>/api_client.py  # HTTP client
  src/<package_name>/py.typed       # Type-hint marker
  src/<package_name>/schema.json    # Generated schema
  pyproject.toml                    # hatchling build backend
  setup.cfg
  README.md
```

### Embedded source

Emits `.py` files only — drop into existing Python project:

```bash
npx vovk generate --from pySrc --out ./python_src
```

## Auto-generate with composed client

Wire Python into `vovk generate` → runs alongside TS:

```ts
// vovk.config.mjs
const config = {
  composedClient: {
    fromTemplates: ['ts', 'py'],
  },
};
export default config;
```

**Bake prod API URL** via `clientTemplateDefs.py.outputConfig.origin` — generated client uses this by default. Required: client sends calls to an absolute URL, so without an origin every call needs `api_root`, and `vovk generate` warns. Pattern from hello-world:

```ts
// vovk.config.js
const PROD_ORIGIN = 'https://hello-world.vovk.dev';

const config = {
  composedClient: { fromTemplates: ['ts', 'py'] },
  clientTemplateDefs: {
    py: {
      extends: 'py',
      outputConfig: { origin: PROD_ORIGIN },
      // composedClient: { outDir: './dist_python' }, // override output dir if needed
    },
  },
};
```

After this, every `npx vovk generate` (+ every `vovk dev` regen) refreshes Python client — `PROD_ORIGIN` baked in for consumers, overridable per-call via `api_root`.

## Generated call shape

Types follow `[PascalCaseMethodName][Body|Query|Params|Output]` as `TypedDict`. Methods static, snake_case. Positional arg order: the generator only emits slots where validation declared, in this order: `body`, `files` (multipart only, sits between `body` and `query`), `query`, `params`, then always-present trailing kwargs `headers`, `api_root`, `disable_client_validation`. After them, optional: `body` for a POST, PUT or PATCH without a body schema, `query` without a query schema, and `files` for a multipart record body.

```python
from my_api_client import UserRPC  # whatever package the generator wrote

body: UserRPC.UpdateUserBody = {
    "email": "[email protected]",
    "profile": {"name": "John Doe", "age": 25},
}
query: UserRPC.UpdateUserQuery = {"notify": "email"}
params: UserRPC.UpdateUserParams = {
    "id": "123e4567-e89b-12d3-a456-426614174000",
}

# Keyword args work for clarity at call sites:
response = UserRPC.update_user(body=body, query=query, params=params)
```

Method names snake_case. Type names PascalCase. Names alike in snake_case (`getUserByID`, `getUserById`) get the first free suffix, per RPC class in schema order: `get_user_by_id`, `get_user_by_id_2`.

### Multipart uploads via `files`

Procedures with `multipart/form-data` accept `files` kwarg — pass `Dict[str, Any]` matching `requests` upload format:

```python
with open("avatar.png", "rb") as f:
    response = UserRPC.upload_avatar(
        params={"id": user_id},
        files={"avatar": ("avatar.png", f, "image/png")},
    )
```

## JSON Lines streaming

Procedures with `iteration` schemas (server-side `async function*` handlers — see **`jsonlines`** skill) generate **sync Python generators** client-side. Return type `Generator[<MethodName>Iteration, None, None]`. Generator only emits slots for validation declared on procedure — for an iteration-only endpoint with no body / query / params, signature is just trailing kwargs:

```python
@staticmethod
def stream_tokens(
    headers: Optional[Dict[str, str]] = None,
    api_root: Optional[str] = None,
    disable_client_validation: bool = False,
) -> Generator[StreamTokensIteration, None, None]:
    ...
```

Add `body: StreamTokensBody`, `query: StreamTokensQuery`, `params: StreamTokensParams` to the signature only when the procedure declares validation for them (same rule as JSON endpoints).

Yielded items typed against procedure's `iteration` schema — `StreamTokensIteration` is `TypedDict` → editors autocomplete fields.

### Basic consumption

```python
from my_api_client import StreamRPC

for item in StreamRPC.stream_tokens():
    print(item["message"], end="", flush=True)
```

### Standard generator patterns

Sync generators support usual idioms:

```python
# Manual pull
stream = StreamRPC.stream_tokens()
first = next(stream)
second = next(stream)

# Collect into a list (drains the stream)
all_items = list(StreamRPC.stream_tokens())

# Comprehensions
messages = [item["message"] for item in StreamRPC.stream_tokens()]

# Early termination — break out of the for-loop
for item in StreamRPC.stream_tokens():
    if item["message"] == "STOP":
        break
    handle(item)
# The underlying HTTP connection closes when the generator goes out of scope.
```

### With body / query / params

Streaming endpoints accept same input shape as JSON:

```python
from my_api_client import ChatRPC

for token in ChatRPC.complete(
    body={"messages": [{"role": "user", "content": "hello"}]},
    query={"model": "gpt-4"},
):
    print(token["text"], end="", flush=True)
```

### Error handling

HTTP fail or iteration validation fail → generator raises on next `next()`. Wrap in `try/except` to recover:

```python
try:
    for item in StreamRPC.stream_tokens():
        handle(item)
except Exception as exc:
    log.error("stream failed mid-iteration: %s", exc)
```

### Sync, not async

Generator **sync** — backed by `requests`, no async API. For asyncio, run iteration in thread pool (`asyncio.to_thread` or `loop.run_in_executor`) or wrap each `next()` call. Native async streaming not in current Python client.

See **`jsonlines`** skill for server-side handler.

## Client-side validation

Validation runs against `schema.json` by default — same Zod/Valibot shapes server enforces, cross-compiled to JSON Schema.

Skip for hot paths or server-only enforcement:

```python
response = UserRPC.update_user(
    params=params, body=body, query=query,
    disable_client_validation=True,
)
```

## Dependencies pulled in

Generated `pyproject.toml` declares (per hello-world):

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
requires-python = ">=3.9"
dependencies = ["requests", "jsonschema[format-nongpl]", "typing_extensions>=4.0.0; python_version < '3.12'"]
```

- `requests` — HTTP client; picks its own `urllib3`.
- `jsonschema[format-nongpl]` — client-side validation; the extra checks formats such as `uri` and `date-time` without GPL libraries. A `pattern` Python's `re` can't compile (`\p{L}`) is left to the server.
- `typing_extensions` — `NotRequired` and `TypeAlias` before Python 3.11.

Build backend **hatchling**, no runtime overhead past deps above.

## Auth + base URL

Generated client targets URL baked at generate time. Override per call via `api_root` kwarg — fully replaces baked-in value for that call. Auth: pass `headers` per call, or set them once on the module-level `client`, whose `session` every call shares:

```py
from my_api import client

client.session.headers["Authorization"] = "Bearer ..."
client.timeout = 30  # seconds, or a (connect, read) tuple; None waits forever
```

**Don't bake secrets into generated package.** Consumers supply API keys at runtime.

## PyPI publishing

Canonical script from hello-world (build wheel + sdist, upload via `twine`):

```bash
python3 -m build ./dist_python --wheel --sdist && python3 -m twine upload ./dist_python/dist/*
```

Both `python3 -m build` and `python3 -m twine` invoke modules directly — works with any Python env that has them installed (`pip install build twine`). Run `twine` with creds via `~/.pypirc` or `TWINE_USERNAME` / `TWINE_PASSWORD` env vars.

Wire into release flow alongside npm bundle + Rust crate (hello-world chains all three under `postversion`):

```json
"scripts": {
  "publish:node":   "npm publish ./dist",
  "publish:rust":   "cargo publish --manifest-path dist_rust/Cargo.toml --allow-dirty",
  "publish:python": "python3 -m build ./dist_python --wheel --sdist && python3 -m twine upload ./dist_python/dist/*",
  "postversion":    "vovk generate && vovk bundle && npm run publish:node && npm run publish:rust && npm run publish:python"
}
```

Version / package name flow from root `package.json` (generator copies whitelisted fields — `name`, `version`, `description`, `license`, `author`, `contributors`, `repository`, `homepage`, `bugs`, `keywords` — into generated `pyproject.toml`). Set `version` in root `package.json`, `npm version patch` propagates to all three targets.

**Name transform — pin before publishing.** Python package name = root `name` with the scope's `@` dropped and every other character Python can't import → underscore (`my-api` → `my_api`, `@acme/web-app` → `acme_web_app`). Same name used for filesystem path (`src/<name>/`), `pyproject.toml` `name`, `pip install` command in README. Set a different distribution name than npm (e.g., npm `my-api`, PyPI `acme_client`) with `outputConfig.package.py_name`:

```js
// vovk.config.mjs
const config = {
  composedClient: { fromTemplates: ['ts', 'py'] },
  clientTemplateDefs: {
    py: {
      extends: 'py',
      outputConfig: {
        origin: 'https://api.example.com',
        package: { py_name: 'acme_client' }, // PyPI name + filesystem path
      },
    },
  },
};
```

## Flows

### "Call my Vovk API from a Python script"

1. `npm i -D vovk-python`.
2. Add `'py'` to `composedClient.fromTemplates` in `vovk.config.mjs`.
3. `npx vovk generate`.
4. `pip install -e ./dist_python` (or wherever generator wrote it).
5. `from vovk_hello_world import UserRPC` and call (editable install exposes the package directly — no `dist_python.src.` prefix).

### "Ship a Python SDK to PyPI"

1. `npx vovk generate --from py --out ./python_package`.
2. Set `version` (and `name`) in the root `package.json` before generating; to override them for this package only, use `clientTemplateDefs.py.outputConfig.package` (see above).
3. `python3 -m build && twine upload dist/*` from `python_package/`.

### "Consume a streaming endpoint from Python"

Server defines `procedure({ iteration })` with `async function*` handler (see `jsonlines`). Client iterates:

```python
for item in StreamRPC.stream_tokens():
    handle(item)
```

### "Embed the client into an existing Python project"

Use `pySrc`:

```bash
npx vovk generate --from pySrc --out ./my_project/vovk_client
```

Then import from `my_project.vovk_client...`.

## Gotchas

- **`TypedDict`, not classes.** Generated shapes dict-based for JSON interop; treat as plain dicts with type hints, not Pydantic models.
- **Method names snake_case**, type names PascalCase. `updateUser` server → `update_user(...)` Python, `UpdateUserBody` etc.
- **Client-side validation uses `jsonschema`** — small import + runtime cost. Disable per-call for speed or server-only enforcement.
- **Base URL override is per-call.** `api_root` kwarg overrides the URL baked at generate time. Headers, timeout and cookies go on the module-level `client` (`client.session.headers`, `client.timeout`).
- **Regen on schema changes.** Any procedure / schema change server-side needs `vovk generate` before Python client reflects. CI should regen as part of build.
- **Don't hand-edit generated files.** Put shared Python utilities alongside, not inside, generated package.
- **Named schemas in `components/schemas` don't yet produce shared importable types.** Each call site gets own scoped `TypedDict` instead of single `UserSchema` reused across methods. Roadmap item — until fixed, expect duplicated type definitions.

© finom · MIT · finom/vovk

Similar skills

All skills for Backend and APIs →

python by other authors comes first.

python Mindrally This skill tells your coding agent how to write Python: PEP 8, type hints, early returns, async for I/O. Useful when the agent edits or generates code in a Python repo. CX Codex +1 983installs python pproenca A skill with 42 Python 3.11+ optimization rules: it steers the agent on async code, data structures and memory use. CX Codex +1 315installs python siviter-xyz Gives the agent one set of Python conventions: typing, dependency injection, module and test layout. Useful when edits land in an existing codebase. CX Codex +1 349installs mcp-builder official anthropics This skill walks through building an MCP server in TypeScript or Python, from reading the protocol spec to writing tests and evaluation questions. 130,949installs claude-api official anthropics Reference skill for the Claude API and Anthropic SDK that enforces valid model IDs, adaptive thinking, and streaming. 74,198installs lark-im larksuite This skill lets an agent work with Lark IM messages, chats, files, and interactive cards. Use it when coding tasks need access to team conversations. CX Codex +1 472,900installs lark-event larksuite Listens to Lark/Feishu events and streams them as NDJSON. Use it for bots, message processing, and long-running subscribers. CX Codex +1 466,800installs caveman-discover JuliusBrussee Finds and labels every LLM workflow in your repository so Caveman Cloud breaks down spend by actual job rather than grouping everything into one bucket. CX Codex +1 104,400installs migration JuliusBrussee Enforces reversible, compatibility-safe transitions for schema, data, and API updates. Useful whenever data loss or rollout downtime is unacceptable. CX Codex +1 103,900installs

From the same set5

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor