---
title: "Non-TypeScript Codegen: Go and Python Clients"
canonical: "https://test.abhinandan.one/non-typescript-codegen"
markdown_url: "https://test.abhinandan.one/non-typescript-codegen.md"
publisher: "Primitive SDKs"
kind: "guide"
content_type: "reference"
category: "API Core / OpenAPI Generation"
description: "Go's ogen client and Python's openapi-python-client client both generate from openapi/primitive-api.codegen.json, then each applies its own language-specific fixup pass."
keywords: ["openapi-python-client", "ogen", "generate_api_client.py", "primitive-api.codegen.json", "sdk-go/api", "guard_optional_body_content_type"]
last_modified: "2026-08-11T18:55:08.046059+00:00"
published_at: "2026-08-11T18:55:07.900752+00:00"
source_files:
  - "sdk-python/scripts/generate_api_client.py"
sections:
  - {anchor: "what-each-language-generates-with", title: "What each language generates with"}
  - {anchor: "regenerating-the-go-client", title: "Regenerating the Go client"}
  - {anchor: "step-run-the-go-generate-target", title: "Run the Go generate target"}
  - {anchor: "step-run-the-go-sdks-checks", title: "Run the Go SDK's checks"}
  - {anchor: "step-commit-the-regenerated-output", title: "Commit the regenerated output"}
  - {anchor: "regenerating-the-python-client", title: "Regenerating the Python client"}
  - {anchor: "step-run-the-python-generate-target", title: "Run the Python generate target"}
  - {anchor: "step-run-the-python-sdks-checks", title: "Run the Python SDK's checks"}
  - {anchor: "step-commit-the-regenerated-output-1", title: "Commit the regenerated output"}
  - {anchor: "pythons-three-fixups", title: "Python's three fixups"}
  - {anchor: "deduplicate-imports", title: "Deduplicate imports"}
  - {anchor: "guard-the-content-type-header-on-optional-body-operations", title: "Guard the Content-Type header on optional-body operations"}
  - {anchor: "use-raw-bytes-for-filedownload-responses", title: "Use raw bytes for file/download responses"}
  - {anchor: "verifying-the-regeneration-worked", title: "Verifying the regeneration worked"}
  - {anchor: "next-steps", title: "Next steps"}
---

> Documentation index: https://test.abhinandan.one/llms.txt

# Non-TypeScript Codegen: Go and Python Clients

Shows how the Go SDK's ogen client and the Python SDK's openapi-python-client client are both generated from the same normalized OpenAPI spec, and what post-processing each language applies before the output ships.

The Go SDK and Python SDK don't hand-write their API clients. Both generate from the same normalized OpenAPI document that [OpenAPI Spec Normalization and Codegen Artifacts](https://test.abhinandan.one/openapi-spec-normalization.md) produces, using an off-the-shelf generator for their language, then run a small fixup pass that repairs generator quirks the platform's wire contract doesn't tolerate.

Reach for this page when you're regenerating the Go or Python client after an `openapi/primitive-api.yaml` change, or debugging why generated code in `sdk-go/api` or `sdk-python/src/primitive/api` looks different from what you expected.

## What each language generates with

