Building Python gRPC Services Without Protoc Using fastgrpc3

Building Python gRPC Services Without Protoc Using fastgrpc3 EduTechBuddy.com © edutechbuddy.com
Building Python gRPC Services Without Protoc Using fastgrpc3 © edutechbuddy.com
Discover how fastgrpc3 lets you create Python gRPC services with type-safe models, validation, and high performance—no protoc required. Learn to define contracts, generate clients, and test real services using a Rust-powered protobuf engine.

Developers working with Python and gRPC often run into repetitive routines: writing .proto files, running protoc to generate code, and keeping generated files in sync with the schema. These steps can slow teams down and lead to mismatches, especially when building several services or collaborating. The usual workflow, as outlined by projects like matrix-contracts, means regenerating Python gRPC stubs with grpc_tools.protoc whenever a .proto file changes, which produces _pb2 and _pb2_grpc modules for sharing contracts.

Introducing fastgrpc3: A New Approach to Python gRPC

fastgrpc3 is an open-source Python framework from Edwin Alkins that simplifies gRPC development by using Pydantic models at the service boundary. This removes the need to handle generated _pb2 and _pb2_grpc classes directly. The engine behind it is protoruf, a Rust-based protobuf implementation that compiles .proto definitions in memory and maps protobuf bytes to Pydantic models. Alkins explains, "This tutorial builds the same service with fastgrpc3, an open-source gRPC framework I wrote on top of my Rust protobuf codec, protoruf." (fastgrpc3 project).

Setting Up fastgrpc3

To begin, install fastgrpc3 using pip:

pip install fastgrpc3

fastgrpc3 works with CPython versions 3.12 and 3.14, offering prebuilt wheels for Linux, macOS, and Windows. You do not need to install protoc separately, since the Rust-based codec comes bundled. The documentation notes, "There is no protoc to install: protobuf is compiled and encoded in Rust by protoruf, which ships as a wheel." (fastgrpc3 docs).

Defining Services with Pydantic Models

With fastgrpc3, you define your gRPC service using Pydantic models for requests and responses. Incoming data gets validated before your handler runs, and responses are checked for correctness. Here is an example of a simple order service:

from pydantic import BaseModel
from fastgrpc3 import GrpcApp, GrpcError, GrpcService, Insecure, StatusCode

class GetOrder(BaseModel):
    order_number: str

class Order(BaseModel):
    order_number: str
    paid: bool = False

DB = {"A": Order(order_number="A", paid=True)}

service = GrpcService("Orders", package="orders.v1")

@service.unary()
async def Get(req: GetOrder) -> Order:
    if req.order_number not in DB:
        raise GrpcError(StatusCode.NOT_FOUND, "no such order")
    return DB[req.order_number]

app = GrpcApp(address="[::]:50051", tls=Insecure())
app.add_service(service)
app.enable_reflection()

Requests that do not match the model are rejected with an INVALID_ARGUMENT status. Errors raised as GrpcError are mapped to standard gRPC status codes, so clients in any language can handle them.

Running and Testing the Service

To start the service, use this command:

fastgrpc3 run orders:app

You can interact with the running service using grpcurl and server reflection:

grpcurl -plaintext -d '{"order_number": "A"}' localhost:50051 orders.v1.Orders/Get

This returns a JSON response with the order details if found.

Automatic Protobuf Contract Generation

Instead of writing .proto files by hand, fastgrpc3 generates them from your Pydantic models. Field numbers are managed and locked in a proto.lock file to keep compatibility over time. You can export standard .proto files for use by other teams or languages:

fastgrpc3 lock orders:app
fastgrpc3 export orders:app -o proto/

This workflow helps maintain a consistent contract and supports integration with Go, Java, TypeScript, and other languages that use protobuf. The generator command fastgrpc3 generate proto/ -o src/myapp/gen produces Pydantic models, a service contract, and a typed client, moving code-generation tasks into the framework while still supporting protobuf schemas.

Typed Python Clients

fastgrpc3 lets you create strongly typed Python clients that use your Pydantic models. Here is an example client for the Orders service:

import asyncio
from fastgrpc3 import Channel, GrpcClient, GrpcError, Insecure
from orders import GetOrder, Order

class OrdersClient(GrpcClient):
    package = "orders.v1"
    service = "Orders"
    get = GrpcClient.unary("Get", GetOrder, Order)

