From 55024651c67a872d429c778642d4ead274c0fe7d Mon Sep 17 00:00:00 2001 From: Yu-Cheng Ling Date: Thu, 2 Apr 2026 14:35:29 -0700 Subject: [PATCH] Fix `__qualname__` AttributeError in fiddle config.py If a partial function generated by `functools.partial` is passed into fiddle AND certain exceptions are raised, accessing `__qualname__` will trigger another exception, prevent the users from seeing the real error messages. Type type `TypeOrCallableProducingT = Union[Callable[..., T], Type[T]]` does not guarantee that `__qualname__` will exist. - Added a regression test case in third_party/py/fiddle/_src/config_test.py that uses functools.partial to trigger the bug. - Replace all `self.__fn_or_cls__.__qualname__` with the existing `self._fn_or_cls_name_repr()` which already solved the problem. PiperOrigin-RevId: 893707305 --- fiddle/_src/config.py | 6 +++--- fiddle/_src/config_test.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/fiddle/_src/config.py b/fiddle/_src/config.py index 9d4a07b7..d7987154 100644 --- a/fiddle/_src/config.py +++ b/fiddle/_src/config.py @@ -348,14 +348,14 @@ def __getattr__(self, name: str): ) and _field_uses_default_factory(self.__fn_or_cls__, name): raise ValueError( "Can't get default value for dataclass field " - + f'{self.__fn_or_cls__.__qualname__}.{name} ' + + f'{self._fn_or_cls_name_repr()}.{name} ' + 'since it uses a default_factory.' ) if param is not None and param.default is not param.empty: return param.default msg = ( f"No parameter '{name}' has been set on {self!r} " - f"(Buildable='{self.__fn_or_cls__.__qualname__}', missing " + f"(Buildable='{self._fn_or_cls_name_repr()}', missing " f"parameter='{name}')." ) # TODO(b/219988937): Implement an edit distance function and display valid @@ -363,7 +363,7 @@ def __getattr__(self, name: str): if hasattr(self.__fn_or_cls__, name): msg += ( f' Note: {self.__fn_or_cls__.__module__}.' - f'{self.__fn_or_cls__.__qualname__} has an attribute/method with ' + f'{self._fn_or_cls_name_repr()} has an attribute/method with ' 'this name, so this could be caused by using a ' f'fdl.{self.__class__.__qualname__} in ' 'place of the actual function or class being configured. Did you ' diff --git a/fiddle/_src/config_test.py b/fiddle/_src/config_test.py index 4510b97d..56a77408 100644 --- a/fiddle/_src/config_test.py +++ b/fiddle/_src/config_test.py @@ -17,6 +17,7 @@ import copy import dataclasses +import functools import pickle import sys import threading @@ -851,6 +852,18 @@ def test_nonexistent_attribute_error(self): with self.assertRaisesRegex(AttributeError, expected_msg): getattr(class_config, 'nonexistent_arg') + def test_partial_nonexistent_attribute_error(self): + # This test serves as a regression test that `functools.partial` + # wasn't well handled in error reporting code. + partial_fn = functools.partial(basic_fn, arg1=1) + cfg = fdl.Config(partial_fn) + # The expected error should indicate the missing parameter, not crash trying + # to access __qualname__ on the partial object. + with self.assertRaisesRegex( + AttributeError, r"No parameter 'nonexistent_arg' has been set" + ): + getattr(cfg, 'nonexistent_arg') + def test_nonexistent_parameter_error(self): class_config = fdl.Config(SampleClass) expected_msg = (r"No parameter named 'nonexistent_arg' exists for "