Go generates its client with ogen into `sdk-go/api`, and Python generates with `openapi-python-client` into `sdk-python/src/primitive/api`. Both generators consume `openapi/primitive-api.codegen.json`, the OpenAPI 3.0.3 build artifact normalized from the hand-written 3.1 spec (never edit that JSON file directly; see [OpenAPI Spec Normalization and Codegen Artifacts](https://test.abhinandan.one/openapi-spec-normalization.md)).

| SDK | Generator | Output location | Client type |
| --- | --- | --- | --- |
| Go | [ogen](https://github.com/ogen-go/ogen) | `sdk-go/api` | Generated Go package, imported as `primitiveapi` |
| Python | [openapi-python-client](https://github.com/openapi-generators/openapi-python-client) | `sdk-python/src/primitive/api` | Generated Python package, imported as `primitive.api` |

The Node SDK's counterpart is the workspace-internal `@primitivedotdev/api-core` package (see [What is API Core?](https://test.abhinandan.one/api-core-overview.md)); Go and Python have no equivalent shared package, so each generates and post-processes its client directly inside its own SDK directory.

> **Tip:** For day-to-day app code, use the high-level `client.send`/`reply`/`forward` surface, not the generated client directly. Reach for the generated client only for operations the high-level client doesn't cover, see [Generated API Client](https://test.abhinandan.one/python-generated-api-client.md) for the Python surface.

## Regenerating the Go client

Run `make go-generate` from the repo root, then `make go-check` and commit the regenerated `sdk-go/api` files with your spec change.

### 1. Run the Go generate target

From the repo root:

```bash
make go-generate
```

This regenerates `sdk-go/api` from `openapi/primitive-api.codegen.json` using ogen.

### 2. Run the Go SDK's checks

```bash
make go-check
```

The Go SDK's own checks are `go test ./...`, the shared-fixture run `go test -run TestSharedCompatibilityFixtures ./...`, and `gofmt -w .`. See [Go SDK Testing and Development](https://test.abhinandan.one/go-sdk-development.md) for the full local workflow.

### 3. Commit the regenerated output

Commit the changed files under `sdk-go/api` alongside your spec change. ogen's output is deterministic for a given spec, so a diff here should map directly to your OpenAPI change.

## Regenerating the Python client

Run `make python-generate` from the repo root; it invokes `sdk-python/scripts/generate_api_client.py`, which regenerates `sdk-python/src/primitive/api` and applies three fixups.

Python's generation script, `sdk-python/scripts/generate_api_client.py`, wraps the `openapi-python-client` CLI and then applies three fixups the raw generator output needs before it's usable.

### 1. Run the Python generate target

From the repo root:

```bash
make python-generate
```

Under the hood this invokes `generate_api_client.py`, which:

1. Runs `openapi-python-client generate --meta none --config openapi-python-client-config.yml --path openapi/primitive-api.codegen.json --output-path <temp dir>` from the `sdk-python` directory.
2. Deletes the previous generated `api/`, `client.py`, `errors.py`, `models/`, and `types.py` under `sdk-python/src/primitive/api`.
3. Copies the freshly generated versions into their place.
4. Applies the three post-processing fixups described below.

### 2. Run the Python SDK's checks

```bash
make python-check
```

The Python SDK's own checks are `uv run pytest`, `uv run ruff check .`, and `uv run basedpyright`.

### 3. Commit the regenerated output

Commit the changed files under `sdk-python/src/primitive/api` alongside your spec change.

## Python's three fixups

The three fixups deduplicate repeated imports, guard the `Content-Type` header on optional-body operations, and switch file responses to raw bytes. `generate_api_client.py` runs each of these over every generated `.py` file after copying, in this order.

### Deduplicate imports

`openapi-python-client` 0.28.3 occasionally emits the same `from X import ...` line twice, for example `from ..types import UNSET, Unset` inside a 201-response model. `dedupe_imports` strips repeated module-level import lines (matched by a regex that only touches unindented `from ... import ...` lines, so imports inside `TYPE_CHECKING` blocks or function bodies are left alone). The duplicates are semantically harmless but trip strict linters downstream.

### Guard the Content-Type header on optional-body operations

`openapi-python-client` 0.28.3 unconditionally emits:

```python
headers["Content-Type"] = "application/json"
```

even for operations whose request body is optional. When a caller omits the body, the generated code still sends `Content-Type: application/json` with no body, which trips middleware expecting either both or neither. `guard_optional_body_content_type` re-indents that assignment so it fires only inside the existing `if not isinstance(body, Unset):` block, matching this generator pattern exactly:

```python
if not isinstance(body, Unset):
    _kwargs["json"] = body.to_dict()

headers["Content-Type"] = "application/json"
```

becomes:

```python
if not isinstance(body, Unset):
    _kwargs["json"] = body.to_dict()
    headers["Content-Type"] = "application/json"
```

This is the Python-side counterpart to the same class of bug the Node SDK fixes in generated TypeScript, see [Generated TypeScript Client Fixups](https://test.abhinandan.one/typescript-client-fixups.md), and it's also the failure mode covered in [Codegen Troubleshooting](https://test.abhinandan.one/codegen-troubleshooting.md) for mismatched Content-Type headers on optional-body requests.

### Use raw bytes for file/download responses

File payloads wrap `BytesIO`, which requires `bytes`, not `str`. `use_bytes_for_file_responses` replaces every occurrence of `BytesIO(response.text)` with `BytesIO(response.content)` across the generated tree, so binary downloads (raw email `.eml` bytes, attachment bundles) decode correctly instead of being mangled through text decoding first.

> **Warning:** Never hand-edit files under `sdk-python/src/primitive/api` or `sdk-go/api`. They're regenerated wholesale on every `make python-generate` / `make go-generate` run, and manual edits are silently discarded. Fix the spec, the generator config, or the fixup script instead.

## Verifying the regeneration worked

Build and test each regenerated SDK, and confirm both languages show a matching diff for the same spec change. After regenerating either client, confirm:

- **Go**: `go build ./...` succeeds from `sdk-go/`, and `go test -run TestSharedCompatibilityFixtures ./...` passes.
- **Python**: `uv run basedpyright` reports no new type errors, and `uv run pytest` passes.

A spec change that adds or removes an operation should show up as a corresponding diff in both `sdk-go/api` and `sdk-python/src/primitive/api`. If only one language's output changed, the other generator run was likely skipped.
