Skip to content

ignore_unknown_fields argument to Message.from_dict() is not propagated #152

@chriselion

Description

@chriselion

Summary

If you have a nested messages and pass ignore_unknown_fields=True, the recursive call to from_dict doesn't pass the ignore_unknown_fields flag.

Reproduction Steps

Given this proto file:

syntax = "proto3";

package betterproto.repro;

message Inner {
    int32 val = 1;
}

message Outer {
    Inner inner = 1;
}

compiled with

uv run python -m grpc_tools.protoc --python_betterproto2_out generated/ -I. nested.proto

This code all works

from generated.betterproto.repro import Outer, Inner

# No unknown fields, works fine
print(Outer.from_dict({"inner": {"val": 42}}, ignore_unknown_fields=True))

# Unknown field in Outer class, ignore_unknown_fields is respected
print(Outer.from_dict({"inner": {"val": 42}, "foo": "bar"}, ignore_unknown_fields=True))

# Unknown field with Inner class, ignore_unknown_fields is respected
print(Inner.from_dict({"val": 42, "foo": "bar"}, ignore_unknown_fields=True))

but this fails

# Unknown field in nested class, ignore_unknown_fields is ignored
print(Outer.from_dict({"inner": {"val": 42, "foo": "bar"}}, ignore_unknown_fields=True))

Expected Results

The code should print Outer(inner=Inner(val=42))

Actual Results

The code raises an exception with this traceback

Traceback (most recent call last):
  File "/snip/nested_fail.py", line 13, in <module>
    print(Outer.from_dict({"inner": {"val": 42, "foo": "bar"}}, ignore_unknown_fields=True))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/snip/.venv/lib/python3.12/site-packages/betterproto2/__init__.py", line 1197, in from_dict
    return cls(**cls._from_dict_init(value, ignore_unknown_fields=ignore_unknown_fields))  # type: ignore
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/snip/.venv/lib/python3.12/site-packages/betterproto2/__init__.py", line 1157, in _from_dict_init
    value = _value_from_dict(value, meta, field_cls)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/snip/.venv/lib/python3.12/site-packages/betterproto2/__init__.py", line 613, in _value_from_dict
    msg = msg_cls.from_dict(value)
          ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/snip/.venv/lib/python3.12/site-packages/betterproto2/__init__.py", line 1197, in from_dict
    return cls(**cls._from_dict_init(value, ignore_unknown_fields=ignore_unknown_fields))  # type: ignore
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/snip/.venv/lib/python3.12/site-packages/betterproto2/__init__.py", line 1143, in _from_dict_init
    raise KeyError(f"Unknown field '{field_name}' in message {cls.__name__}.") from None
KeyError: "Unknown field 'foo' in message Inner."

You can see here

msg = msg_cls.from_dict(value)

that the recursive msg_cls.from_dict(value) doesn't use the top-level arguments.

System Information

$ uv run python -m grpc_tools.protoc --version                                                                                                   
libprotoc 31.1

$ uv run python --version                     
Python 3.12.11

$ uv pip show betterproto2 
Name: betterproto2
Version: 0.9.0
Location: [snip]/.venv/lib/python3.12/site-packages
Requires: python-dateutil, typing-extensions
Required-by: betterproto2-compiler

$ uv pip show betterproto2_compiler
Name: betterproto2-compiler
Version: 0.9.0
Location: [snip]/.venv/lib/python3.12/site-packages
Requires: betterproto2, jinja2, ruff, typing-extensions
Required-by:

Checklist

  • I have searched the issues for duplicates.
  • I have shown the entire traceback, if possible.
  • I have verified this issue occurs on the latest prelease of betterproto which can be installed using pip install -U --pre betterproto, if possible.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions