Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/annotating_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ assert bytes(Message(foo=[1, 2])) == b"\x08\x01\x08\x02"

Required fields are [deprecated](https://developers.google.com/protocol-buffers/docs/style#things_to_avoid) in `proto2` and not supported in `proto3`, thus in `pure-protobuf` fields are always optional. `#!python Optional` annotation is accepted for type hinting, but has no functional meaning for `#!python BaseMessage`.

Both traditional `#!python Optional[T]` and modern Python 3.10+ union syntax `#!python T | None` are supported and work identically.

## Default values

In `pure-protobuf` it's developer's responsibility to take care of default values. If encoded message does not contain a particular element, the corresponding field stays unprovided:
Expand Down
9 changes: 8 additions & 1 deletion pure_protobuf/helpers/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
except ImportError:
NoneType = type(None) # type: ignore

try:
from types import UnionType # type: ignore

UNION_TYPES = (Union, UnionType)
except ImportError:
UNION_TYPES = (Union,) # type: ignore


class Sentinel: # pragma: no cover
"""Sentinel object used for defaults."""
Expand All @@ -32,7 +39,7 @@ def extract_repeated(hint: Any) -> tuple[Any, TypeGuard[list]]:

def extract_optional(hint: Any) -> tuple[Any, bool]:
"""Extract a possible optional flag."""
if get_origin(hint) is Union:
if get_origin(hint) in UNION_TYPES:
cleaned_args = tuple(arg for arg in get_args(hint) if arg is not NoneType)
return Union[cleaned_args], True
return hint, False
7 changes: 7 additions & 0 deletions tests/helpers/test_typing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from collections.abc import Iterable
from typing import Any, Optional, Union

Expand Down Expand Up @@ -28,3 +29,9 @@ def test_extract_optional(hint: Any, expected_flag: bool, expected_inner: Any) -
)
def test_extract_repeated(hint: Any, expected_flag: bool, expected_inner: Any) -> None:
assert extract_repeated(hint) == (expected_inner, expected_flag)


@mark.skipif(sys.version_info < (3, 10), reason="Union syntax requires Python 3.10+")
def test_extract_optional_union_syntax() -> None:
assert extract_optional(int | None) == (int, True) # type: ignore
assert extract_optional(str | None) == (str, True) # type: ignore