Skip to main content

Server and client runtime library for Connect RPC

Project description

connect-python

License CI codecov PyPI version API Docs

A Python implementation of Connect: Protobuf RPC that works.

This repo provides a Python implementation of Connect, including both client and server support. It includes a protoc plugin that generates typed client stubs and server interfaces from your .proto files, along with runtime libraries for both synchronous and asynchronous code.

Features

  • Clients: Both synchronous and asynchronous clients backed by pyqwest
  • Servers: WSGI and ASGI server implementations for use with any Python app server
  • Type Safety: Fully type-annotated, including the generated code
  • Compression: Built-in support for gzip, brotli, and zstd compression
  • Interceptors: Server-side and client-side interceptors for cross-cutting concerns
  • Streaming: Full support for server, client, and bidirectional streaming
  • Standards Compliant: Verified implementation using the official Connect conformance test suite

Installation

Install the runtime library

pip install connectrpc

Or with your preferred package manager:

# Using uv
uv add connectrpc

# Using poetry
poetry add connectrpc

Install the code generator

With a protobuf definition in hand, you can generate stub code. This is easiest using buf, but you can also use protoc if you're feeling masochistic.

A reasonable buf.gen.yaml:

version: v2
plugins:
  - remote: buf.build/protocolbuffers/python
    out: .
  - remote: buf.build/protocolbuffers/pyi
    out: .
  - remote: buf.build/connectrpc/python
    out: .

Or, you can install the compiler (e.g. pip install protoc-gen-connectrpc), and it can be referenced as protoc-gen-connectrpc. Then, you can use protoc-gen-connectrpc as a local plugin:

- local: .venv/bin/protoc-gen-connectrpc
  out: .

Alternatively, download a precompiled binary from the releases.

protoc-gen-connectrpc is only needed for code generation. Your actual application should include connectrpc as a dependency for the runtime component.

Basic Client Usage

from your_service_pb2 import HelloRequest, HelloResponse
from your_service_connect import HelloServiceClient

# Create async client
async def main():
    async with HelloServiceClient("https://api.example.com") as client:
        # Make a unary RPC call
        response = await client.say_hello(HelloRequest(name="World"))
        print(response.message)  # "Hello, World!"

Basic Server Usage

from connectrpc.request import RequestContext
from your_service_pb2 import HelloRequest, HelloResponse
from your_service_connect import HelloService, HelloServiceASGIApplication

class MyHelloService(HelloService):
    async def say_hello(self, request: HelloRequest, ctx: RequestContext) -> HelloResponse:
        return HelloResponse(message=f"Hello, {request.name}!")

# Create ASGI app
app = HelloServiceASGIApplication(MyHelloService())

# Run with any ASGI server like uvicorn and hypercorn:
# uvicorn server:app --port 8080

Basic Client Usage (Synchronous)

from your_service_pb2 import HelloRequest
from your_service_connect import HelloServiceClientSync

# Create sync client
def main():
    with HelloServiceClientSync("https://api.example.com") as client:
        # Make a unary RPC call
        response = client.say_hello(HelloRequest(name="World"))
        print(response.message)  # "Hello, World!"

if __name__ == "__main__":
    main()

Check out the docs for more detailed usage including streaming, interceptors, and other advanced features.

Streaming Support

connect-python supports all RPC streaming types:

  • Unary: Single request, single response
  • Server Streaming: Single request, multiple responses
  • Client Streaming: Multiple requests, single response
  • Bidirectional Streaming: Multiple requests, multiple responses

Server Streaming

Single request, multiple responses:

# Server implementation
async def make_hats(self, req: Size, ctx: RequestContext) -> AsyncIterator[Hat]:
    for i in range(3):
        yield Hat(size=req.inches + i, color=["red", "green", "blue"][i])

# Client usage
async for hat in client.make_hats(Size(inches=12)):
    print(f"Received: {hat}")

Client Streaming

Multiple requests, single response:

# Server implementation
async def collect_sizes(self, reqs: AsyncIterator[Size], ctx: RequestContext) -> Summary:
    total = 0
    count = 0
    async for size in reqs:
        total += size.inches
        count += 1
    return Summary(total=total, average=total/count if count else 0)

