Skip to content

API Client Maintenance

Eric Fitzgerald edited this page Apr 6, 2026 · 2 revisions

API Client Maintenance

This page covers the maintenance, operations, and release processes for the TMI auto-generated API client libraries. For end-user installation and usage, see API-Clients.

Overview

The TMI project maintains three auto-generated REST client libraries:

Client Package Name Registry Generator Models
Python tmi-client PyPI openapi-generator 7.x Pydantic v2
TypeScript @tmiclient/client npm openapi-generator 7.x TypeScript interfaces
Go tmiclient Go modules (git) swagger-codegen 3.0.75 Go structs

All clients are generated from the TMI OpenAPI specification hosted at:

https://raw.githubusercontent.com/ericfitz/tmi/refs/heads/{branch}/api-schema/tmi-openapi.json

Repository: github.com/ericfitz/tmi-clients

Repository Structure

tmi-clients/
├── regenerate_python.py                          # Python client regeneration script
├── regenerate_go.py                              # Go client regeneration script
├── regenerate_ts.py                              # TypeScript client regeneration script
├── regen_common.py                               # Shared utilities for all regeneration scripts
├── release_python.sh                             # Python release automation script
├── .github/workflows/
│   ├── ci.yml                                    # CI for all three clients
│   ├── publish-python.yml                        # PyPI publishing (tag: python-v*)
│   ├── publish-go.yml                            # Go validation (tag: go-v*)
│   └── publish-js.yml                            # npm publishing (tag: ts-v*)
├── python-client-generated/
│   ├── scripts/openapi-generator-config.json     # Codegen configuration
│   ├── tmi_client/                               # Package source (Pydantic v2 models)
│   │   ├── api/                                  # API endpoint classes
│   │   └── models/                               # Data model classes
│   ├── test/                                     # Auto-generated tests
│   ├── docs/                                     # Auto-generated API docs
│   │   └── developer/                            # Developer docs (migration guide, changelog)
│   ├── test_diagram_fixes.py                     # Integration test (custom, preserved across regeneration)
│   ├── pyproject.toml                            # Python packaging (uv compatible)
│   └── setup.py                                  # Legacy packaging (kept in sync)
├── go-client-generated/
│   ├── scripts/swagger-codegen-config.json       # Codegen configuration
│   ├── *.go                                      # Generated Go source files
│   ├── go.mod                                    # Go module definition
│   └── docs/                                     # Auto-generated docs
└── typescript-client-generated/
    ├── scripts/openapi-generator-config.json      # Codegen configuration
    ├── src/
    │   ├── apis/                                  # API classes
    │   └── models/                                # TypeScript interfaces
    ├── package.json                               # npm package definition
    └── tsconfig.json                              # TypeScript compiler configuration

Versioning Strategy

Client versions track the info.version field in the OpenAPI specification. The regeneration scripts automatically extract this version from the spec and stamp it into all relevant configuration files.

How Version Stamping Works

  1. The regeneration script downloads the OpenAPI spec (or reads a local copy).
  2. It extracts the version from info.version (for example, 1.3.0).
  3. It updates the codegen config file (packageVersion or npmVersion field).
  4. After code generation, it stamps the version into package files:
    • Python: pyproject.toml (version = "X.Y.Z") and setup.py (VERSION = "X.Y.Z")
    • TypeScript: package.json ("version": "X.Y.Z")
    • Go: codegen config (packageVersion)

Spec Branches and Versions

The OpenAPI spec lives on multiple branches of the ericfitz/tmi repository:

  • main -- current stable spec (currently version 1.3.0)
  • dev/1.4.0 -- next development spec (version 1.4.0)

Release Tags

Each client is tagged and released independently using per-client tag prefixes:

  • Python: python-vX.Y.Z
  • TypeScript: ts-vX.Y.Z
  • Go: go-vX.Y.Z

This allows you to release one client without affecting the others.

Regenerating Clients

Prerequisites

Install the following tools before regenerating any client:

