pyRPC
← Back to Blog

v0.2.0 - Type safety, proper async, and @pyrpc/types

·6 min read

pyRPC v0.2.0 fixes three ship-blocking issues that eroded the “tRPC for Python” promise. Here’s what changed and why.

1. RPCCallable now actually awaits

RPCCallable.__await__ was a no-op - pass. Calling await client.add(1, 2) silently returned nothing.

The fix: RPCCallable.__call__ now detects whether a running event loop exists. In async contexts, it returns a coroutine via call_async. In sync contexts, it calls call_sync directly.

client = RPCClient("http://localhost:8000")

# Sync  -  works as before
result = client.add(1, 2)

# Async  -  now actually works!
async_result = await client.add(1, 2)

The explicit .aio() method also remains available for clarity.

2. Generated types that actually mean something

Every parameter and return in the generated TypeScript interface was any:

// Before: zero type safety
add(a: any, b: any): Promise<any>;

The codegen now maps Python types to TypeScript types:

PythonTypeScript
int / floatnumber
strstring
boolboolean
Optional[str]string | null
List[int]number[]
Dict[str, int]Record<string, number>
// After: real types
add(a: number, b: number): Promise<number>;
greet(name: string): Promise<string>;

Custom Pydantic models resolve to their class name for future model generation.

3. @pyrpc/types - install, set up, done

@pyrpc/types is now a dependency of @pyrpc/client. The package ships a placeholder src/index.ts that is replaced when codegen runs. The import path import type { Types } from "@pyrpc/types" always works - before codegen, it resolves to an empty Record<string, never>; after codegen, it resolves to your actual procedure types.

Setup is an npm install

Installing @pyrpc/client triggers a postinstall script in @pyrpc/types:

npm install @pyrpc/client

If you’re in an interactive terminal, it prompts for your backend URL, fetches the schema, and generates types - all in one step. The generated file lives inside node_modules/@pyrpc/types/src/index.ts, so import type { Types } from "@pyrpc/types" resolves immediately.

For CI or non-interactive environments, set PYRPC_URL:

PYRPC_URL=https://api.example.com npm install @pyrpc/client

No separate pyrpc init command needed. No polling. No manual steps. The postinstall handles the common case.

Types at compile time

The generated file exports Types, a TypeScript interface with your procedure signatures. Since it’s inside node_modules/@pyrpc/types, the import path is clean and standard.

The baseUrl in createClient() is the same URL used during codegen - there’s one URL, it’s your backend. No staging-vs-production confusion.

Production / CI workflow

In CI, types are generated as a build step:

# Install deps
npm install @pyrpc/client

# Build (CI provides PYRPC_URL, or codegen runs explicitly)
pyrpc codegen https://api.example.com
npm run build

The generated file can also be committed to the repo so CI doesn’t need access to a running Python server.

What’s next

  • Pydantic model → TypeScript interface generation
  • Dev-mode file watcher: pyrpc dev starts a file watcher (not HTTP poller) that regenerates types when Python sources change, using the same OS-level notification APIs as uvicorn --reload

Check the quickstart to try it out.