Skip to content

Commit 9298678

Browse files
Fix dot-notation read on $@ref-backed ConfigParser proxies
_ConfigProxy.__getattr__ fell back to getattr(self._value, key) when the chained config id was absent from the resolver. For a proxy backed by a $@ref the children have no ids of their own, so parser.alias.x looked up "x" as a dict *attribute* and raised AttributeError, even though parser.alias["x"] resolved it and returned the value. Resolve a key present in the underlying container the same way __getitem__ already does, so dot- and bracket-notation agree on ref-backed proxies and config keys keep precedence over dict methods, as documented. Keys absent from the container still fall back to the container's own attributes, so .keys()/.items() are unaffected. Signed-off-by: VenkateswarluNagineni <venkates2002@tamu.edu>
1 parent 3a458fe commit 9298678

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

monai/bundle/config_parser.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,14 @@ def __getattr__(self, key: str) -> Any:
162162
try:
163163
return self._chain(key)
164164
except KeyError:
165-
return getattr(self._value, key)
165+
pass
166+
if isinstance(self._value, dict) and key in self._value:
167+
# the chained id is absent from the resolver (for example when this proxy is
168+
# backed by a `$@ref`, whose children have no ids of their own), but the key
169+
# does exist in the container: resolve it like `__getitem__` does, so dot- and
170+
# bracket-notation agree and config keys keep precedence over dict methods.
171+
return self._value[key]
172+
return getattr(self._value, key)
166173

167174
def __getitem__(self, key: str | int) -> Any:
168175
try:

tests/bundle/test_config_parser.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,24 @@ def test_chained_ref_backed_proxy_write_through(self):
487487
del parser.alias["y"]
488488
self.assertNotIn("y", parser.get_parsed_content("target"))
489489

490+
def test_ref_backed_proxy_attribute_read(self):
491+
# Dot-notation must agree with bracket-notation on a proxy reached via $@ref:
492+
# "alias::x" has no id in the resolver, but "x" is a key of the aliased node, so
493+
# both notations must resolve it (parser.alias.x raised AttributeError before this
494+
# fix, while parser.alias["x"] returned the value).
495+
parser = ConfigParser(config={"target": {"x": 1, "y": 2}, "alias": "$@target"}, globals={"monai": "monai"})
496+
self.assertEqual(parser.alias.x, parser.alias["x"])
497+
self.assertEqual(parser.alias.x, 1)
498+
# a key absent from the container still falls back to the container's own methods
499+
self.assertEqual(sorted(parser.alias.keys()), ["x", "y"])
500+
501+
def test_chained_ref_backed_proxy_attribute_read(self):
502+
# dot-notation must follow the full ref chain, as _backing_id() does for writes.
503+
parser = ConfigParser(
504+
config={"target": {"x": 1}, "mid": "$@target", "alias": "$@mid"}, globals={"monai": "monai"}
505+
)
506+
self.assertEqual(parser.alias.x, 1)
507+
490508
def test_raw_is_read_only(self):
491509
with self.assertRaises(AttributeError):
492510
self.parser.A._raw = {"something": "else"}

0 commit comments

Comments
 (0)