{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/non-typescript-codegen","markdown_url":"https://test.abhinandan.one/non-typescript-codegen.md","article":{"id":"c23d95d6-89dc-4a09-9b84-e91225c4810e","article_slug":"non-typescript-codegen","parent_article_slug":null,"parent_article_title":null,"kind":"guide","published_at":"2026-08-11T18:55:07.900752+00:00","keywords":["openapi-python-client","ogen","generate_api_client.py","primitive-api.codegen.json","sdk-go/api","guard_optional_body_content_type"],"meta_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.","og_image_url":null,"source_file_paths":["sdk-python/scripts/generate_api_client.py"],"recording_id":null,"replayable":false,"task_name":"Non-TypeScript Codegen: Go and Python Clients","category":"API Core / OpenAPI Generation","summary":null,"description":"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.","content_kind":"repo_page","content_markdown":"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](openapi-spec-normalization) 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.\n\nReach 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.\n\n## What each language generates with\n\nGo 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](openapi-spec-normalization)).\n\n| SDK | Generator | Output location | Client type |\n| --- | --- | --- | --- |\n| Go | [ogen](https://github.com/ogen-go/ogen) | `sdk-go/api` | Generated Go package, imported as `primitiveapi` |\n| Python | [openapi-python-client](https://github.com/openapi-generators/openapi-python-client) | `sdk-python/src/primitive/api` | Generated Python package, imported as `primitive.api` |\n\nThe Node SDK's counterpart is the workspace-internal `@primitivedotdev/api-core` package (see [What is API Core?](api-core-overview)); Go and Python have no equivalent shared package, so each generates and post-processes its client directly inside its own SDK directory.\n\n<Tip>\n\nFor 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](python-generated-api-client) for the Python surface.\n\n</Tip>\n\n## Regenerating the Go client\n\nRun `make go-generate` from the repo root, then `make go-check` and commit the regenerated `sdk-go/api` files with your spec change.\n\n<Steps>\n\n<Step title=\"Run the Go generate target\">\n\nFrom the repo root:\n\n```bash\nmake go-generate\n```\n\nThis regenerates `sdk-go/api` from `openapi/primitive-api.codegen.json` using ogen.\n\n</Step>\n\n<Step title=\"Run the Go SDK's checks\">\n\n```bash\nmake go-check\n```\n\nThe 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](go-sdk-development) for the full local workflow.\n\n</Step>\n\n<Step title=\"Commit the regenerated output\">\n\nCommit 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.\n\n</Step>\n\n</Steps>\n\n## Regenerating the Python client\n\nRun `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.\n\nPython'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.\n\n<Steps>\n\n<Step title=\"Run the Python generate target\">\n\nFrom the repo root:\n\n```bash\nmake python-generate\n```\n\nUnder the hood this invokes `generate_api_client.py`, which:\n\n1. 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.\n2. Deletes the previous generated `api/`, `client.py`, `errors.py`, `models/`, and `types.py` under `sdk-python/src/primitive/api`.\n3. Copies the freshly generated versions into their place.\n4. Applies the three post-processing fixups described below.\n\n</Step>\n\n<Step title=\"Run the Python SDK's checks\">\n\n```bash\nmake python-check\n```\n\nThe Python SDK's own checks are `uv run pytest`, `uv run ruff check .`, and `uv run basedpyright`.\n\n</Step>\n\n<Step title=\"Commit the regenerated output\">\n\nCommit the changed files under `sdk-python/src/primitive/api` alongside your spec change.\n\n</Step>\n\n</Steps>\n\n## Python's three fixups\n\nThe 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.\n\n### Deduplicate imports\n\n`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.\n\n### Guard the Content-Type header on optional-body operations\n\n`openapi-python-client` 0.28.3 unconditionally emits:\n\n```python\nheaders[\"Content-Type\"] = \"application/json\"\n```\n\neven 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:\n\n```python\nif not isinstance(body, Unset):\n    _kwargs[\"json\"] = body.to_dict()\n\nheaders[\"Content-Type\"] = \"application/json\"\n```\n\nbecomes:\n\n```python\nif not isinstance(body, Unset):\n    _kwargs[\"json\"] = body.to_dict()\n    headers[\"Content-Type\"] = \"application/json\"\n```\n\nThis is the Python-side counterpart to the same class of bug the Node SDK fixes in generated TypeScript, see [Generated TypeScript Client Fixups](typescript-client-fixups), and it's also the failure mode covered in [Codegen Troubleshooting](codegen-troubleshooting) for mismatched Content-Type headers on optional-body requests.\n\n### Use raw bytes for file/download responses\n\nFile 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.\n\n<Warning>\n\nNever 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.\n\n</Warning>\n\n## Verifying the regeneration worked\n\nBuild and test each regenerated SDK, and confirm both languages show a matching diff for the same spec change. After regenerating either client, confirm:\n\n- **Go**: `go build ./...` succeeds from `sdk-go/`, and `go test -run TestSharedCompatibilityFixtures ./...` passes.\n- **Python**: `uv run basedpyright` reports no new type errors, and `uv run pytest` passes.\n\nA 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.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"OpenAPI Spec Normalization and Codegen Artifacts\" href=\"openapi-spec-normalization\">\n\nSee how the source 3.1 YAML becomes the 3.0.3 JSON both generators consume.\n\n</Card>\n\n<Card title=\"Regenerating SDK Code from the OpenAPI Spec\" href=\"codegen-workflow\">\n\nRun the full end-to-end pipeline across all three SDK languages in one pass.\n\n</Card>\n\n<Card title=\"Codegen Troubleshooting\" href=\"codegen-troubleshooting\">\n\nDiagnose schema drift, stale imports, and Content-Type mismatches after a regeneration.\n\n</Card>\n\n<Card title=\"Go SDK Testing and Development\" href=\"go-sdk-development\">\n\nRun the Go SDK's test suite and shared compatibility fixtures locally.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-python/scripts/generate_api_client.py","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Non-TypeScript+Codegen%3A+Go+and+Python+Clients&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fnon-typescript-codegen","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}