After the v0.2.0 release we stepped back and looked at the developer experience more carefully. The type generation worked, async worked, but the tooling story was fragmented and the messaging was unclear. Over the last week we shipped a series of improvements that reshape how you interact with pyRPC — especially if you are on the backend side.
Pattern A: one CLI, two subcommands
The biggest structural change is how the codegen tooling is organized. We went back and forth between splitting pyrpc-codegen and pyrpc-pull into separate packages, but that created more confusion than it solved. One extra pip install, one more thing to remember, one more version to track.
We settled on what we call Pattern A: a single pyrpc-codegen package that ships one CLI with five subcommands. The two you will use most:
pyrpc pull— import a Python module, walk itsget_registry_schema(), and save a portable JSON schema file. This is a server-side or CI tool. It pulls the schema out of Python and puts it on disk so codegen can run anywhere.pyrpc codegen— read a schema file or a URL, generate TypeScript types. That is it.
This mirrors Prisma’s db pull / generate split — one command to extract state from the source of truth, another to produce artifacts from it. The cycle is always: pull, then codegen. The two steps separate where the schema comes from from what you do with it.
Lazy imports make codegen lightweight
pyrpc codegen accepts both file paths and URLs. If you pass a URL, it fetches the schema over HTTP, transforms it, and writes TypeScript — all without ever importing pyrpc-core. The heavy framework dependencies (FastAPI, Pydantic, and all their transitive deps) stay on disk. Only pull, serve, andinspect trigger the lazy import.
This matters for CI pipelines and frontend-only workflows. If you are a TypeScript developer who just needs to regenerate types from a deployed server, you can pip install pyrpc-codegen and run pyrpc codegenwith PYRPC_URL set — no Python backend setup required.
The implementation is straightforward: a _lazy_import_pyrpc_core() function that does a deferred import pyrpc_core, and a _load_schema() dispatcher that checks for an http:// or https:// prefix to decide between HTTP fetch or filesystem read. No URL-guessing, no required protocol flag, no accidental network calls on local paths.
Frontend DX: npm install and done
The frontend side is now deliberately minimal. TypeScript developers do not need to know about pyrpc pull, pyrpc codegen, or the CLI at all. The entire setup is:
npm install @pyrpc/client
The postinstall script prompts for your server URL, fetches GET /rpc, and generates node_modules/@pyrpc/types/src/index.ts with full type definitions. Autocompletion works at compile time, no extra tooling, nonpx, no backend setup on the frontend machine.
For CI, set the PYRPC_URL environment variable and the postinstall runs headlessly:
PYRPC_URL=https://api.example.com npm install @pyrpc/client
We removed every mention of pull, npx pyrpc, and the CLI from the frontend-facing documentation — the quickstart, the TypeScript client guide, the installation page. If you are a frontend developer, none of that exists. The only reference to pull lives in the CLI reference doc where backend and CI developers will find it.
Cross-language story, not tRPC mimicry
A recurring theme in early feedback was that pyRPC looked like “tRPC, but for Python” — which implied it was a Python-only concept. The real value is the bridge: Python backend, TypeScript frontend, shared types across the language boundary.
We rewrote the home page, the comparison doc, and the CLI help text to lead with this framing. The headline is now “Python backend. TypeScript frontend.” The subtitle explains the rest: shared types, generated clients, no manual API surface maintenance. The vs tRPC section in the comparison doc now reads: “Python backend, TypeScript frontend, shared types. Works across language boundaries.”
This is not cosmetic. It changes what developers expect when they evaluate pyRPC. They are not choosing between tRPC and pyRPC for a TypeScript project. They are choosing between manual API types and automatic ones when their backend is Python and their frontend is TypeScript — which describes a huge portion of full-stack web development.
SECURITY.md gets the OSS treatment
We rewrote SECURITY.md to match the standard that mature open-source projects set. The new version has five clear sections:
- Reporting — GitHub private advisories, what to include, expected response time, credit policy.
- Supported versions — only the latest minor before 1.0.0, no SLA for pre-releases.
- What is in scope — core protocol, adapter boundary, generated code, introspection endpoint, error responses.
- What is not in scope — upstream dependencies, already-compromised machines, old versions.
- What we do and what we cannot promise — transparent about the trust boundaries.
Security docs are rarely read until they are needed. When they are needed, they need to be direct, specific, and practical. The old version was a dry list of rules. The new one tells you exactly what to do, what we cover, and where the responsibility shifts to you.
One notable addition: we explicitly state that pyRPC does not sandbox user code. If you register a @rpc procedure that deletes files, that is the procedure author’s responsibility. This is an honest boundary — we do not want users assuming pyRPC provides a security layer it does not.
Windows compatibility fix
A small but important fix: we replaced the Unicode checkmark ([OK] replaced[unicode-checkmark]) and em-dashes (- replaced [em-dash]) in the CLI output and templates. Windows terminals default to cp1252 encoding, which does not support these characters. Running pyrpc pull on Windows would crash with a UnicodeEncodeError before it ever wrote the schema file.
We also codified this in CONTRIBUTING.md: no emojis, no em-dashes, no Unicode special characters anywhere in the codebase. It is a strict policy because these bugs are silent on macOS and Linux and only surface on Windows at the worst possible moment.
Naming and conventions
We formalized branch naming and PR conventions in CONTRIBUTING.md:
- Branch prefixes:
feat/,fix/,chore/,docs/,perf/,security/(kebab-case). - PR titles: Conventional Commits with scopes (
core,adapter,client,codegen,docs,release,cli). - GitHub issue templates and a PR template now exist for contributor clarity.
- FAQ section in
CONTRIBUTING.mdmirrors mature OSS project patterns.
These might seem like overhead for a pre-1.0 project, but they establish the rhythm early. Every PR we merge now follows the same format, which makes the changelog (and the release notes) trivially derivable from the commit history.
What did not change
The type generation itself (_pytype_to_ts) was already solid after the v0.2.0 release — it maps int, str, bool, Optional,Union, List, Dict, Tuple, Set, and Noneto their TypeScript equivalents. The only case that falls through toany is a type that pyRPC cannot parse at all — which is increasingly rare as we handle more of the Python type system.
The one remaining gap is Pydantic model generation. Currently, a@model class resolves to its class name only (e.g. User). The next major codegen improvement will generate full TypeScriptinterface User { ... } definitions from Pydantic model schema.
What is next
The immediate roadmap has three items:
- React integration patterns — a docs page showing
createClientwithuseQuery/useMutation, auth headers, and error handling. - Pydantic model-to-interface generation — full TypeScript interfaces from Python model definitions.
- Compatibility doc — what version numbers mean, safe vs breaking changes, rolling upgrade patterns.
The CLI, the frontend DX, the security policy, and the documentation are in a good state. The remaining work is about depth — richer type generation, more production examples, and clearer guidance for teams scaling from one developer to many.
All 36 tests pass. The repo is at github.com/pyrpc/pyrpc.

pyRPC