Skip to content

Python codegen: split _client.py into per-namespace client files #152

@hardbyte

Description

@hardbyte

Background

Generated Python clients put every endpoint class into a single _client.py. For schemas with many namespaces this file grows large — Partly's core-server schema produces a 17k-line file with 98 endpoint classes (AsyncBillingClient, AsyncDismantlerClient, …). The rest of the package is already split per-namespace (each namespace's types live in <ns>/__init__.py), so the monolithic _client.py is the odd file out.

What to consider

Move each endpoint class into the namespace it belongs to:

core_server_client/
  _client.py               # Top-level AsyncClient / Client (aggregator)
  billing/
    __init__.py            # types (unchanged)
    _client.py             # AsyncBillingClient / BillingClient
  dismantler/
    __init__.py
    _client.py             # AsyncDismantlerClient / DismantlerClient
  ...

The top-level AsyncClient keeps its current shape — it just imports the per-namespace classes and exposes them as attributes.

Trade-offs

Pros

  • Each file's size matches its conceptual scope.
  • "Add an endpoint to namespace X" diffs only X's file.
  • IDE jump-to-definition lands in a small file.
  • The layout mirrors what the types side of the package already does.
  • Plausibly faster import time on partial use.

Cons

  • Codegen change with snapshot churn — every endpoint class moves files.
  • Endpoint→namespace boundary needs a design pass. Reflectapi groups functions by name prefix (pets.list, pets.createAsyncPetsClient); the mapping to type-namespace isn't always 1:1, so the split rule needs to be deliberate, not mechanical.
  • Risk of cross-namespace import cycles (an endpoint in namespace A returns a type in namespace B). Solvable with TYPE_CHECKING guards or lazy imports, but adds care.
  • One more layer of indirection for users reading the generated code.

Open questions

  1. Function-group → file mapping: does each top-level group (the part before . in pets.list) get its own file, or do we group sub-prefixes together (pets.list and pets.create together but pets.cdc-events separate)?
  2. Does the top-level AsyncClient keep returning namespace-client objects lazily (attribute access creates on demand) or eagerly (constructed in __init__)?
  3. How should the existing _client.py users migrate — is the top-level re-export sufficient or do we deprecate direct imports?

Out of scope

Not blocking — current generated clients work. This is an ergonomics + maintainability improvement to consider before any 1.0 surface-area lock-in.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions