From a0cc7e0980d12694937796ad14f056c836c85c68 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 16 Nov 2025 16:54:02 +0100 Subject: [PATCH 1/4] add missing Self return types to from_dict methods --- .../betterproto2_compiler/known_types/any.py | 3 +- .../known_types/duration.py | 3 +- .../known_types/google_values.py | 19 ++++++------ .../known_types/struct.py | 7 +++-- .../known_types/timestamp.py | 3 +- .../lib/google/protobuf/__init__.py | 31 ++++++++++--------- 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/any.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/any.py index 57a23a97..8184f34c 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/any.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/any.py @@ -1,6 +1,7 @@ import typing import betterproto2 +from typing_extensions import Self from betterproto2_compiler.lib.google.protobuf import Any as VanillaAny @@ -60,7 +61,7 @@ def to_dict(self, **kwargs) -> dict[str, typing.Any]: # TODO typing @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: value = dict(value) # Make a copy type_url = value.pop("@type", None) diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py index e6dcda1c..858bdf2c 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py @@ -3,6 +3,7 @@ import typing import betterproto2 +from typing_extensions import Self from betterproto2_compiler.lib.google.protobuf import Duration as VanillaDuration @@ -30,7 +31,7 @@ def delta_to_json(delta: datetime.timedelta) -> str: # TODO typing @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): if not re.match(r"^\d+(\.\d+)?s$", value): raise ValueError(f"Invalid duration string: {value}") diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py index e5439011..476296ad 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py @@ -1,6 +1,7 @@ import typing import betterproto2 +from typing_extensions import Self from betterproto2_compiler.lib.google.protobuf import ( BoolValue as VanillaBoolValue, @@ -24,7 +25,7 @@ def to_wrapped(self) -> bool: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bool): return BoolValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -48,7 +49,7 @@ def to_wrapped(self) -> int: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return Int32Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -72,7 +73,7 @@ def to_wrapped(self) -> int: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return Int64Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -96,7 +97,7 @@ def to_wrapped(self) -> int: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return UInt32Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -120,7 +121,7 @@ def to_wrapped(self) -> int: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return UInt64Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -144,7 +145,7 @@ def to_wrapped(self) -> float: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): return FloatValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -168,7 +169,7 @@ def to_wrapped(self) -> float: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): return DoubleValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -192,7 +193,7 @@ def to_wrapped(self) -> str: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): return StringValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -216,7 +217,7 @@ def to_wrapped(self) -> bytes: return self.value @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bytes): return BytesValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/struct.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/struct.py index 6bcffadc..96e4bfb7 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/struct.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/struct.py @@ -1,6 +1,7 @@ import typing import betterproto2 +from typing_extensions import Self from betterproto2_compiler.lib.google.protobuf import ( ListValue as VanillaListValue, @@ -13,7 +14,7 @@ class Struct(VanillaStruct): # TODO typing @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: assert isinstance(value, dict) fields: dict[str, Value] = {} @@ -47,7 +48,7 @@ def to_dict( class Value(VanillaValue): # TODO typing @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: match value: case bool() as b: return cls(bool_value=b) @@ -94,7 +95,7 @@ def to_dict( class ListValue(VanillaListValue): # TODO typing @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: return cls(values=[Value.from_dict(v) for v in value]) # TODO typing diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py index 3620c01b..c3e94d22 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py @@ -3,6 +3,7 @@ import betterproto2 import dateutil.parser +from typing_extensions import Self from betterproto2_compiler.lib.google.protobuf import Timestamp as VanillaTimestamp @@ -55,7 +56,7 @@ def timestamp_to_json(dt: datetime.datetime) -> str: # TODO typing @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): dt = dateutil.parser.isoparse(value) dt = dt.astimezone(datetime.timezone.utc) diff --git a/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py b/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py index bb217701..3d788c23 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py +++ b/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py @@ -89,6 +89,7 @@ import datetime import re import typing +from typing_extensions import Self import warnings from dataclasses import dataclass @@ -917,7 +918,7 @@ def to_dict(self, **kwargs) -> dict[str, typing.Any]: return output @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: value = dict(value) # Make a copy type_url = value.pop("@type", None) @@ -1024,7 +1025,7 @@ class BoolValue(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bool): return BoolValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -1063,7 +1064,7 @@ class BytesValue(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bytes): return BytesValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -1182,7 +1183,7 @@ class DoubleValue(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): return DoubleValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -1308,7 +1309,7 @@ def delta_to_json(delta: datetime.timedelta) -> str: return f"{'.'.join(parts)}s" @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): if not re.match(r"^\d+(\.\d+)?s$", value): raise ValueError(f"Invalid duration string: {value}") @@ -2599,7 +2600,7 @@ class FloatValue(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): return FloatValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -2690,7 +2691,7 @@ class Int32Value(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return Int32Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -2729,7 +2730,7 @@ class Int64Value(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return Int64Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -2768,7 +2769,7 @@ class ListValue(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: return cls(values=[Value.from_dict(v) for v in value]) def to_dict( @@ -3393,7 +3394,7 @@ class StringValue(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): return StringValue(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -3439,7 +3440,7 @@ class Struct(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: assert isinstance(value, dict) fields: dict[str, Value] = {} @@ -3625,7 +3626,7 @@ def timestamp_to_json(dt: datetime.datetime) -> str: return f"{result}.{nanos:09d}" @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): dt = dateutil.parser.isoparse(value) dt = dt.astimezone(datetime.timezone.utc) @@ -3715,7 +3716,7 @@ class UInt32Value(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return UInt32Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -3754,7 +3755,7 @@ class UInt64Value(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): return UInt64Value(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -3875,7 +3876,7 @@ class Value(betterproto2.Message): """ @classmethod - def from_dict(cls, value, *, ignore_unknown_fields: bool = False): + def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: match value: case bool() as b: return cls(bool_value=b) From 14f9bba74b633a51f941aa67e4041523fa308e17 Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 16 Nov 2025 17:48:56 +0100 Subject: [PATCH 2/4] prefer cls and self over hardcoded class name to fix pyright and better subclassing --- .../known_types/duration.py | 2 +- .../known_types/google_values.py | 18 ++++++------- .../known_types/timestamp.py | 4 +-- .../lib/google/protobuf/__init__.py | 26 +++++++++---------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py index 858bdf2c..c5a87576 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/duration.py @@ -37,7 +37,7 @@ def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: raise ValueError(f"Invalid duration string: {value}") seconds = float(value[:-1]) - return Duration(seconds=int(seconds), nanos=int((seconds - int(seconds)) * 1e9)) + return cls(seconds=int(seconds), nanos=int((seconds - int(seconds)) * 1e9)) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py index 476296ad..8c955664 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/google_values.py @@ -27,7 +27,7 @@ def to_wrapped(self) -> bool: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bool): - return BoolValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -51,7 +51,7 @@ def to_wrapped(self) -> int: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return Int32Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -75,7 +75,7 @@ def to_wrapped(self) -> int: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return Int64Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -99,7 +99,7 @@ def to_wrapped(self) -> int: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return UInt32Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -123,7 +123,7 @@ def to_wrapped(self) -> int: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return UInt64Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -147,7 +147,7 @@ def to_wrapped(self) -> float: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): - return FloatValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -171,7 +171,7 @@ def to_wrapped(self) -> float: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): - return DoubleValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -195,7 +195,7 @@ def to_wrapped(self) -> str: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): - return StringValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -219,7 +219,7 @@ def to_wrapped(self) -> bytes: @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bytes): - return BytesValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( diff --git a/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py b/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py index c3e94d22..bd8f6e79 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py +++ b/betterproto2_compiler/src/betterproto2_compiler/known_types/timestamp.py @@ -10,7 +10,7 @@ class Timestamp(VanillaTimestamp): @classmethod - def from_datetime(cls, dt: datetime.datetime) -> "Timestamp": + def from_datetime(cls, dt: datetime.datetime) -> Self: if not dt.tzinfo: raise ValueError("datetime must be timezone aware") @@ -60,7 +60,7 @@ def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): dt = dateutil.parser.isoparse(value) dt = dt.astimezone(datetime.timezone.utc) - return Timestamp.from_datetime(dt) + return cls.from_datetime(dt) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) diff --git a/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py b/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py index 3d788c23..bee52d79 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py +++ b/betterproto2_compiler/src/betterproto2_compiler/lib/google/protobuf/__init__.py @@ -1027,7 +1027,7 @@ class BoolValue(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bool): - return BoolValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -1066,7 +1066,7 @@ class BytesValue(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, bytes): - return BytesValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -1185,7 +1185,7 @@ class DoubleValue(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): - return DoubleValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -1315,7 +1315,7 @@ def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: raise ValueError(f"Invalid duration string: {value}") seconds = float(value[:-1]) - return Duration(seconds=int(seconds), nanos=int((seconds - int(seconds)) * 1e9)) + return cls(seconds=int(seconds), nanos=int((seconds - int(seconds)) * 1e9)) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -2602,7 +2602,7 @@ class FloatValue(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, float): - return FloatValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -2693,7 +2693,7 @@ class Int32Value(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return Int32Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -2732,7 +2732,7 @@ class Int64Value(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return Int64Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -3396,7 +3396,7 @@ class StringValue(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): - return StringValue(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -3581,7 +3581,7 @@ class Timestamp(betterproto2.Message): """ @classmethod - def from_datetime(cls, dt: datetime.datetime) -> "Timestamp": + def from_datetime(cls, dt: datetime.datetime) -> Self: if not dt.tzinfo: raise ValueError("datetime must be timezone aware") @@ -3630,7 +3630,7 @@ def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, str): dt = dateutil.parser.isoparse(value) dt = dt.astimezone(datetime.timezone.utc) - return Timestamp.from_datetime(dt) + return cls.from_datetime(dt) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) @@ -3644,7 +3644,7 @@ def to_dict( # If the output format is PYTHON, we should have kept the wraped type without building the real class assert output_format == betterproto2.OutputFormat.PROTO_JSON - return Timestamp.timestamp_to_json(self.to_datetime()) + return self.timestamp_to_json(self.to_datetime()) @staticmethod def from_wrapped(wrapped: datetime.datetime) -> "Timestamp": @@ -3718,7 +3718,7 @@ class UInt32Value(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return UInt32Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( @@ -3757,7 +3757,7 @@ class UInt64Value(betterproto2.Message): @classmethod def from_dict(cls, value, *, ignore_unknown_fields: bool = False) -> Self: if isinstance(value, int): - return UInt64Value(value=value) + return cls(value=value) return super().from_dict(value, ignore_unknown_fields=ignore_unknown_fields) def to_dict( From d226ae0422376de6f5fc7c744370f80ed2eaa72e Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 16 Nov 2025 19:39:11 +0100 Subject: [PATCH 3/4] add Self to generated code imports --- .../src/betterproto2_compiler/templates/header.py.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 b/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 index e21afa5c..51cf3667 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 +++ b/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 @@ -18,7 +18,7 @@ import dateutil.parser import warnings from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator import typing -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Self {% if output_file.settings.pydantic_dataclasses %} import pydantic From 3017ae951bee0f3776c0e502f84f54c707e4046f Mon Sep 17 00:00:00 2001 From: Benedikt Bartscher Date: Sun, 16 Nov 2025 19:58:34 +0100 Subject: [PATCH 4/4] fix: need to import Self from typing_extensions for older Python versions... --- .../src/betterproto2_compiler/templates/header.py.j2 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 b/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 index 51cf3667..750e1f66 100644 --- a/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 +++ b/betterproto2_compiler/src/betterproto2_compiler/templates/header.py.j2 @@ -18,7 +18,8 @@ import dateutil.parser import warnings from collections.abc import AsyncIterable, AsyncIterator, Iterable, Iterator import typing -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING +from typing_extensions import Self {% if output_file.settings.pydantic_dataclasses %} import pydantic