danielgtaylor / danielgtaylor/python-betterproto

Shorter optional fields

Open
#499 0 comments 0 reactions 0 assignees View on GitHub
enhancement medium
Dominant language
Python
Stars
1.8k
Forks
234
PR merge metrics
No merged PRs in 30d

Description

Optional fields generate very long and repetitive code.

The idea is to use shorter and simpler code, and internally consider it as a field having `optional=True, group=f"_{field_name}"`.

Related to #459.

### Proto file
```proto
syntax = "proto3";

message Foo {
optional uint32 bar = 1;
};
```

### Currently generated code
```py
from dataclasses import dataclass

import betterproto

@dataclass(eq=False, repr=False)
class Foo(betterproto.Message):
bar: Optional[int] = betterproto.uint32_field(
1, optional=True, group="_bar"
)
```

### Idea A
Read the type-hint of the field, and if it's a union of a type with `None`, consider it an optional field.

This might be a bad idea if unions of a type + `None` are used for something other than optional fields.

An example implementation of detecting optional fields can be found here: [`cattrs/converters.py`](https://github.com/python-attrs/cattrs/blob/e1db1f24fc884e152f8d19ee02de24d87e97e55a/src/cattrs/converters.py#L109-L110).
```py
from dataclasses import dataclass
from typing import Union, Optional

import betterproto

@dataclass(eq=False, repr=False)
class Foo(betterproto.Message):
bar: Optional[int] = betterproto.uint32_field(1)
```

### Idea B
Use a custom type-hint that indicates a field is optional, and is correctly understood by type-checkers as `Optional`.
```py
from dataclasses import dataclass

import betterproto

@dataclass(eq=False, repr=False)
class Foo(betterproto.Message):
bar: betterproto.OptionalField[int] = betterproto.uint32_field(1)
```

It could possibly be implemented something like this:
[`Annotated`](https://docs.python.org/3/library/typing.html#typing.Annotated)
```py
from typing import Optional, TypeVar
from typing_extensions import Annotated

T = TypeVar("T")

optional_metadata = object() # or another sentinel

OptionalField = Annotated[Optional[T], optional_metadata]
```

### Idea C
Automatically add `group=f"_{field_name}"` if `optional=True` and no `group` specified.
```py
from dataclasses import dataclass

import betterproto

@dataclass(eq=False, repr=False)
class Foo(betterproto.Message):
bar: Optional[int] = betterproto.uint32_field(1, optional=True)
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.