Branch

Generated REST Clients

Human dan

I'm interviewing for a senior engineer position and would like to do some light interview prep. The team I'd be joining is migrating and modernizing a legacy application. That application is used by organizations around the world, so it is key that the migration is smooth and painless.

Responsibilities

  • Support legacy data migration efforts
  • Prepare and validate customer data for migration
  • Execute and monitor data load activities
  • Investigate and resolve migration related activities
  • Implement fixes and enhancements to migration tooling and related application code
  • Deliver minor user interface improvements to support migration workflows.
  • Conduct code reviews and ensure code quality standards
  • Solve complex technical problems and identify practical solutions
  • Collaborate with stakeholders on migration priorities and outcomes

Qualifications

  • 4+ years of software dev experience
  • Python (Django): Strong experience building and maintaining server-side applications. Comfortable with Django management commands, ORM bulk operations, transactions, and service-layer patterns. Experience with the data migration domain (ETL, data mapping, checkpointed jobs) is a plus.
  • Data migration and ETL: Hands-on experience moving structured data between systems. Familiarity with XML/CSV parsing, metadata schemas, controlled vocabularies Controlled Vocabularies, and bulk database loads. Django and ETL Experience with data collections, DAM, or library/archive metadata is strongly preferred. Dam and libary/archive metadata
  • PostgreSQL: Solid understanding of relational databases, JSON/JSONB fields, upserts, and debugging data integrity issues at scale.
    2 branches
  • TypeScript / React: Working proficiency for minor UI updates in Next.js App router codebase. Next.js App routerComfortable reading and extending existing components; deep SSR/RSC expertise not required for this role.
  • Material UI (MUI): Familiarity with MUI V6+ for small UI changes.
  • RESTful APIs: Experience consuming and integrating with RES APIs; OpenAPI/Swagger-generated clients a plus. Generated REST Clients You are here
This branch begins here Generated REST Clients
Human dan

Show me how I would generate clients in python.

AI gpt-5.6-terra

A common way to generate a Python REST API client from an OpenAPI/Swagger specification is with OpenAPI Generator.

1. Obtain the OpenAPI spec

The API needs to expose a spec file, usually at one of these URLs:

https://api.example.com/openapi.json
https://api.example.com/swagger.json
https://api.example.com/openapi.yaml

Download it if desired:

curl -o openapi.json https://api.example.com/openapi.json

2. Generate with OpenAPI Generator

Install the generator

Using npm:

npm install -g @openapitools/openapi-generator-cli

Or run it through Docker, which avoids a local Java/npm setup:

docker run --rm \
  -v "${PWD}:/local" \
  openapitools/openapi-generator-cli generate \
  -i /local/openapi.json \
  -g python \
  -o /local/generated-client

Generate a Python client locally

openapi-generator-cli generate \
  -i openapi.json \
  -g python \
  -o generated-client \
  --additional-properties=packageName=legacy_api_client,projectName=legacy-api-client

This typically creates a package structure such as:

generated-client/
  legacy_api_client/
    api/
    models/
    api_client.py
    configuration.py
  test/
  pyproject.toml or setup.py

Install it into your Django project:

pip install -e ./generated-client

3. Use the generated client

Suppose the OpenAPI document defines a CustomersApi and an endpoint like GET /customers/{id}.

from legacy_api_client import ApiClient, Configuration
from legacy_api_client.api.customers_api import CustomersApi

configuration = Configuration(
    host="https://legacy-api.example.com"
)
configuration.api_key["Authorization"] = "Bearer my-access-token"

with ApiClient(configuration) as api_client:
    customers_api = CustomersApi(api_client)

    customer = customers_api.get_customer(customer_id="cust_123")

    print(customer.name)

Generated method names depend on the spec's operationId. For example, this endpoint:

paths:
  /customers/{customer_id}:
    get:
      operationId: getCustomer

usually becomes something close to:

customers_api.get_customer(customer_id="cust_123")

4. Prefer a service-layer wrapper in Django

I would generally avoid calling generated client code directly from views, management commands, or migration orchestration logic. Wrap it so that generated-code details remain isolated.

# integrations/legacy_system/client.py
from legacy_api_client import ApiClient, Configuration
from legacy_api_client.api.assets_api import AssetsApi


class LegacySystemClient:
    def __init__(self, base_url: str, access_token: str):
        configuration = Configuration(host=base_url)
        configuration.api_key["Authorization"] = f"Bearer {access_token}"
        self.api_client = ApiClient(configuration)
        self.assets_api = AssetsApi(self.api_client)

    def get_asset(self, external_id: str):
        return self.assets_api.get_asset(asset_id=external_id)

    def create_asset(self, payload):
        return self.assets_api.create_asset(asset_create_request=payload)

Then a Django management command or migration service can depend on your wrapper:

# migrations/services/asset_import.py
from integrations.legacy_system.client import LegacySystemClient


class AssetImportService:
    def __init__(self, legacy_client: LegacySystemClient):
        self.legacy_client = legacy_client

    def import_asset(self, external_id: str):
        source_asset = self.legacy_client.get_asset(external_id)

        # Map the source API model to the local Django model.
        # Validate, persist, and record migration status here.
        return source_asset

That design is useful because an API spec regeneration may change generated method names or model structure, while the rest of your application only relies on your stable wrapper.

5. A practical migration-oriented generation command

For a checked-in generated client:

openapi-generator-cli generate \
  -i https://legacy-api.example.com/openapi.json \
  -g python \
  -o clients/legacy_api \
  --additional-properties=packageName=legacy_api_client,projectName=legacy-api-client,packageVersion=0.1.0 \
  --global-property=apiDocs=false,modelDocs=false,apiTests=false,modelTests=false

In a real team, I’d put this in a Makefile or script:

generate-legacy-client:
    openapi-generator-cli generate \
        -i specs/legacy-api.openapi.yaml \
        -g python \
        -o clients/legacy_api \
        --additional-properties=packageName=legacy_api_client,projectName=legacy-api-client \
        --global-property=apiDocs=false,modelDocs=false,apiTests=false,modelTests=false

Then use CI to verify the generated client is current:

make generate-legacy-client
git diff --exit-code

This prevents a spec change from silently leaving the committed client out of date.

6. Alternative: generate a modern httpx client with openapi-python-client

Another popular option is openapi-python-client, which generates clients based on httpx.

pip install openapi-python-client
openapi-python-client generate --path openapi.json --output-path generated-client

Typical usage:

from legacy_api_client import Client
from legacy_api_client.api.customers import get_customer

client = Client(
    base_url="https://legacy-api.example.com",
    headers={"Authorization": "Bearer my-access-token"},
)

response = get_customer.sync_detailed(
    client=client,
    customer_id="cust_123",
)

if response.status_code == 200:
    customer = response.parsed

For a Django migration application, either generator is valid. The main things I’d emphasize in an interview are:

  • Treat the generated client as an integration boundary, not business logic.
  • Pin the generator version for reproducible output.
  • Version and validate the OpenAPI spec.
  • Regenerate intentionally, ideally with a CI drift check.
  • Add retries, timeouts, error normalization, and observability in your wrapper rather than editing generated files.
  • Ensure migration jobs are idempotent: API retries must not accidentally create duplicate records.

Explore conversation