# Client usage
async def send_sizes():
    for i in range(5):
        yield Size(inches=i * 2)

summary = await client.collect_sizes(send_sizes())

Bidirectional Streaming

Multiple requests and responses:

# Server implementation (like the Eliza chatbot)
async def converse(self, reqs: AsyncIterator[ConverseRequest], ctx: RequestContext) -> AsyncIterator[ConverseResponse]:
    async for req in reqs:
        # Process and respond to each message
        reply = process_message(req.sentence)
        yield ConverseResponse(sentence=reply)

# Client usage
async def chat():
    yield ConverseRequest(sentence="Hello")
    yield ConverseRequest(sentence="How are you?")

async for response in client.converse(chat()):
    print(f"Response: {response.sentence}")

Streaming Notes

  • HTTP/2 ASGI servers (Hypercorn, Daphne): Support all streaming types including full-duplex bidirectional

  • HTTP/1.1 servers: Support half-duplex bidirectional streaming only

  • WSGI servers: Support streaming but not full-duplex bidirectional due to protocol limitations

  • Clients: Support half-duplex bidirectional streaming only

Examples

The example/ directory contains complete working examples demonstrating all features:

  • Eliza Chatbot: A Connect implementation of the classic ELIZA psychotherapist chatbot
    • eliza_service.py - Async ASGI server implementation
    • eliza_service_sync.py - Synchronous WSGI server implementation
    • eliza_client.py - Async client example
    • eliza_client_sync.py - Synchronous client example
  • All streaming patterns: Unary, server streaming, client streaming, and bidirectional
  • Integration examples: Starlette, Flask, and other frameworks

Run the Eliza example:

# Start the server
cd example
uvicorn example.eliza_service:app --port 8080

# In another terminal, run the client
python -m example.eliza_client

Supported Protocols

  • ✅ Connect Protocol over HTTP/1.1 and HTTP/2
  • ✅ gRPC Protocol support
  • 🚧 gRPC-Web Protocol support is not available

Server Runtime Options

For ASGI servers:

  • Uvicorn - Lightning-fast ASGI server
  • Daphne - Django Channels' ASGI server with HTTP/2 support
  • Hypercorn - ASGI server with HTTP/2 and HTTP/3 support

For WSGI servers:

  • Gunicorn - Python WSGI HTTP Server
  • uWSGI - Full-featured application server
  • Any WSGI-compliant server

For testing, you'll need the buf CLI for running conformance tests.

WSGI Support

connect-python provides full WSGI support via ConnectWSGIApplication for synchronous Python applications. This enables integration with traditional WSGI servers like Gunicorn and uWSGI.

from connectrpc.request import RequestContext
from connectrpc.server import ConnectWSGIApplication
from your_service_pb2 import Request, Response
from your_service_connect import YourService, YourServiceWSGIApplication

class YourServiceImpl(YourService):
    def your_method(self, request: Request, ctx: RequestContext) -> Response:
        # Synchronous implementation
        return Response(message="Hello from WSGI")

    # WSGI also supports streaming (except full-duplex bidirectional)
    def stream_data(self, request: Request, ctx: RequestContext) -> Iterator[Response]:
        for i in range(3):
            yield Response(message=f"Message {i}")

# Create WSGI application
app = YourServiceWSGIApplication(YourServiceImpl())

# Run with gunicorn: gunicorn server:app

Compression Support

connect-python supports multiple compression algorithms:

  • gzip: Built-in support, always available
  • brotli: Available when brotli package is installed
  • zstd: Available when zstandard package is installed

Compression is automatically negotiated between client and server based on the Accept-Encoding and Content-Encoding headers.

Interceptors

Server-Side Interceptors

Interceptors allow you to add cross-cutting concerns like authentication, logging, and metrics:

from connectrpc.interceptor import MetadataInterceptor
from connectrpc.request import RequestContext

