diff --git a/docs/getting-started.md b/docs/getting-started.md index c0925193..f5a8546e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,7 +10,6 @@ You can also use it from `grpcio-tools`: pip install grpcio-tools ``` - ## Install `betterproto2_compiler` It is possible to install `betterproto2_compiler` using pip: @@ -19,10 +18,6 @@ It is possible to install `betterproto2_compiler` using pip: pip install betterproto2_compiler ``` -!!! warning - The compiler needs Python 3.10, 3.11 or 3.12. Don't worry! The generated code will be compatible with all Python versions from Python 3.8 to Python 3.13. - - ## Compile a proto file Create the following `example.proto` file. @@ -35,6 +30,10 @@ package helloworld; message HelloWorld { string message = 1; } + +service HelloService { + rpc SayHello (HelloWorld) returns (HelloWorld); +} ``` You should now be able to compile it using: @@ -50,3 +49,40 @@ If you installed `protoc` with `grpc-tools`, the command will be: mkdir lib python -m grpc.tools.protoc -I . --python_betterproto2_out=lib example.proto ``` + +### Service compilation + +#### Clients + +By default, for each service, betterproto will generate a synchronous client. Both synchronous and asynchronous clients +are supported. + + - Synchronous clients rely on the `grpcio` package. Make sure to enable the `grpcio` extra package when installing + betterproto2 to use them. + - Asynchronous clients rely on the `grpclib` package. Make sure to enable the `grpclib` extra package when installing + betterproto2 to use them. + +To choose which clients to generate, use the `client_generation` option of betterproto. It supports the following +values: + + - `none`: Clients are not generated. + - `sync`: Only synchronous clients are generated. + - `async`: Only asynchronous clients are generated. + - `sync_async`: Both synchronous and asynchronous clients are generated. + Asynchronous clients are generated with the Async suffix. + - `async_sync`: Both synchronous and asynchronous clients are generated. + Synchronous clients are generated with the Sync suffix. + - `sync_async_no_default`: Both synchronous and asynchronous clients are generated. + Synchronous clients are generated with the Sync suffix, and asynchronous clients are generated with the Async + suffix. + +For example, `protoc -I . --python_betterproto2_out=lib example.proto --python_betterproto2_opt=client_generation=async` +will only generate asynchronous clients. + +#### Servers + +By default, betterproto will not generate server base classes. To enable them, set the `server_generation` option to +`async` with `--python_betterproto2_opt=server_generation=async`. + +These base classes will be asynchronous and rely on `grpclib`. To use them, make sure to install `betterproto2` with the +`grpclib` extra package. diff --git a/src/betterproto2_compiler/plugin/parser.py b/src/betterproto2_compiler/plugin/parser.py index 67405006..72435bb1 100644 --- a/src/betterproto2_compiler/plugin/parser.py +++ b/src/betterproto2_compiler/plugin/parser.py @@ -14,7 +14,7 @@ CodeGeneratorResponseFeature, CodeGeneratorResponseFile, ) -from betterproto2_compiler.settings import ClientGeneration, Settings +from betterproto2_compiler.settings import ClientGeneration, ServerGeneration, Settings from .compiler import outputfile_compiler from .models import ( @@ -62,22 +62,27 @@ def _traverse( def get_settings(plugin_options: list[str]) -> Settings: # Synchronous clients are suitable for most users client_generation = ClientGeneration.SYNC + server_generation = ServerGeneration.NONE for opt in plugin_options: if opt.startswith("client_generation="): name = opt.split("=")[1] - - # print(ClientGeneration.__members__, file=sys.stderr) - # print([member.value for member in ClientGeneration]) - try: client_generation = ClientGeneration(name) except ValueError: raise ValueError(f"Invalid client_generation option: {name}") + if opt.startswith("server_generation="): + name = opt.split("=")[1] + try: + server_generation = ServerGeneration(name) + except ValueError: + raise ValueError(f"Invalid server_generation option: {name}") + return Settings( pydantic_dataclasses="pydantic_dataclasses" in plugin_options, client_generation=client_generation, + server_generation=server_generation, ) diff --git a/src/betterproto2_compiler/settings.py b/src/betterproto2_compiler/settings.py index fc230875..a5269939 100644 --- a/src/betterproto2_compiler/settings.py +++ b/src/betterproto2_compiler/settings.py @@ -20,17 +20,17 @@ class ClientGeneration(StrEnum): SYNC_ASYNC = "sync_async" """Both synchronous and asynchronous clients are generated. - The asynchronous client is generated with the Async suffix.""" + Asynchronous clients are generated with the Async suffix.""" ASYNC_SYNC = "async_sync" """Both synchronous and asynchronous clients are generated. - The synchronous client is generated with the Sync suffix.""" + Synchronous clients are generated with the Sync suffix.""" SYNC_ASYNC_NO_DEFAULT = "sync_async_no_default" """Both synchronous and asynchronous clients are generated. - The synchronous client is generated with the Sync suffix, and the asynchronous client is generated with the Async + Synchronous clients are generated with the Sync suffix, and asynchronous clients are generated with the Async suffix.""" @property @@ -60,8 +60,14 @@ def is_async_prefixed(self) -> bool: return self in {ClientGeneration.SYNC_ASYNC, ClientGeneration.SYNC_ASYNC_NO_DEFAULT} +class ServerGeneration(StrEnum): + NONE = "none" + ASYNC = "async" + + @dataclass class Settings: pydantic_dataclasses: bool client_generation: ClientGeneration + server_generation: ServerGeneration diff --git a/src/betterproto2_compiler/templates/template.py.j2 b/src/betterproto2_compiler/templates/template.py.j2 index 1281e679..4f89d832 100644 --- a/src/betterproto2_compiler/templates/template.py.j2 +++ b/src/betterproto2_compiler/templates/template.py.j2 @@ -102,6 +102,7 @@ default_message_pool.register_message("{{ output_file.package }}", "{{ message.p {{ i }} {% endfor %} +{% if output_file.settings.server_generation == "async" %} {% for _, service in output_file.services|dictsort(by="key") %} class {{ service.py_name }}Base(ServiceBase): {% if service.comment %} @@ -170,3 +171,4 @@ class {{ service.py_name }}Base(ServiceBase): } {% endfor %} +{% endif %} diff --git a/tests/util.py b/tests/util.py index d4772c7e..af644f2e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -27,12 +27,7 @@ def get_directories(path): yield from directories -async def protoc( - path: str | Path, - output_dir: str | Path, - reference: bool = False, - pydantic_dataclasses: bool = False, -): +async def protoc(path: str | Path, output_dir: str | Path, reference: bool = False, pydantic_dataclasses: bool = False): path: Path = Path(path).resolve() output_dir: Path = Path(output_dir).resolve() python_out_option: str = "python_betterproto2_out" if not reference else "python_out"