8000 Merge branch 'master' into types · danielgtaylor/python-betterproto@c84071e · GitHub
[go: up one dir, main page]

Skip to content

Commit c84071e

Browse files
authored
Merge branch 'master' into types
2 parents 13b2c30 + 126b256 commit c84071e

File tree

9 files changed

+139
-49
lines changed

9 files changed

+139
-49
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ jobs:
1616
fail-fast: false
1717
matrix:
1818
os: [Ubuntu, MacOS, Windows]
19-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
19+
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
2020
steps:
21-
- uses: actions/checkout@v3
21+
- uses: actions/checkout@v4
2222

2323
- name: Set up Python ${{ matrix.python-version }}
24-
uses: actions/setup-python@v4
24+
uses: actions/setup-python@v5
2525
with:
2626
python-version: ${{ matrix.python-version }}
2727

2828
- name: Get full Python version
2929
id: full-python-version
3030
shell: bash
31-
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
31+
run: echo "version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")" >> "$GITHUB_OUTPUT"
3232

3333
- name: Install poetry
3434
shell: bash

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,22 @@ message Test {
277277
}
278278
```
279279

280-
You can use `betterproto.which_one_of(message, group_name)` to determine which of the fields was set. It returns a tuple of the field name and value, or a blank string and `None` if unset.
280+
On Python 3.10 and later, you can use a `match` statement to access the provided one-of field, which supports type-checking:
281+
282+
```py
283+
test = Test()
284+
match test:
285+
case Test(on=value):
286+
print(value) # value: bool
287+
case Test(count=value):
288+
print(value) # value: int
289+
case Test(name=value):
290+
print(value) # value: str
291+
case _:
292+
print("No value provided")
293+
```
294+
295+
You can also use `betterproto.which_one_of(message, group_name)` to determine which of the fields was set. It returns a tuple of the field name and value, or a blank string and `None` if unset.
281296

282297
```py
283298
>>> test = Test()
@@ -292,17 +307,11 @@ You can use `betterproto.which_one_of(message, group_name)` to determine which o
292307
>>> test.count = 57
293308
>>> betterproto.which_one_of(test, "foo")
294309
["count", 57]
295-
>>> test.on
296-
False
297310

