Twirp Python Client/Server (via Twirpy)
Twirp is a simple RPC framework built on Protocol Buffers, prioritizing simplicity over expansive features. It enables service-to-service communication by generating routing and serialization code from .proto definitions, running over standard HTTP 1.1 with support for both Protobuf and JSON serialization. The `twirp` Python package (version 0.0.7) serves as the runtime library for 'Twirpy', which is the Python implementation of the Twirp framework, supporting Twirp Wire Protocol v7. While the `twirp` package itself has not seen recent updates, its underlying implementation, `twirpy`, is actively maintained with periodic releases.
Warnings
- breaking Twirp Wire Protocol v7 introduces breaking changes from v5, notably affecting server URL prefixes. The default prefix is now `/twirp`, but custom prefixes can be set via `server_path_prefix` in server and client constructors. Ensure compatibility when upgrading or interacting with services using different protocol versions.
- gotcha The `twirp` Python package provides the runtime library, but the actual server/client code generation requires the `protoc` compiler and the `protoc-gen-twirpy` Go plugin, which must be installed separately.
- gotcha The default maximum message body length is 100KB. Larger messages will result in errors unless this limit is explicitly overridden.
- gotcha The `twirp` PyPI package (version 0.0.7) is effectively a wrapper for the `twirpy` project. While `twirp` 0.0.7 mentions support for Protocol v7, active development and newer versions (e.g., 0.3.0.dev2) are found under the `twirpy` PyPI package. This can cause confusion regarding the 'latest' version or documentation sources.
Install
-
pip install twirp uvicorn -
protoc --python_out=. --twirpy_out=. ./your_service.proto -
go install github.com/verloop/twirpy/protoc-gen-twirpy@latest
Imports
- TwirpASGIApp
from twirp.asgi import TwirpASGIApp
- InvalidArgument
from twirp.exceptions import InvalidArgument
- TwirpServerException
from twirp.exceptions import TwirpServerException
- Context
from twirp.context import Context
Quickstart
import random
from twirp.asgi import TwirpASGIApp
from twirp.exceptions import InvalidArgument
from twirp.context import Context
# Assuming you have a haberdasher.proto and generated code like this:
# protoc --python_out=. --twirpy_out=. haberdasher.proto
# from . import haberdasher_twirp, haberdasher_pb2
# For demonstration, we'll mock these imports
# Mocking generated code for quickstart demonstration
class MockHat:
def __init__(self, size, color, name):
self.size = size
self.color = color
self.name = name
class MockSize:
def __init__(self, inches):
self.inches = inches
class MockHaberdasherPb2:
def Hat(self, size, color, name):
return MockHat(size, color, name)
class MockHaberdasherTwirp:
class HaberdasherServer:
def __init__(self, service, server_path_prefix='/twirp'):
self.service = service
self.server_path_prefix = server_path_prefix
class HaberdasherClient:
def __init__(self, base_url, server_path_prefix='/twirp'):
self.base_url = base_url
self.server_path_prefix = server_path_prefix
def MakeHat(self, ctx, request):
# In a real scenario, this would make an HTTP call
print(f"Client: Making hat for size {request.inches} inches")
# Simulate a successful response
return MockHat(size=request.inches, color="mock_color", name="mock_hat")
haberdasher_pb2 = MockHaberdasherPb2()
haberdasher_twirp = MockHaberdasherTwirp()
class HaberdasherService(object):
def MakeHat(self, context, size):
if size.inches <= 0:
raise InvalidArgument(argument="inches", error="I can't make a hat that small!")
return haberdasher_pb2.Hat(
size=size.inches,
color=random.choice(["white", "black", "brown", "red", "blue"]),
name=random.choice(["bowler", "baseball cap", "top hat", "derby"])
)
# Server setup
service = haberdasher_twirp.HaberdasherServer(service=HaberdasherService())
app = TwirpASGIApp()
app.add_service(service)
print("Twirp server (mocked) app created. Run with 'uvicorn your_module:app --port=3000' (after code generation).")
# Client usage (demonstrative, requires running server)
# client = haberdasher_twirp.HaberdasherClient("http://localhost:3000")
# try:
# response = client.MakeHat(ctx=Context(), request=haberdasher_pb2.Size(inches=12))
# print(f"Client received: {response.name} {response.color} hat, size {response.size}")
# except TwirpServerException as e:
# print(f"Client error: {e.code} - {e.message}")