pyRPC
← Back to Blog

Inside the codegen template: what client.ts.j2 actually generates

·10 min read

When you run pyrpc codegen, a Jinja2 template turns your Python schema into a TypeScript file. This post walks through what that template produces and why.

The input

Codegen receives a JSON schema extracted from your Python code. It contains procedure names, parameter types, return types, and (since v0.9.0) a kind field per procedure.

The output structure

The generated index.ts has three sections:

// 1. Imports
import { Types as BaseTypes } from "@pyrpc/types"

// 2. Types interface — one method per procedure
export interface Types extends BaseTypes {
  greet: (name: string) => Promise<string>
  get_user: (userId: number) => Promise<User>
  update_user: (userId: number, name: string) => Promise<User>
}

// 3. ProcedureKinds — runtime kind map
export type ProcedureKinds = {
  greet: "query"
  get_user: "query"
  update_user: "mutation"
}

export const procedureKinds = {
  greet: "query",
  get_user: "query",
  update_user: "mutation",
} as const satisfies ProcedureKinds

Why extend BaseTypes

The extends BaseTypes pattern means the generated file is compatible with the @pyrpc/types placeholder. If codegen has not run yet, Types is an empty record. After codegen, it has your procedures. TypeScript stays happy either way.

How parameters map

Python parameters become positional TypeScript parameters:

# Python
@rpc.query
def get_user(user_id: int, include_posts: bool = False) -> dict: ...

// Generated TypeScript
get_user: (userId: number, includePosts?: boolean) => Promise<User>

Required parameters are positional. Default values become optional parameters with ?. Names are camelCased automatically.

Model interfaces

Pydantic models referenced in return types get their own interfaces via jsonschema-ts. These are inlined in the same file or imported from a shared definitions block.

The npx daemon

The jsonschema-ts integration runs a persistent Node.js process. First call: ~3.3s (cold start). Subsequent calls: ~4.6ms. That is a 715× speedup for the type conversion step. The template itself is instant — the bottleneck was always npx subprocess overhead.

Client docs · npx daemon deep dive