298311
# Default (zero) values also work.
299312
>>> test.name = ""
300313
>>> betterproto.which_one_of(test, "foo")
301314
["name", ""]
302-
>>> test.count
303-
0
304-
>>> test.on
305-
False
306315
```
307316

308317
Again this is a little different than the official Google code generator:

benchmarks/benchmarks.py

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,32 @@
66

77
@dataclass
88
class TestMessage(betterproto.Message):
9-
foo: int = betterproto.uint32_field(0)
10-
bar: str = betterproto.string_field(1)
11-
baz: float = betterproto.float_field(2)
9+
foo: int = betterproto.uint32_field(1)
10+
bar: str = betterproto.string_field(2)
11+
baz: float = betterproto.float_field(3)
1212

1313

1414
@dataclass
1515
class TestNestedChildMessage(betterproto.Message):
16-
str_key: str = betterproto.string_field(0)
17-
bytes_key: bytes = betterproto.bytes_field(1)
18-
bool_key: bool = betterproto.bool_field(2)
19-
float_key: float = betterproto.float_field(3)
20-
int_key: int = betterproto.uint64_field(4)
16+
str_key: str = betterproto.string_field(1)
17+
bytes_key: bytes = betterproto.bytes_field(2)
18+
bool_key: bool = betterproto.bool_field(3)
19+
float_key: float = betterproto.float_field(4)
20+
int_key: int = betterproto.uint64_field(5)
2121

2222

2323
@dataclass
2424
class TestNestedMessage(betterproto.Message):
25-
foo: TestNestedChildMessage = betterproto.message_field(0)
26-
bar: TestNestedChildMessage = betterproto.message_field(1)
27-
baz: TestNestedChildMessage = betterproto.message_field(2)
25+
foo: TestNestedChildMessage = betterproto.message_field(1)
26+
bar: TestNestedChildMessage = betterproto.message_field(2)
27+
baz: TestNestedChildMessage = betterproto.message_field(3)
2828

2929

3030
@dataclass
3131
class TestRepeatedMessage(betterproto.Message):
32-
foo_repeat: List[str] = betterproto.string_field(0)
33-
bar_repeat: List[int] = betterproto.int64_field(1)
34-
baz_repeat: List[bool] = betterproto.bool_field(2)
32+
foo_repeat: List[str] = betterproto.string_field(1)
33+
bar_repeat: List[int] = betterproto.int64_field(2)
34+
baz_repeat: List[bool] = betterproto.bool_field(3)
3535

3636

3737
class BenchMessage:
@@ -44,25 +44,14 @@ def setup(self):
4444
self.instance_filled_bytes = bytes(self.instance_filled)
4545
self.instance_filled_nested = TestNestedMessage(
4646
TestNestedChildMessage("foo", bytearray(b"test1"), True, 0.1234, 500),
47-
TestNestedChildMessage("bar", bytearray(b"test2"), True, 3.1415, -302),
47+
TestNestedChildMessage("bar", bytearray(b"test2"), True, 3.1415, 302),
4848
TestNestedChildMessage("baz", bytearray(b"test3"), False, 1e5, 300),
4949
)
5050
self.instance_filled_nested_bytes = bytes(self.instance_filled_nested)
5151
self.instance_filled_repeated = TestRepeatedMessage(
52-
[
53-
"test1",
54-
"test2",
55-
"test3",
56-
"test4",
57-
"test5",
58-
"test6",
59-
"test7",
60-
"test8",
61-
"test9",
62-
"test10",
63-
],
64-
[2, -100, 0, 500000, 600, -425678, 1000000000, -300, 1, -694214214466],
65-
[True, False, False, False, True, True, False, True, False, False],
52+
[f"test{i}" for i in range(1_000)],
53+
[(i - 500) ** 3 for i in range(1_000)],
54+
[i % 2 == 0 for i in range(1_000)],
6655
)
6756
self.instance_filled_repeated_bytes = bytes(self.instance_filled_repeated)
6857

@@ -71,9 +60,9 @@ def time_overhead(self):
7160

7261
@dataclass
7362
class Message(betterproto.Message):
74-
foo: int = betterproto.uint32_field(0)
75-
bar: str = betterproto.string_field(1)
76-
baz: float = betterproto.float_field(2)
63+
foo: int = betterproto.uint32_field(1)
64+
bar: str = betterproto.string_field(2)
65+
baz: float = betterproto.float_field(3)
7766

7867
def time_instantiation(self):
7968
"""Time instantiation"""

poetry.lock

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jinja2 = { version = ">=3.0.3", optional = true }
2020
python-dateutil = "^2.8"
2121
isort = {version = "^5.11.5", optional = true}
2222
typing-extensions = "^4.7.1"
23+
betterproto-rust-codec = { version = "0.1.0", optional = true }
2324

2425
[tool.poetry.group.dev]
2526
optional = true
@@ -52,6 +53,7 @@ protoc-gen-python_betterproto = "betterproto.plugin:main"
5253

5354
[tool.poetry.extras]
5455
compiler = ["black", "isort", "jinja2"]
56+
rust-codec = ["betterproto-rust-codec"]
5557

5658

5759
# Dev workflow tasks

src/betterproto/__init__.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ def __post_init__(self) -> None:
732732
group_current.setdefault(meta.group)
733733

734734
value = self.__raw_get(field_name)
735-
if value != PLACEHOLDER and not (meta.optional and value is None):
735+
if value is not PLACEHOLDER and not (meta.optional and value is None):
736736
# Found a non-sentinel value
737737
all_sentinel = False
738738

@@ -1862,6 +1862,23 @@ def _validate_field_groups(cls, values):
18621862
{}
18631863
) # HACK to avoid typing.get_type_hints breaking because we have to manually pass globals
18641864

1865+
# monkey patch (de-)serialization functions of class `Message`
1866+
# with functions from `betterproto-rust-codec` if available
1867+
try:
1868+
import betterproto_rust_codec
1869+
1870+
def __parse_patch(self: T, data: bytes) -> T:
1871+
betterproto_rust_codec.deserialize(self, data)
1872+
return self
1873+
1874+
def __bytes_patch(self) -> bytes:
1875+
return betterproto_rust_codec.serialize(self)
1876+
1877+
Message.parse = __parse_patch
1878+
Message.__bytes__ = __bytes_patch
1879+
except ModuleNotFoundError:
1880+
pass
1881+
18651882

18661883
def serialized_on_wire(message: Message) -> bool:
18671884
"""

src/betterproto/lib/google/protobuf/__init__.py

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/betterproto/plugin/models.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,7 @@ def get_comment(
143143
pad = " " * indent
144144
for sci_loc in proto_file.source_code_info.location:
145145
if list(sci_loc.path) == path and sci_loc.leading_comments:
146-
lines = textwrap.wrap(
147-
sci_loc.leading_comments.strip().replace("\n", ""), width=79 - indent
148-
)
149-
146+
lines = sci_loc.leading_comments.strip().split("\n")
150147
# This is a field, message, enum, service, or method
151148
if len(lines) == 1 and len(lines[0]) < 79 - indent - 6:
152149
lines[0] = lines[0].strip('"')

tests/test_struct.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import json
2+
3+
from betterproto.lib.google.protobuf import Struct
4+
5+
6+
def test_struct_roundtrip():
7+
data = {
8+
"foo": "bar",
9+
"baz": None,
10+
"quux": 123,
11+
"zap": [1, {"two": 3}, "four"],
12+
}
13+
data_json = json.dumps(data)
14+
15+
struct_from_dict = Struct().from_dict(data)
16+
assert struct_from_dict.fields == data
17+
assert struct_from_dict.to_dict() == data
18+
assert struct_from_dict.to_json() == data_json
19+
20+
struct_from_json = Struct().from_json(data_json)
21+
assert struct_from_json.fields == data
22+
assert struct_from_json.to_dict() == data
23+
assert struct_from_json == struct_from_dict
24+
assert struct_from_json.to_json() == data_json

0 commit comments

Comments
 (0)
0