Tool Required For Install Command
openapi-generator Python, TypeScript brew install openapi-generator
swagger-codegen Go brew install swagger-codegen
uv Python brew install uv
go (1.21+) Go brew install go
node (18+) TypeScript brew install node

Running the Regeneration Scripts

All three scripts follow the same CLI pattern:

# Regenerate from the main branch spec (default)
python3 regenerate_python.py
python3 regenerate_go.py
python3 regenerate_ts.py

# Regenerate from a specific branch
python3 regenerate_python.py --branch dev/1.4.0
python3 regenerate_go.py --branch dev/1.4.0
python3 regenerate_ts.py --branch dev/1.4.0

# Regenerate from a local spec file
python3 regenerate_python.py /path/to/tmi-openapi.json
python3 regenerate_go.py /path/to/tmi-openapi.json
python3 regenerate_ts.py /path/to/tmi-openapi.json

Regeneration Workflow

Every regeneration script follows the same sequence of steps. Understanding this workflow is essential for debugging regeneration failures.

  1. Check prerequisites -- verify that the required codegen tool and language runtime are installed and meet minimum version requirements.
  2. Download spec -- fetch the OpenAPI spec from GitHub (or copy a local file). Extract the info.version field and update the codegen config file.
  3. Backup custom files -- copy files that must survive regeneration (integration tests, ignore files, developer docs) to a temporary backup directory.
  4. Clean generated files -- delete all previously generated source, docs, and config files. Custom directories (such as docs/developer/) are preserved, either by backup/restore or by excluding them from the clean step.
  5. Run code generation -- invoke openapi-generator or swagger-codegen with the language-specific config file.
  6. Stamp version -- write the spec version into generated package files (pyproject.toml, package.json, etc.).
  7. Apply patches -- fix known codegen bugs automatically (see "Codegen Bug Patches" below).
  8. Restore custom files -- copy backed-up files back into the client directory.
  9. Install dependencies -- run the language-specific dependency install (uv pip install, npm install, or go mod tidy).
  10. Run tests -- execute the generated test suite and any integration tests.
  11. Generate report -- write a REGENERATION_REPORT.md with file counts, build status, and test results.
  12. Clean up -- remove the temporary backup directory.

Exit Codes

Code Meaning
0 Success -- codegen, patches, and tests all passed
1 Fatal error -- code generation itself failed (prerequisites missing, spec download failed, codegen crashed)
2 Completed with issues -- codegen succeeded but patches had warnings or tests failed

After regeneration, always review the generated REGENERATION_REPORT.md and check test_output.log (or build_output.log) for details on any issues.

Codegen Bug Patches

Each regeneration script includes automatic patches for known bugs in the code generators. These patches are applied after every regeneration to ensure the generated code compiles and behaves correctly.

Python Patches

1. Regex Validator Fix

Bug: openapi-generator generates @field_validator functions that call re.match() on UUID and datetime fields. However, Pydantic v2 parses these values into native Python types (uuid.UUID, datetime.datetime) before the validator runs. Calling re.match() on a non-string type raises a TypeError.

Fix: Insert a string conversion line before the re.match() call:

value = value.isoformat() if hasattr(value, 'isoformat') else str(value)

