betterproto2-compiler 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- betterproto2_compiler/compile/importing.py +5 -25
- betterproto2_compiler/known_types/__init__.py +98 -2
- betterproto2_compiler/known_types/duration.py +45 -0
- betterproto2_compiler/known_types/google_values.py +231 -0
- betterproto2_compiler/known_types/timestamp.py +39 -0
- betterproto2_compiler/lib/google/protobuf/__init__.py +418 -603
- betterproto2_compiler/lib/google/protobuf/compiler/__init__.py +25 -14
- betterproto2_compiler/plugin/models.py +55 -23
- betterproto2_compiler/plugin/parser.py +10 -5
- betterproto2_compiler/settings.py +9 -3
- betterproto2_compiler/templates/header.py.j2 +2 -0
- betterproto2_compiler/templates/template.py.j2 +2 -0
- {betterproto2_compiler-0.4.0.dist-info → betterproto2_compiler-0.5.0.dist-info}/METADATA +2 -2
- {betterproto2_compiler-0.4.0.dist-info → betterproto2_compiler-0.5.0.dist-info}/RECORD +17 -16
- {betterproto2_compiler-0.4.0.dist-info → betterproto2_compiler-0.5.0.dist-info}/LICENSE.md +0 -0
- {betterproto2_compiler-0.4.0.dist-info → betterproto2_compiler-0.5.0.dist-info}/WHEEL +0 -0
- {betterproto2_compiler-0.4.0.dist-info → betterproto2_compiler-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -5,7 +5,7 @@ from typing import (
|
|
5
5
|
TYPE_CHECKING,
|
6
6
|
)
|
7
7
|
|
8
|
-
from betterproto2_compiler.
|
8
|
+
from betterproto2_compiler.known_types import WRAPPED_TYPES
|
9
9
|
from betterproto2_compiler.settings import Settings
|
10
10
|
|
11
11
|
from ..casing import safe_snake_case
|
@@ -14,18 +14,6 @@ from .naming import pythonize_class_name
|
|
14
14
|
if TYPE_CHECKING:
|
15
15
|
from ..plugin.models import PluginRequestCompiler
|
16
16
|
|
17
|
-
WRAPPER_TYPES: dict[str, type] = {
|
18
|
-
".google.protobuf.DoubleValue": google_protobuf.DoubleValue,
|
19
|
-
".google.protobuf.FloatValue": google_protobuf.FloatValue,
|
20
|
-
".google.protobuf.Int32Value": google_protobuf.Int32Value,
|
21
|
-
".google.protobuf.Int64Value": google_protobuf.Int64Value,
|
22
|
-
".google.protobuf.UInt32Value": google_protobuf.UInt32Value,
|
23
|
-
".google.protobuf.UInt64Value": google_protobuf.UInt64Value,
|
24
|
-
".google.protobuf.BoolValue": google_protobuf.BoolValue,
|
25
|
-
".google.protobuf.StringValue": google_protobuf.StringValue,
|
26
|
-
".google.protobuf.BytesValue": google_protobuf.BytesValue,
|
27
|
-
}
|
28
|
-
|
29
17
|
|
30
18
|
def parse_source_type_name(field_type_name: str, request: PluginRequestCompiler) -> tuple[str, str]:
|
31
19
|
"""
|
@@ -73,26 +61,18 @@ def get_type_reference(
|
|
73
61
|
imports: set,
|
74
62
|
source_type: str,
|
75
63
|
request: PluginRequestCompiler,
|
76
|
-
|
64
|
+
wrap: bool = True,
|
77
65
|
settings: Settings,
|
78
66
|
) -> str:
|
79
67
|
"""
|
80
68
|
Return a Python type name for a proto type reference. Adds the import if
|
81
69
|
necessary. Unwraps well known type if required.
|
82
70
|
"""
|
83
|
-
if unwrap:
|
84
|
-
if source_type in WRAPPER_TYPES:
|
85
|
-
wrapped_type = type(WRAPPER_TYPES[source_type]().value)
|
86
|
-
return f"{wrapped_type.__name__} | None"
|
87
|
-
|
88
|
-
if source_type == ".google.protobuf.Duration":
|
89
|
-
return "datetime.timedelta"
|
90
|
-
|
91
|
-
elif source_type == ".google.protobuf.Timestamp":
|
92
|
-
return "datetime.datetime"
|
93
|
-
|
94
71
|
source_package, source_type = parse_source_type_name(source_type, request)
|
95
72
|
|
73
|
+
if wrap and (source_package, source_type) in WRAPPED_TYPES:
|
74
|
+
return WRAPPED_TYPES[(source_package, source_type)]
|
75
|
+
|
96
76
|
current_package: list[str] = package.split(".") if package else []
|
97
77
|
py_package: list[str] = source_package.split(".") if source_package else []
|
98
78
|
py_type: str = pythonize_class_name(source_type)
|
@@ -2,6 +2,17 @@ from collections.abc import Callable
|
|
2
2
|
|
3
3
|
from .any import Any
|
4
4
|
from .duration import Duration
|
5
|
+
from .google_values import (
|
6
|
+
BoolValue,
|
7
|
+
BytesValue,
|
8
|
+
DoubleValue,
|
9
|
+
FloatValue,
|
10
|
+
Int32Value,
|
11
|
+
Int64Value,
|
12
|
+
StringValue,
|
13
|
+
UInt32Value,
|
14
|
+
UInt64Value,
|
15
|
+
)
|
5
16
|
from .timestamp import Timestamp
|
6
17
|
|
7
18
|
# For each (package, message name), lists the methods that should be added to the message definition.
|
@@ -9,6 +20,91 @@ from .timestamp import Timestamp
|
|
9
20
|
# to the template file: they will automatically be removed if not necessary.
|
10
21
|
KNOWN_METHODS: dict[tuple[str, str], list[Callable]] = {
|
11
22
|
("google.protobuf", "Any"): [Any.pack, Any.unpack, Any.to_dict],
|
12
|
-
("google.protobuf", "Timestamp"): [
|
13
|
-
|
23
|
+
("google.protobuf", "Timestamp"): [
|
24
|
+
Timestamp.from_datetime,
|
25
|
+
Timestamp.to_datetime,
|
26
|
+
Timestamp.timestamp_to_json,
|
27
|
+
Timestamp.from_dict,
|
28
|
+
Timestamp.to_dict,
|
29
|
+
Timestamp.from_wrapped,
|
30
|
+
Timestamp.to_wrapped,
|
31
|
+
],
|
32
|
+
("google.protobuf", "Duration"): [
|
33
|
+
Duration.from_timedelta,
|
34
|
+
Duration.to_timedelta,
|
35
|
+
Duration.delta_to_json,
|
36
|
+
Duration.from_dict,
|
37
|
+
Duration.to_dict,
|
38
|
+
Duration.from_wrapped,
|
39
|
+
Duration.to_wrapped,
|
40
|
+
],
|
41
|
+
("google.protobuf", "BoolValue"): [
|
42
|
+
BoolValue.from_dict,
|
43
|
+
BoolValue.to_dict,
|
44
|
+
BoolValue.from_wrapped,
|
45
|
+
BoolValue.to_wrapped,
|
46
|
+
],
|
47
|
+
("google.protobuf", "Int32Value"): [
|
48
|
+
Int32Value.from_dict,
|
49
|
+
Int32Value.to_dict,
|
50
|
+
Int32Value.from_wrapped,
|
51
|
+
Int32Value.to_wrapped,
|
52
|
+
],
|
53
|
+
("google.protobuf", "Int64Value"): [
|
54
|
+
Int64Value.from_dict,
|
55
|
+
Int64Value.to_dict,
|
56
|
+
Int64Value.from_wrapped,
|
57
|
+
Int64Value.to_wrapped,
|
58
|
+
],
|
59
|
+
("google.protobuf", "UInt32Value"): [
|
60
|
+
UInt32Value.from_dict,
|
61
|
+
UInt32Value.to_dict,
|
62
|
+
UInt32Value.from_wrapped,
|
63
|
+
UInt32Value.to_wrapped,
|
64
|
+
],
|
65
|
+
("google.protobuf", "UInt64Value"): [
|
66
|
+
UInt64Value.from_dict,
|
67
|
+
UInt64Value.to_dict,
|
68
|
+
UInt64Value.from_wrapped,
|
69
|
+
UInt64Value.to_wrapped,
|
70
|
+
],
|
71
|
+
("google.protobuf", "FloatValue"): [
|
72
|
+
FloatValue.from_dict,
|
73
|
+
FloatValue.to_dict,
|
74
|
+
FloatValue.from_wrapped,
|
75
|
+
FloatValue.to_wrapped,
|
76
|
+
],
|
77
|
+
("google.protobuf", "DoubleValue"): [
|
78
|
+
DoubleValue.from_dict,
|
79
|
+
DoubleValue.to_dict,
|
80
|
+
DoubleValue.from_wrapped,
|
81
|
+
DoubleValue.to_wrapped,
|
82
|
+
],
|
83
|
+
("google.protobuf", "StringValue"): [
|
84
|
+
StringValue.from_dict,
|
85
|
+
StringValue.to_dict,
|
86
|
+
StringValue.from_wrapped,
|
87
|
+
StringValue.to_wrapped,
|
88
|
+
],
|
89
|
+
("google.protobuf", "BytesValue"): [
|
90
|
+
BytesValue.from_dict,
|
91
|
+
BytesValue.to_dict,
|
92
|
+
BytesValue.from_wrapped,
|
93
|
+
BytesValue.to_wrapped,
|
94
|
+
],
|
95
|
+
}
|
96
|
+
|
97
|
+
# A wrapped type is the type of a message that is automatically replaced by a known Python type.
|
98
|
+
WRAPPED_TYPES: dict[tuple[str, str], str] = {
|
99
|
+
("google.protobuf", "BoolValue"): "bool",
|
100
|
+
("google.protobuf", "Int32Value"): "int",
|
101
|
+
("google.protobuf", "Int64Value"): "int",
|
102
|
+
("google.protobuf", "UInt32Value"): "int",
|
103
|
+
("google.protobuf", "UInt64Value"): "int",
|
104
|
+
("google.protobuf", "FloatValue"): "float",
|
105
|
+
("google.protobuf", "DoubleValue"): "float",
|
106
|
+
("google.protobuf", "StringValue"): "str",
|
107
|
+
("google.protobuf", "BytesValue"): "bytes",
|
108
|
+
("google.protobuf", "Timestamp"): "datetime.datetime",
|
109
|
+
("google.protobuf", "Duration"): "datetime.timedelta",
|
14
110
|
}
|
@@ -1,4 +1,8 @@
|
|
1
1
|
import datetime
|
2
|
+
import re
|
3
|
+
import typing
|
4
|
+
|
5
|
+
import betterproto2
|
2
6
|
|
3
7
|
from betterproto2_compiler.lib.google.protobuf import Duration as VanillaDuration
|
4
8
|
|
@@ -23,3 +27,44 @@ class Duration(VanillaDuration):
|
|
23
27
|
while len(parts[1]) not in (3, 6, 9):
|
24
28
|
parts[1] = f"{parts[1]}0"
|
25
29
|
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 wrapped 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()
|
@@ -0,0 +1,231 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
import betterproto2
|
4
|
+
|
5
|
+
from betterproto2_compiler.lib.google.protobuf import (
|
6
|
+
BoolValue as VanillaBoolValue,
|
7
|
+
BytesValue as VanillaBytesValue,
|
8
|
+
DoubleValue as VanillaDoubleValue,
|
9
|
+
FloatValue as VanillaFloatValue,
|
10
|
+
Int32Value as VanillaInt32Value,
|
11
|
+
Int64Value as VanillaInt64Value,
|
12
|
+
StringValue as VanillaStringValue,
|
13
|
+
UInt32Value as VanillaUInt32Value,
|
14
|
+
UInt64Value as VanillaUInt64Value,
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
class BoolValue(VanillaBoolValue):
|
19
|
+
@staticmethod
|
20
|
+
def from_wrapped(wrapped: bool) -> "BoolValue":
|
21
|
+
return BoolValue(value=wrapped)
|
22
|
+
|
23
|
+
def to_wrapped(self) -> bool:
|
24
|
+
return self.value
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def from_dict(cls, value):
|
28
|
+
if isinstance(value, bool):
|
29
|
+
return BoolValue(value=value)
|
30
|
+
return super().from_dict(value)
|
31
|
+
|
32
|
+
def to_dict(
|
33
|
+
self,
|
34
|
+
*,
|
35
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
36
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
37
|
+
include_default_values: bool = False,
|
38
|
+
) -> dict[str, typing.Any] | typing.Any:
|
39
|
+
return self.value
|
40
|
+
|
41
|
+
|
42
|
+
class Int32Value(VanillaInt32Value):
|
43
|
+
@staticmethod
|
44
|
+
def from_wrapped(wrapped: int) -> "Int32Value":
|
45
|
+
return Int32Value(value=wrapped)
|
46
|
+
|
47
|
+
def to_wrapped(self) -> int:
|
48
|
+
return self.value
|
49
|
+
|
50
|
+
@classmethod
|
51
|
+
def from_dict(cls, value):
|
52
|
+
if isinstance(value, int):
|
53
|
+
return Int32Value(value=value)
|
54
|
+
return super().from_dict(value)
|
55
|
+
|
56
|
+
def to_dict(
|
57
|
+
self,
|
58
|
+
*,
|
59
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
60
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
61
|
+
include_default_values: bool = False,
|
62
|
+
) -> dict[str, typing.Any] | typing.Any:
|
63
|
+
return self.value
|
64
|
+
|
65
|
+
|
66
|
+
class Int64Value(VanillaInt64Value):
|
67
|
+
@staticmethod
|
68
|
+
def from_wrapped(wrapped: int) -> "Int64Value":
|
69
|
+
return Int64Value(value=wrapped)
|
70
|
+
|
71
|
+
def to_wrapped(self) -> int:
|
72
|
+
return self.value
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def from_dict(cls, value):
|
76
|
+
if isinstance(value, int):
|
77
|
+
return Int64Value(value=value)
|
78
|
+
return super().from_dict(value)
|
79
|
+
|
80
|
+
def to_dict(
|
81
|
+
self,
|
82
|
+
*,
|
83
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
84
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
85
|
+
include_default_values: bool = False,
|
86
|
+
) -> dict[str, typing.Any] | typing.Any:
|
87
|
+
return self.value
|
88
|
+
|
89
|
+
|
90
|
+
class UInt32Value(VanillaUInt32Value):
|
91
|
+
@staticmethod
|
92
|
+
def from_wrapped(wrapped: int) -> "UInt32Value":
|
93
|
+
return UInt32Value(value=wrapped)
|
94
|
+
|
95
|
+
def to_wrapped(self) -> int:
|
96
|
+
return self.value
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def from_dict(cls, value):
|
100
|
+
if isinstance(value, int):
|
101
|
+
return UInt32Value(value=value)
|
102
|
+
return super().from_dict(value)
|
103
|
+
|
104
|
+
def to_dict(
|
105
|
+
self,
|
106
|
+
*,
|
107
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
108
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
109
|
+
include_default_values: bool = False,
|
110
|
+
) -> dict[str, typing.Any] | typing.Any:
|
111
|
+
return self.value
|
112
|
+
|
113
|
+
|
114
|
+
class UInt64Value(VanillaUInt64Value):
|
115
|
+
@staticmethod
|
116
|
+
def from_wrapped(wrapped: int) -> "UInt64Value":
|
117
|
+
return UInt64Value(value=wrapped)
|
118
|
+
|
119
|
+
def to_wrapped(self) -> int:
|
120
|
+
return self.value
|
121
|
+
|
122
|
+
@classmethod
|
123
|
+
def from_dict(cls, value):
|
124
|
+
if isinstance(value, int):
|
125
|
+
return UInt64Value(value=value)
|
126
|
+
return super().from_dict(value)
|
127
|
+
|
128
|
+
def to_dict(
|
129
|
+
self,
|
130
|
+
*,
|
131
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
132
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
133
|
+
include_default_values: bool = False,
|
134
|
+
) -> dict[str, typing.Any] | typing.Any:
|
135
|
+
return self.value
|
136
|
+
|
137
|
+
|
138
|
+
class FloatValue(VanillaFloatValue):
|
139
|
+
@staticmethod
|
140
|
+
def from_wrapped(wrapped: float) -> "FloatValue":
|
141
|
+
return FloatValue(value=wrapped)
|
142
|
+
|
143
|
+
def to_wrapped(self) -> float:
|
144
|
+
return self.value
|
145
|
+
|
146
|
+
@classmethod
|
147
|
+
def from_dict(cls, value):
|
148
|
+
if isinstance(value, float):
|
149
|
+
return FloatValue(value=value)
|
150
|
+
return super().from_dict(value)
|
151
|
+
|
152
|
+
def to_dict(
|
153
|
+
self,
|
154
|
+
*,
|
155
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
156
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
157
|
+
include_default_values: bool = False,
|
158
|
+
) -> dict[str, typing.Any] | typing.Any:
|
159
|
+
return self.value
|
160
|
+
|
161
|
+
|
162
|
+
class DoubleValue(VanillaDoubleValue):
|
163
|
+
@staticmethod
|
164
|
+
def from_wrapped(wrapped: float) -> "DoubleValue":
|
165
|
+
return DoubleValue(value=wrapped)
|
166
|
+
|
167
|
+
def to_wrapped(self) -> float:
|
168
|
+
return self.value
|
169
|
+
|
170
|
+
@classmethod
|
171
|
+
def from_dict(cls, value):
|
172
|
+
if isinstance(value, float):
|
173
|
+
return DoubleValue(value=value)
|
174
|
+
return super().from_dict(value)
|
175
|
+
|
176
|
+
def to_dict(
|
177
|
+
self,
|
178
|
+
*,
|
179
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
180
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
181
|
+
include_default_values: bool = False,
|
182
|
+
) -> dict[str, typing.Any] | typing.Any:
|
183
|
+
return self.value
|
184
|
+
|
185
|
+
|
186
|
+
class StringValue(VanillaStringValue):
|
187
|
+
@staticmethod
|
188
|
+
def from_wrapped(wrapped: str) -> "StringValue":
|
189
|
+
return StringValue(value=wrapped)
|
190
|
+
|
191
|
+
def to_wrapped(self) -> str:
|
192
|
+
return self.value
|
193
|
+
|
194
|
+
@classmethod
|
195
|
+
def from_dict(cls, value):
|
196
|
+
if isinstance(value, str):
|
197
|
+
return StringValue(value=value)
|
198
|
+
return super().from_dict(value)
|
199
|
+
|
200
|
+
def to_dict(
|
201
|
+
self,
|
202
|
+
*,
|
203
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
204
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
205
|
+
include_default_values: bool = False,
|
206
|
+
) -> dict[str, typing.Any] | typing.Any:
|
207
|
+
return self.value
|
208
|
+
|
209
|
+
|
210
|
+
class BytesValue(VanillaBytesValue):
|
211
|
+
@staticmethod
|
212
|
+
def from_wrapped(wrapped: bytes) -> "BytesValue":
|
213
|
+
return BytesValue(value=wrapped)
|
214
|
+
|
215
|
+
def to_wrapped(self) -> bytes:
|
216
|
+
return self.value
|
217
|
+
|
218
|
+
@classmethod
|
219
|
+
def from_dict(cls, value):
|
220
|
+
if isinstance(value, bytes):
|
221
|
+
return BytesValue(value=value)
|
222
|
+
return super().from_dict(value)
|
223
|
+
|
224
|
+
def to_dict(
|
225
|
+
self,
|
226
|
+
*,
|
227
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
228
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
229
|
+
include_default_values: bool = False,
|
230
|
+
) -> dict[str, typing.Any] | typing.Any:
|
231
|
+
return self.value
|
@@ -1,4 +1,8 @@
|
|
1
1
|
import datetime
|
2
|
+
import typing
|
3
|
+
|
4
|
+
import betterproto2
|
5
|
+
import dateutil.parser
|
2
6
|
|
3
7
|
from betterproto2_compiler.lib.google.protobuf import Timestamp as VanillaTimestamp
|
4
8
|
|
@@ -6,6 +10,11 @@ from betterproto2_compiler.lib.google.protobuf import Timestamp as VanillaTimest
|
|
6
10
|
class Timestamp(VanillaTimestamp):
|
7
11
|
@classmethod
|
8
12
|
def from_datetime(cls, dt: datetime.datetime) -> "Timestamp":
|
13
|
+
if not dt.tzinfo:
|
14
|
+
raise ValueError("datetime must be timezone aware")
|
15
|
+
|
16
|
+
dt = dt.astimezone(datetime.timezone.utc)
|
17
|
+
|
9
18
|
# manual epoch offset calulation to avoid rounding errors,
|
10
19
|
# to support negative timestamps (before 1970) and skirt
|
11
20
|
# around datetime bugs (apparently 0 isn't a year in [0, 9999]??)
|
@@ -43,3 +52,33 @@ class Timestamp(VanillaTimestamp):
|
|
43
52
|
return f"{result}.{int(nanos // 1e3):06d}Z"
|
44
53
|
# Serialize 9 fractional digits.
|
45
54
|
return f"{result}.{nanos:09d}"
|
55
|
+
|
56
|
+
# TODO typing
|
57
|
+
@classmethod
|
58
|
+
def from_dict(cls, value):
|
59
|
+
if isinstance(value, str):
|
60
|
+
dt = dateutil.parser.isoparse(value)
|
61
|
+
dt = dt.astimezone(datetime.timezone.utc)
|
62
|
+
return Timestamp.from_datetime(dt)
|
63
|
+
|
64
|
+
return super().from_dict(value)
|
65
|
+
|
66
|
+
# TODO typing
|
67
|
+
def to_dict(
|
68
|
+
self,
|
69
|
+
*,
|
70
|
+
output_format: betterproto2.OutputFormat = betterproto2.OutputFormat.PROTO_JSON,
|
71
|
+
casing: betterproto2.Casing = betterproto2.Casing.CAMEL,
|
72
|
+
include_default_values: bool = False,
|
73
|
+
) -> dict[str, typing.Any] | typing.Any:
|
74
|
+
# If the output format is PYTHON, we should have kept the wraped type without building the real class
|
75
|
+
assert output_format == betterproto2.OutputFormat.PROTO_JSON
|
76
|
+
|
77
|
+
return Timestamp.timestamp_to_json(self.to_datetime())
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def from_wrapped(wrapped: datetime.datetime) -> "Timestamp":
|
81
|
+
return Timestamp.from_datetime(wrapped)
|
82
|
+
|
83
|
+
def to_wrapped(self) -> datetime.datetime:
|
84
|
+
return self.to_datetime()
|