Skip to content

Commit 7fd48f0

Browse files
drzbidaeigenein
authored andcommitted
🐛 Support Python 3.10+ union syntax (T | None), fixes #193
1 parent 4104eea commit 7fd48f0

File tree

3 files changed

+17
-1
lines changed

3 files changed

+17
-1
lines changed

docs/annotating_fields.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ assert bytes(Message(foo=[1, 2])) == b"\x08\x01\x08\x02"
8484

8585
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`.
8686

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

8991
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:

pure_protobuf/helpers/_typing.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
except ImportError:
99
NoneType = type(None) # type: ignore
1010

11+
try:
12+
from types import UnionType # type: ignore
13+
14+
UNION_TYPES = (Union, UnionType)
15+
except ImportError:
16+
UNION_TYPES = (Union,) # type: ignore
17+
1118

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

3340
def extract_optional(hint: Any) -> tuple[Any, bool]:
3441
"""Extract a possible optional flag."""
35-
if get_origin(hint) is Union:
42+
if get_origin(hint) in UNION_TYPES:
3643
cleaned_args = tuple(arg for arg in get_args(hint) if arg is not NoneType)
3744
return Union[cleaned_args], True
3845
return hint, False

tests/helpers/test_typing.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from collections.abc import Iterable
23
from typing import Any, Optional, Union
34

@@ -28,3 +29,9 @@ def test_extract_optional(hint: Any, expected_flag: bool, expected_inner: Any) -
2829
)
2930
def test_extract_repeated(hint: Any, expected_flag: bool, expected_inner: Any) -> None:
3031
assert extract_repeated(hint) == (expected_inner, expected_flag)
32+
33+
34+
@mark.skipif(sys.version_info < (3, 10), reason="Union syntax requires Python 3.10+")
35+
def test_extract_optional_union_syntax() -> None:
36+
assert extract_optional(int | None) == (int, True) # type: ignore
37+
assert extract_optional(str | None) == (str, True) # type: ignore

0 commit comments

Comments
 (0)