betterproto2-compiler 0.4.0__py3-none-any.whl → 0.5.1__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.
@@ -5,7 +5,7 @@ from typing import (
5
5
  TYPE_CHECKING,
6
6
  )
7
7
 
8
- from betterproto2_compiler.lib.google import protobuf as google_protobuf
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
- unwrap: bool = True,
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"): [Timestamp.from_datetime, Timestamp.to_datetime, Timestamp.timestamp_to_json],
13
- ("google.protobuf", "Duration"): [Duration.from_timedelta, Duration.to_timedelta, Duration.delta_to_json],
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
  }
@@ -37,7 +37,7 @@ class Any(VanillaAny):
37
37
  except KeyError:
38
38
  raise TypeError(f"Can't unpack unregistered type: {self.type_url}")
39
39
 
40
- return message_type().parse(self.value)
40
+ return message_type.parse(self.value)
41
41
 
42
42
  def to_dict(self, **kwargs) -> dict[str, typing.Any]:
43
43
  # TODO allow passing a message pool to `to_dict`
@@ -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()