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.create → AsyncPetsClient); 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
- 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)?
- Does the top-level
AsyncClient keep returning namespace-client objects lazily (attribute access creates on demand) or eagerly (constructed in __init__)?
- 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
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.pyis the odd file out.What to consider
Move each endpoint class into the namespace it belongs to:
The top-level
AsyncClientkeeps its current shape — it just imports the per-namespace classes and exposes them as attributes.Trade-offs
Pros
Cons
pets.list,pets.create→AsyncPetsClient); the mapping to type-namespace isn't always 1:1, so the split rule needs to be deliberate, not mechanical.TYPE_CHECKINGguards or lazy imports, but adds care.Open questions
.inpets.list) get its own file, or do we group sub-prefixes together (pets.listandpets.createtogether butpets.cdc-eventsseparate)?AsyncClientkeep returning namespace-client objects lazily (attribute access creates on demand) or eagerly (constructed in__init__)?_client.pyusers 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