223 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
			
		
		
	
	
			223 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
| 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 <https://grpclib.readthedocs.io/en/latest>`_ 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!"
 |