Skip to content
This repository was archived by the owner on Jun 9, 2025. It is now read-only.

Commit d067d0a

Browse files
committed
Support durations
1 parent 20eecb3 commit d067d0a

File tree

4 files changed

+55
-2
lines changed

4 files changed

+55
-2
lines changed

src/betterproto2_compiler/known_types/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@
1919
Timestamp.from_wrapped,
2020
Timestamp.to_wrapped,
2121
],
22-
("google.protobuf", "Duration"): [Duration.from_timedelta, Duration.to_timedelta, Duration.delta_to_json],
22+
("google.protobuf", "Duration"): [
23+
Duration.from_timedelta,
24+
Duration.to_timedelta,
25+
Duration.delta_to_json,
26+
Duration.from_dict,
27+
Duration.to_dict,
28+
Duration.from_wrapped,
29+
Duration.to_wrapped,
30+
],
2331
("google.protobuf", "BoolValue"): [BoolValue.from_wrapped, BoolValue.to_wrapped],
2432
("google.protobuf", "StringValue"): [StringValue.from_wrapped, StringValue.to_wrapped],
2533
}

src/betterproto2_compiler/known_types/duration.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import datetime
2+
import re
3+
import typing
4+
5+
import betterproto2
26

37
from betterproto2_compiler.lib.google.protobuf import Duration as VanillaDuration
48

@@ -23,3 +27,44 @@ def delta_to_json(delta: datetime.timedelta) -> str:
2327
while len(parts[1]) not in (3, 6, 9):
2428
parts[1] = f"{parts[1]}0"
2529
return f"{'.'.join(parts)}s"
30+
31+
# TODO typing
32+
@classmethod
33+
def from_dict(cls, value):
34+
if isinstance(value, str):
35+
if not re.match(r"^\d+(\.\d+)?s$", value):
36+
raise ValueError(f"Invalid duration string: {value}")
37+
38+
seconds = float(value[:-1])
39+
return Duration(seconds=int(seconds), nanos=int((seconds - int(seconds)) * 1e9))
40+
41+
return super().from_dict(value)
42+
43+
# TODO typing
44+
def to_dict(
45+
self,
46+
*,
47+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
48+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
49+
include_default_values: bool = False,
50+
) -> dict[str, typing.Any] | typing.Any:
51+
# If the output format is PYTHON, we should have kept the wraped type without building the real class
52+
assert output_format == betterproto2.OutputFormat.PROTO_JSON
53+
54+
assert 0 <= self.nanos < 1e9
55+
56+
if self.nanos == 0:
57+
return f"{self.seconds}s"
58+
59+
nanos = f"{self.nanos:09d}".rstrip("0")
60+
if len(nanos) < 3:
61+
nanos += "0" * (3 - len(nanos))
62+
63+
return f"{self.seconds}.{nanos}s"
64+
65+
@staticmethod
66+
def from_wrapped(wrapped: datetime.timedelta) -> "Duration":
67+
return Duration.from_timedelta(wrapped)
68+
69+
def to_wrapped(self) -> datetime.timedelta:
70+
return self.to_timedelta()

src/betterproto2_compiler/known_types/timestamp.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def to_dict(
7171
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
7272
include_default_values: bool = False,
7373
) -> dict[str, typing.Any] | typing.Any:
74-
print("ok")
7574
# If the output format is PYTHON, we should have kept the wraped type without building the real class
7675
assert output_format == betterproto2.OutputFormat.PROTO_JSON
7776

src/betterproto2_compiler/templates/header.py.j2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ __all__ = (
1818
{%- endfor -%}
1919
)
2020

21+
import re
2122
import builtins
2223
import datetime
2324
import dateutil.parser

0 commit comments

Comments
 (0)