class LoggingInterceptor:
    """Implements the MetadataInterceptor protocol."""

    async def on_start(self, ctx: RequestContext) -> None:
        print(f"Handling {ctx.method().name} request")

    async def on_end(self, token: None, ctx: RequestContext, error: Exception | None) -> None:
        if error:
            print(f"Failed {ctx.method().name}: {error}")
        else:
            print(f"Completed {ctx.method().name} request")

# Add to your application
app = HelloServiceASGIApplication(
    MyHelloService(),
    interceptors=[LoggingInterceptor()]
)

Client-Side Interceptors

Clients also support interceptors for request/response processing:

client = HelloServiceClient(
    "https://api.example.com",
    interceptors=[AuthInterceptor(), RetryInterceptor()]
)

Advanced Features

Connect GET Support

connect-python automatically enables GET request support for methods marked with idempotency_level = NO_SIDE_EFFECTS in your proto files:

service YourService {
  // This method will support both GET and POST requests
  rpc GetData(Request) returns (Response) {
    option idempotency_level = NO_SIDE_EFFECTS;
  }
}

Clients can use GET requests automatically:

# The client will use GET for idempotent methods
response = await client.get_data(request)

CORS Support

connect-python works with any ASGI CORS middleware. For example, using Starlette:

from starlette.middleware.cors import CORSMiddleware
from starlette.applications import Starlette

app = Starlette()
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["GET", "POST"],
    allow_headers=["*"],
)
# Mount your Connect application

Message Size Limits

Protect against resource exhaustion by limiting message sizes:

# ASGI application with 1MB limit
app = YourServiceASGIApplication(
    service,
    read_max_bytes=1024 * 1024  # 1MB
)

# Client with message size limit
client = YourServiceClient(
    "https://api.example.com",
    read_max_bytes=1024 * 1024
)

When exceeded, returns RESOURCE_EXHAUSTED error.

Proto Editions Support

protoc-gen-connectrpc supports up to Protobuf Editions 2024:

edition = "2024";

package your.service;

service YourService {
  rpc YourMethod(Request) returns (Response);
}

Development

We use ruff for linting and formatting, ty for type checking, and tombi for TOML linting and formatting.

We rely on the conformance test suit (in ./conformance) to verify behavior.

Set up a virtual env:

uv sync

Then, use uv run poe check to do development checks, or check out uv run poe for other targets.

Status

This project is in beta and is being actively developed. 1.0 will include a new Protobuf implementation built from scratch by Buf, which may introduce breaking changes. Join us on Slack if you have questions or feedback.

Legal

Offered under the Apache 2 license.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

connectrpc-0.10.1.tar.gz (47.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

connectrpc-0.10.1-py3-none-any.whl (64.1 kB view details)

Uploaded Python 3

File details

Details for the file connectrpc-0.10.1.tar.gz.

File metadata

  • Download URL: connectrpc-0.10.1.tar.gz
  • Upload date:
  • Size: 47.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for connectrpc-0.10.1.tar.gz
Algorithm Hash digest
SHA256 eaf093a4f0d1b9c854c71839083ed5242a551100e99cf52e3cc5e35663f2a5ea
MD5 3a4d9196aaa4c5147166a6cb9e91e481
BLAKE2b-256 9311adb3ab104282202000b1c98213c70c7068d76917d9f17c83b818e3d212c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for connectrpc-0.10.1.tar.gz:

Publisher: release.yaml on connectrpc/connect-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file connectrpc-0.10.1-py3-none-any.whl.

File metadata

  • Download URL: connectrpc-0.10.1-py3-none-any.whl
  • Upload date:
  • Size: 64.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for connectrpc-0.10.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6e965625bbc4b185532bd98e4919528bea6d1c24619b6dba416d0dfafc10e344
MD5 f7eb1c2c2e745b7b9d2d8cbd42b070a1
BLAKE2b-256 cf6749a6a2d1d0ee04b4d96410d12c751a44437a9bdf4991d7b88fa5c231c0d2

See more details on using hashes here.

Provenance

The following attestation bundles were made for connectrpc-0.10.1-py3-none-any.whl:

Publisher: release.yaml on connectrpc/connect-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page