Getting Started =============== Installation ++++++++++++ Installation from PyPI is as simple as running: .. code-block:: sh python3 -m pip install -U betterproto If you are using Windows, then the following should be used instead: .. code-block:: sh py -3 -m pip install -U betterproto To include the protoc plugin, install betterproto[compiler] instead of betterproto, e.g. .. code-block:: sh python3 -m pip install -U "betterproto[compiler]" Compiling proto files +++++++++++++++++++++ Given you installed the compiler and have a proto file, e.g ``example.proto``: .. code-block:: proto syntax = "proto3"; package hello; // Greeting represents a message you can tell a user. message Greeting { string message = 1; } To compile the proto you would run the following: You can run the following to invoke protoc directly: .. code-block:: sh mkdir hello protoc -I . --python_betterproto_out=lib example.proto or run the following to invoke protoc via grpcio-tools: .. code-block:: sh pip install grpcio-tools python -m grpc_tools.protoc -I . --python_betterproto_out=lib example.proto This will generate ``lib/__init__.py`` which looks like: .. code-block:: python # Generated by the protocol buffer compiler. DO NOT EDIT! # sources: example.proto # plugin: python-betterproto from dataclasses import dataclass import betterproto @dataclass class Greeting(betterproto.Message): """Greeting represents a message you can tell a user.""" message: str = betterproto.string_field(1) Then to use it: .. code-block:: python >>> from lib import Greeting >>> test = Greeting() >>> test Greeting(message='') >>> test.message = "Hey!" >>> test Greeting(message="Hey!") >>> bytes(test) b'\n\x04Hey!' >>> Greeting().parse(serialized) Greeting(message="Hey!") Async gRPC Support ++++++++++++++++++ The generated code includes `grpclib `_ based stub (client and server) classes for rpc services declared in the input proto files. It is enabled by default. Given a service definition similar to the one below: .. code-block:: proto syntax = "proto3"; package echo; message EchoRequest { string value = 1; // Number of extra times to echo uint32 extra_times = 2; } message EchoResponse { repeated string values = 1; } message EchoStreamResponse { string value = 1; } service Echo { rpc Echo(EchoRequest) returns (EchoResponse); rpc EchoStream(EchoRequest) returns (stream EchoStreamResponse); } The generated client can be used like so: .. code-block:: python import asyncio from grpclib.client import Channel import echo async def main(): channel = Channel(host="127.0.0.1", port=50051) service = echo.EchoStub(channel) response = await service.echo(value="hello", extra_times=1) print(response) async for response in service.echo_stream(value="hello", extra_times=1): print(response) # don't forget to close the channel when you're done! channel.close() asyncio.run(main()) # python 3.7 only # outputs EchoResponse(values=['hello', 'hello']) EchoStreamResponse(value='hello') EchoStreamResponse(value='hello') The server-facing stubs can be used to implement a Python gRPC server. To use them, simply subclass the base class in the generated files and override the service methods: .. code-block:: python from echo import EchoBase from grpclib.server import Server from typing import AsyncIterator class EchoService(EchoBase): async def echo(self, value: str, extra_times: int) -> "EchoResponse": return value async def echo_stream( self, value: str, extra_times: int ) -> AsyncIterator["EchoStreamResponse"]: for _ in range(extra_times): yield value async def start_server(): HOST = "127.0.0.1" PORT = 1337 server = Server([EchoService()]) await server.start(HOST, PORT) await server.serve_forever() JSON ++++ Message objects include :meth:`betterproto.Message.to_json` and :meth:`betterproto.Message.from_json` methods for JSON (de)serialisation, and :meth:`betterproto.Message.to_dict`, :meth:`betterproto.Message.from_dict` for converting back and forth from JSON serializable dicts. For compatibility the default is to convert field names to :attr:`betterproto.Casing.CAMEL`. You can control this behavior by passing a different casing value, e.g: .. code-block:: python @dataclass class MyMessage(betterproto.Message): a_long_field_name: str = betterproto.string_field(1) >>> test = MyMessage(a_long_field_name="Hello World!") >>> test.to_dict(betterproto.Casing.SNAKE) {"a_long_field_name": "Hello World!"} >>> test.to_dict(betterproto.Casing.CAMEL) {"aLongFieldName": "Hello World!"} >>> test.to_json(indent=2) '{\n "aLongFieldName": "Hello World!"\n}' >>> test.from_dict({"aLongFieldName": "Goodbye World!"}) >>> test.a_long_field_name "Goodbye World!"