Description
I absolutely love betterproto but some of my protobuf files are very complicated with many fields and nested structures and I need to pass a lot of data around to a lot of different places. The objects I use to pass those around are, naturally, the dataclasses betterproto provides. However, betterproto splits these objects up so I end up with code that looks like
class VolService(VolServiceBase):
async def run_vol_pipeline(
self,
oc_key: "OcKey", # nested
source: str,
source_topic: str,
cycle_id: str,
cycle_time: datetime,
chain_size: int,
cycle_size: int,
priced_count: int,
unavailable_count: int,
bad_count: int,
snapshots: Optional[List["OptionSnapshot"]], # repeated nested
) -> AsyncIterator["PyvolResult"]:
req = PyvolRequest(
oc_key,
source,
source_topic,
cycle_id,
cycle_time,
chain_size,
cycle_size,
priced_count,
unavailable_count,
bad_count,
snapshots,
)
whereas if you pass the object the method signature is cleaner and users can easily extract fields anywhere down the call stack.
class VolService(VolServiceBase):
async def run_vol_pipeline(self, request: PyvolRequest) -> AsyncIterator["PyvolResult"]:
...
This is actually how grpclib works! I also feel the same way about the clients. Why not allow us to pass a message object directly instead of having to do something like
async def query(req: fx.FxRequest) -> fx.FxResponse:
service = fx.FxServiceStub(channel) # the service stub
asdict = {f.name: getattr(req, f.name) for f in dataclasses.fields(req)}
return await service.run_fx_pipeline(**asdict)
It gives the user more control about how they want to handle data. It's fully typed so we don't lose anything.