gRPC Support
Fory can generate Python gRPC service companions for schemas that define
services. The generated modules use grpcio for transport and use Fory to
serialize request and response objects.
Use this mode when every RPC peer is generated from the same Fory IDL, protobuf IDL, or FlatBuffers IDL and you want gRPC transport semantics with Fory payload encoding. Use standard protobuf gRPC code generation when clients or tools must consume protobuf message bytes directly.
Generated Python companions currently target the synchronous grpcio API. Use
regular def servicer methods, grpc.server(...), standard grpc.Channel
instances, and Python iterators or generators for streaming RPCs. The generated
stub accepts any channel configured by your application. The compiler does not
generate grpc.aio stubs or service bases, so do not implement generated
servicer methods as async def unless you add a custom adapter outside the
generated companion. Python gRPC async support based on grpc.aio will be
available in the next Fory release.
Install Dependencies
Install grpcio alongside pyfory. The generated companion imports grpc, but
pyfory does not add gRPC as a hard dependency.
pip install pyfory grpcio
Define a Service
Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
rpc_service definitions. A Fory IDL service looks like this:
package demo.greeter;
message HelloRequest {
string name = 1;
}
message HelloReply {
string reply = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
Generate Python model and gRPC companion code with --grpc:
foryc service.fdl --python_out=./generated/python --grpc
For this schema, the Python generator emits:
| File | Purpose |
|---|---|
demo_greeter.py | Fory dataclasses and registration helpers |
demo_greeter_grpc.py | grpcio stub, servicer base, and registrar |
The module name is derived from the Fory package by replacing dots with
underscores. A schema with no package uses generated.py and
generated_grpc.py.
Implement a Server
Subclass the generated servicer and register it with a normal grpcio server.
Generated Python method names use snake_case, while the gRPC wire path keeps the
original IDL method name.
from concurrent import futures
import grpc
import demo_greeter
import demo_greeter_grpc
class Greeter(demo_greeter_grpc.GreeterServicer):
def say_hello(self, request, context):
return demo_greeter.HelloReply(reply=f"Hello, {request.name}")
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=8))
demo_greeter_grpc.add_servicer(Greeter(), server)
server.add_insecure_port("[::]:50051")
server.start()
server.wait_for_termination()
if __name__ == "__main__":
serve()
Generated request and response types are serialized by the generated companion, so service implementations do not perform manual Fory registration.
Create a Client
Use the generated stub with a normal grpcio channel. Production clients
usually pass a TLS/auth-configured channel:
import grpc
import demo_greeter
import demo_greeter_grpc
def main():
credentials = grpc.ssl_channel_credentials()
with grpc.secure_channel("api.example.com:443", credentials) as channel:
stub = demo_greeter_grpc.GreeterStub(channel)
reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory"))
print(reply.reply)
if __name__ == "__main__":
main()
For local tests and development, an insecure channel can be used explicitly:
# Test-only channel. Use a TLS/auth-configured grpc.Channel in production.
with grpc.insecure_channel("localhost:50051") as channel:
stub = demo_greeter_grpc.GreeterStub(channel)
grpcio still owns channel options, credentials, deadlines, metadata, retries,
and interceptors.
Streaming RPCs
Fory service definitions can use unary, server-streaming, client-streaming, and bidirectional streaming RPC shapes:
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}
Generated Python code follows grpcio conventions:
| IDL shape | Servicer method shape | Stub method shape |
|---|---|---|
rpc A (Req) returns (Res) | returns one response object | returns one response object |
rpc A (Req) returns (stream Res) | yields response objects | returns an iterator of responses |
rpc A (stream Req) returns (Res) | consumes an iterator and returns a response | accepts an iterator of requests |
rpc A (stream Req) returns (stream Res) | consumes and yields iterators | accepts and returns iterators |
Servicer methods use snake_case names, while generated descriptors preserve the exact IDL service and method names for the gRPC path.
Server implementations can use Python iterators directly:
class Greeter(demo_greeter_grpc.GreeterServicer):
def lots_of_replies(self, request, context):
yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}")
def lots_of_greetings(self, request_iterator, context):
names = [request.name for request in request_iterator]
return demo_greeter.HelloReply(reply=", ".join(names))
def chat(self, request_iterator, context):
for request in request_iterator:
yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
Generated clients use the standard grpcio streaming call shapes:
credentials = grpc.ssl_channel_credentials()
with grpc.secure_channel("api.example.com:443", credentials) as channel:
stub = demo_greeter_grpc.GreeterStub(channel)
for reply in stub.lots_of_replies(
demo_greeter.HelloRequest(name="Fory")
):
print(reply.reply)
def greeting_requests():
yield demo_greeter.HelloRequest(name="Ada")
yield demo_greeter.HelloRequest(name="Grace")
summary = stub.lots_of_greetings(greeting_requests())
print(summary.reply)
def chat_requests():
yield demo_greeter.HelloRequest(name="Fory")
yield demo_greeter.HelloRequest(name="RPC")
for reply in stub.chat(chat_requests()):
print(reply.reply)
Operations
The generated service companion only supplies Fory serialization callbacks.
Operational behavior remains standard grpcio behavior:
- Deadlines and cancellations
- TLS and authentication credentials
- Client and server interceptors
- Status codes, details, and metadata
- Channel and server lifecycle
- Thread pool sizing for synchronous servers
Troubleshooting
ModuleNotFoundError: No module named 'grpc'
Install grpcio in the environment that runs the generated service module:
pip install grpcio
TypeError: Unsupported gRPC servicer type
Pass an instance of the generated servicer subclass to
demo_greeter_grpc.add_servicer(...). If the schema contains multiple services,
the generated registrar accepts only the matching generated servicer types.
UNIMPLEMENTED
Confirm that the generated servicer was registered with the server, and that the client and server were generated from the same package, service, and method names.
Protobuf Clients Cannot Decode the Service
Fory gRPC companions do not use protobuf wire encoding for messages. Use a Fory-generated client for Fory-generated services, or provide a separate protobuf service endpoint for generic protobuf clients.