async def main() -> None:
    async with Channel("localhost:50051", tls=Insecure()) as channel:
        orders = OrdersClient(channel)
        print(await orders.get(GetOrder(order_number="A")))
        try:
            await orders.get(GetOrder(order_number="Z"))
        except GrpcError as error:
            print(error.code.name, error.message)

if __name__ == "__main__":
    asyncio.run(main())

Responses come back as Pydantic models, making it easier to work with typed data and integrate with tools like mypy and modern editors.

Dependency Injection and Advanced Features

fastgrpc3 supports dependency injection similar to FastAPI. You can define dependencies for database sessions or user context. Cleanup for yield-based dependencies is handled automatically. The framework also supports sub-dependencies, per-call caching, blocking I/O providers, and test overrides.

from collections.abc import AsyncIterator
from fastgrpc3 import Context, Depends

async def session() -> AsyncIterator[dict[str, Order]]:
    yield DB

def current_user(ctx: Context) -> str:
    return str(ctx.metadata.get("x-user", "anonymous"))

@service.unary()
async def Pay(
    req: GetOrder,
    db: dict[str, Order] = Depends(session),
    user: str = Depends(current_user),
) -> Order:
    order = db[req.order_number]
    order.paid = True
    return order

Hooks, middleware, and exception handlers can be added around every call, supporting robust service design and testing.

Testing with Real Servers

fastgrpc3 includes a TestClient that launches the app on a local port, so you can test the actual gRPC wire protocol:

from fastgrpc3.testing import TestClient
from orders import GetOrder, app
from client import OrdersClient

async def test_get() -> None:
    async with TestClient(app) as tc:
        order = await tc.client(OrdersClient).get(GetOrder(order_number="A"))
        assert order.paid

This approach ensures your tests cover real network interactions, not just mocks.

Production-Ready Deployment and High Performance

For production, you can enable TLS, health checks, reflection, and documentation endpoints. fastgrpc3 can run on the standard grpc.aio server or switch to a Rust-based transport built on tonic for higher throughput. The fastgrpc3-transport project describes a tonic-based Rust gRPC server transport where HTTP/2, gRPC framing, TLS, compression, and flow control run in Rust, while Python's asyncio loop receives calls in batches and dispatches them through fastgrpc3's runtime. The transport is marked "experimental" and provides a parity matrix for differences from grpc.aio. CI-tested wheels are available for Linux x86_64 and aarch64, macOS arm64 and x86_64, and Windows x86_64, supporting CPython 3.12 and 3.14.

Benchmarks from the project state that encoding a 20-field message takes about 1 microsecond, which is 55 times faster than using google.protobuf with Pydantic. These numbers come from the author and reflect the Rust-backed codec's performance under specific test conditions.

How fastgrpc3 Works Under the Hood

The core of fastgrpc3 is the protoruf codec, which compiles protobuf schemas in memory and maps them to Pydantic models. This setup removes the need for generated _pb2 classes and speeds up encoding and decoding, with performance gains over traditional methods. The framework's generator command can produce Pydantic models, service contracts, and typed clients directly from protobuf definitions, moving code-generation tasks into the framework itself.

Compatibility and Ecosystem Considerations

While fastgrpc3 offers a model-first workflow and Rust-powered speed, the conventional generated-stub workflow is still widely used in Python projects, especially where broad ecosystem compatibility and multi-language support matter. Teams using established CI pipelines or distributing contracts across languages may stick with protoc-based tooling. The fastgrpc3 project is at version 0.1 and is described as an early-stage open-source framework. Feedback and compatibility discovery are ongoing, and teams considering adoption are encouraged to test schema evolution, error semantics, streaming, TLS, compression, generated-client interoperability, and operational behavior against the documented parity matrix and known issues.

Getting Started

To try fastgrpc3, install the package with tonic support:

pip install "fastgrpc3[tonic]"

For more details, visit the official documentation or the project's GitHub repository. fastgrpc3 is currently at version 0.1, and feedback from developers is encouraged to help improve the framework.

Camila Ferreira Editor EduTechBuddy.com
Co-Founder

Camila Ferreira

Camila Ferreira is the Co-Founder of EduTechBuddy, where her editorial focus is programming education and practical software development skills. Her primary subjects include Python programming, Git version control, and the fundamental concepts beginners need to understand when working with code.