Affected files: tmi_client/models/*.py (any model with UUID or datetime fields that have regex patterns in the spec)

Implementation: patch_regex_validators() in regenerate_python.py

2. Test Return-Type Fix

Bug: openapi-generator generates test stubs with def make_instance(self, include_optional) -> ModelType: but the method body is entirely commented out, so the function always returns None. This causes type-checker errors (ty, mypy) because the return type annotation does not match the actual return value.

Fix: Change the return type annotation to -> None.

Affected files: test/*.py

Implementation: patch_test_return_types() in regenerate_python.py

3. urllib3 CVE Patch

Bug: openapi-generator defaults to requiring urllib3 >= 2.1.0, but versions before 2.6.3 have HIGH-severity CVEs (decompression-bomb bypass, unbounded decompression chain).

Fix: Bump the minimum urllib3 version to >= 2.6.3 in all dependency files.

Affected files: pyproject.toml, setup.py, requirements.txt

Implementation: patch_urllib3_minimum_version() in regenerate_python.py

Go Patches

1. RevokeToken Form Params (CRITICAL)

Bug: swagger-codegen generates the RevokeToken function with a JSON body parameter (localVarPostBody = &body), but the OAuth 2.0 token revocation spec (RFC 7009) requires form-encoded parameters (application/x-www-form-urlencoded). Without this patch, token revocation is completely broken.

Fix: Replace the body parameter assignment with individual form parameter additions:

// Before (broken):
localVarPostBody = &body

// After (correct):
localVarFormParams.Add("token", parameterToString(token, ""))
localVarFormParams.Add("token_type_hint", parameterToString(tokenTypeHint, ""))
if clientId != "" {
    localVarFormParams.Add("client_id", parameterToString(clientId, ""))
}
if clientSecret != "" {
    localVarFormParams.Add("client_secret", parameterToString(clientSecret, ""))
}

Affected files: api_authentication.go

Implementation: Exact string replacement in main() of regenerate_go.py

Severity: CRITICAL -- without this patch, token revocation does not work.

TypeScript Patches

1. Optional-Extends Fix

Bug: openapi-generator marks inherited required properties as optional in child interfaces when using allOf inheritance. For example, DfdDiagram extends BaseDiagram generates type?: DfdDiagramTypeEnum instead of type: DfdDiagramTypeEnum. This causes TypeScript error TS2430 (interface incorrectly extends interface).

Fix: Remove the ? from affected property declarations to make them required, matching the parent interface.

Affected pairs:

  • DfdDiagram extends BaseDiagram (property: type)
  • DfdDiagramInput extends BaseDiagramInput (property: type)
  • Edge extends Cell (property: shape)
  • Node extends Cell (property: shape)

Affected files: src/models/DfdDiagram.ts, src/models/DfdDiagramInput.ts, src/models/Edge.ts, src/models/Node.ts

Implementation: patch_optional_extends() in regenerate_ts.py

2. TokenRequest Missing Model

Bug: openapi-generator generates imports for TokenRequest, TokenRequestFromJSON, and TokenRequestToJSON in AuthenticationApi.ts, but never generates the TokenRequest model file. This happens because the OpenAPI spec uses application/x-www-form-urlencoded for the token endpoint, and the generator creates a reference to a model it never produces.

Fix: Remove the TokenRequest imports and replace TokenRequestToJSON(body) calls with body as any.

Affected files: src/apis/AuthenticationApi.ts

Implementation: patch_missing_token_request() in regenerate_ts.py

Adding New Patches

When you discover a new codegen bug, follow this procedure to add an automatic patch that survives future regeneration runs.

Step 1: Identify the Bug

Regenerate the client and inspect the build or test output:

python3 regenerate_python.py
# Check: REGENERATION_REPORT.md, test_output.log, build_output.log

Step 2: Write a Patch Function

Add a new function to the appropriate regenerate_*.py script. Follow the existing pattern:

def patch_your_fix(had_issues: bool) -> bool:
    """Describe the bug, why it happens, and what the fix does.

    Include affected files and any relevant context.
    """
    # Use helpers from regen_common for simple cases:
    patch_file_regex(filepath, pattern, replacement, "description")
    patch_file_exact(filepath, old_text, new_text, "description")

    # Or write custom logic for complex multi-file patches
    return had_issues

The regen_common module provides two general-purpose patching helpers:

  • patch_file_regex(file, pattern, replacement, description) -- apply a regex substitution. Returns True if any replacements were made.
  • patch_file_exact(file, old_text, new_text, description) -- replace an exact string. Returns True if the replacement was made.

Both helpers print warnings (instead of failing) when the file or pattern is not found, which makes patches resilient to future codegen output changes.

Step 3: Register the Patch

Add the function call in the "Apply patches" section of the script's main() function:

had_issues = patch_your_fix(had_issues)

Step 4: Verify

Regenerate the client and confirm the patch applies cleanly:

python3 regenerate_python.py
# Exit code 0 = success

Step 5: Document

Update the patch function's docstring with a clear explanation. Then update this wiki page with the new patch details under the appropriate language section.

Publishing Clients

Python (PyPI)

The Python client uses PyPI trusted publishing (OIDC) -- no API tokens are stored in GitHub secrets.

Using the Release Script

# Validate the release (build, check, test -- but do not publish)
./release_python.sh --dry-run

# Publish (build, test, tag, push tag, create GitHub Release)
./release_python.sh

# Override the version (instead of reading from pyproject.toml)
./release_python.sh 1.4.0

The release_python.sh script performs the following steps:

  1. Reads the version from python-client-generated/pyproject.toml (or uses the version you provide).
  2. Checks for uncommitted changes and verifies the tag does not already exist.
  3. Builds the package with uv build and validates it with twine check.
  4. Runs the test suite.
  5. Creates an annotated git tag python-vX.Y.Z and pushes it.
  6. Creates a GitHub Release with the built distribution files attached.

Automated Publishing

The publish-python.yml workflow triggers automatically when a GitHub Release is published with a python-v* tag. It:

  1. Verifies the package version matches the tag version.
  2. Runs the test suite.
  3. Builds the package.
  4. Publishes to PyPI using trusted publishing (OIDC, no tokens).

Requirements:

  • A pypi GitHub environment must be configured on the repository.
  • A PyPI pending publisher must be set up for the tmi-client package, linked to this repository and the publish-python.yml workflow.

TypeScript (npm)

Release Process

  1. Ensure the version in typescript-client-generated/package.json is correct (the regeneration script sets this from the spec).
  2. Create an annotated git tag and push it:
    git tag -a ts-v1.3.0 -m "Release TypeScript client v1.3.0"
    git push origin ts-v1.3.0
  3. Create a GitHub Release targeting the ts-v1.3.0 tag.

Automated Publishing

The publish-js.yml workflow triggers on the Release event for ts-v* tags. It:

  1. Installs dependencies (npm install).
  2. Builds the TypeScript project (npm run build).
  3. Runs tests (npm test).
  4. Publishes to npm with provenance (npm publish --provenance --access public).

Requirements:

  • An npm GitHub environment must be configured on the repository.
  • An NPM_TOKEN secret must be set in the npm environment with publish permissions for the @tmiclient/client package.

Go (Go Modules)

Go modules are served directly from git tags -- there is no registry upload step.

Release Process

  1. Ensure the code builds and tests pass:
    cd go-client-generated
    go build ./...
    go test -v ./...
  2. Create an annotated git tag and push it:
    git tag -a go-v1.3.0 -m "Release Go client v1.3.0"
    git push origin go-v1.3.0
  3. Create a GitHub Release targeting the go-v1.3.0 tag.

The publish-go.yml workflow runs on the Release event for go-v* tags and validates (build + test) but does not upload anything. Users install the Go client directly from the git tag:

go get github.com/ericfitz/tmi-clients/go-client-generated@v1.3.0

Updating Dependencies

Python

Edit both pyproject.toml and setup.py in python-client-generated/ -- these two files must stay in sync. After editing, verify the build:

cd python-client-generated
uv build
uv run --with pytest python3 -m pytest test/ -v

Important: The regeneration script overwrites pyproject.toml and setup.py on every run. If your dependency change must persist across regeneration, add a patch function to regenerate_python.py. The existing patch_urllib3_minimum_version() function is a good example of how to do this.

TypeScript

The regeneration script writes a fresh package.json from a template string (PACKAGE_JSON) in regenerate_ts.py. If you need to add or update a dependency persistently, edit the PACKAGE_JSON template in the regeneration script, not just the package.json file.

For one-off changes that do not need to survive regeneration, edit typescript-client-generated/package.json directly:

cd typescript-client-generated
npm install
npm run build

Go

Edit go.mod in go-client-generated/, then run:

cd go-client-generated
go mod tidy
go build ./...
go test -v ./...

Note: The regeneration script does NOT restore go.mod from backup. Instead, it patches the existing go.mod or creates a fresh one if none exists after codegen. Persistent dependency changes should be added to the FRESH_GO_MOD template string in regenerate_go.py. Be aware that go mod tidy may adjust dependency versions beyond what the template specifies.

CI Pipeline

The ci.yml workflow runs on every push and pull request to main. It tests all three clients in parallel.

Python Job

The CI pipeline uses pip directly rather than uv, since uv is not pre-installed on GitHub Actions runners.

- pip install -e ".[test]"
- pytest test/ -v --tb=short

TypeScript Job

- npm install
- npm run build
- npm test

Go Job

- go build ./...
- go test -v ./...

Known issue: The CI workflow currently references javascript-client-generated for the TypeScript job. This is a known defect -- the directory was renamed to typescript-client-generated and ci.yml has not been updated yet. The TypeScript CI job will fail until the working directory is corrected. Note that the publish workflow (publish-js.yml) already uses the correct directory name.

Troubleshooting

"Package version does not match tag"

The publish-python.yml workflow checks that the installed package version matches the git tag. If you see this error, the version in pyproject.toml does not match the python-vX.Y.Z tag you created. Either regenerate the client from the correct spec version or manually update pyproject.toml and setup.py.

"Regex validator patch: no validators needed fixing"

This is an informational warning, not an error. It means the current spec has no UUID or datetime fields with regex patterns that trigger the Pydantic v2 validator bug. The patch function runs safely and produces no changes.

"RevokeToken patch did not match"

The swagger-codegen output structure for api_authentication.go has changed. Inspect the file manually and look for the RevokeToken function. You may need to update the REVOKE_TOKEN_OLD constant in regenerate_go.py to match the new output format.

Build Failures After Regeneration

  1. Check REGENERATION_REPORT.md for a summary of what happened.
  2. Check test_output.log for test failures.
  3. Check build_output.log for compilation errors (Go, TypeScript).
  4. If the regeneration script exited with code 2, the codegen succeeded but something downstream failed. The logs will tell you what.

"Tag already exists"

You have already created a release for this version. Options:

  • Bump the version in the spec or manually in the package files, then tag again.
  • Delete the existing tag if it was created by mistake: git tag -d TAG && git push origin :refs/tags/TAG

TypeScript TS2430 Errors

If you see TypeScript errors like Interface 'DfdDiagram' incorrectly extends interface 'BaseDiagram', the optional-extends patch may not have applied. Regenerate the client and check the build output. If the patch reports "no properties needed fixing", the codegen output format may have changed.

Go "undefined: TokenRevokeBody" or Similar

If the Go client fails to build with references to undefined types, swagger-codegen may have changed its output format. Compare the current api_authentication.go with the expected patch targets and update regenerate_go.py accordingly.

Quick Reference

Python TypeScript Go
Package tmi-client @tmiclient/client tmiclient
Registry PyPI npm Go modules (git)
Generator openapi-generator 7.x openapi-generator 7.x swagger-codegen 3.0.75
Release tag python-vX.Y.Z ts-vX.Y.Z go-vX.Y.Z
Config file scripts/openapi-generator-config.json scripts/openapi-generator-config.json scripts/swagger-codegen-config.json
Runtime Python 3.9+ Node 18+ Go 1.21+
Models Pydantic v2 TypeScript interfaces Go structs
Regenerate python3 regenerate_python.py python3 regenerate_ts.py python3 regenerate_go.py
Release script ./release_python.sh Manual tag + GH Release Manual tag + GH Release
Publish workflow publish-python.yml publish-js.yml publish-go.yml
Publish method Trusted publishing (OIDC) npm token Git tags (no upload)

Related Pages

Clone this wiki locally