Skip to content

Commit 2afded9

Browse files
Fix from_dict unknown fields (#110)
1 parent 51678cc commit 2afded9

File tree

2 files changed

+23
-5
lines changed

2 files changed

+23
-5
lines changed

betterproto2/src/betterproto2/__init__.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,16 +1108,23 @@ def to_dict(
11081108
return output
11091109

11101110
@classmethod
1111-
def _from_dict_init(cls, mapping: Mapping[str, Any] | Any) -> Mapping[str, Any]:
1111+
def _from_dict_init(cls, mapping: Mapping[str, Any] | Any, *, ignore_unknown_fields: bool) -> Mapping[str, Any]:
11121112
init_kwargs: dict[str, Any] = {}
11131113
for key, value in mapping.items():
11141114
field_name = safe_snake_case(key)
1115-
field_cls = cls._betterproto.cls_by_field[field_name]
11161115

11171116
try:
1117+
field_cls = cls._betterproto.cls_by_field[field_name]
11181118
meta = cls._betterproto.meta_by_field_name[field_name]
11191119
except KeyError:
1120-
continue # TODO is it a problem?
1120+
# According to the protobuf spec (https://protobuf.dev/programming-guides/json/): "The protobuf JSON
1121+
# parser should reject unknown fields by default but may provide an option to ignore unknown fields in
1122+
# parsing."
1123+
if ignore_unknown_fields:
1124+
continue
1125+
1126+
raise KeyError(f"Unknown field '{field_name}' in message {cls.__name__}.") from None
1127+
11211128
if value is None:
11221129
continue
11231130

@@ -1148,7 +1155,7 @@ def _from_dict_init(cls, mapping: Mapping[str, Any] | Any) -> Mapping[str, Any]:
11481155
return init_kwargs
11491156

11501157
@classmethod
1151-
def from_dict(cls: type[Self], value: Mapping[str, Any] | Any) -> Self:
1158+
def from_dict(cls: type[Self], value: Mapping[str, Any] | Any, *, ignore_unknown_fields: bool = False) -> Self:
11521159
"""
11531160
Parse the key/value pairs into the a new message instance.
11541161
@@ -1165,7 +1172,7 @@ def from_dict(cls: type[Self], value: Mapping[str, Any] | Any) -> Self:
11651172
if not isinstance(value, Mapping) and hasattr(cls, "from_wrapped"): # type: ignore
11661173
return cls.from_wrapped(value) # type: ignore
11671174

1168-
return cls(**cls._from_dict_init(value))
1175+
return cls(**cls._from_dict_init(value, ignore_unknown_fields=ignore_unknown_fields)) # type: ignore
11691176

11701177
def to_json(
11711178
self,

betterproto2/tests/test_features.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
)
1111
from unittest.mock import ANY
1212

13+
import pytest
14+
1315
import betterproto2
1416
from betterproto2 import OutputFormat
1517

@@ -54,6 +56,15 @@ def test_unknown_fields():
5456
assert newer == new_again
5557

5658

59+
def test_from_dict_unknown_fields():
60+
from tests.output_betterproto.features import Older
61+
62+
with pytest.raises(KeyError):
63+
Older.from_dict({"x": True, "y": 1})
64+
65+
assert Older.from_dict({"x": True, "y": 1}, ignore_unknown_fields=True) == Older(x=True)
66+
67+
5768
def test_oneof_support():
5869
from tests.output_betterproto.features import IntMsg, OneofMsg
5970

0 commit comments

Comments
 (0)