diff --git a/bec_lib/bec_lib/devicemanager.py b/bec_lib/bec_lib/devicemanager.py index 02f67c37d..20daebc27 100644 --- a/bec_lib/bec_lib/devicemanager.py +++ b/bec_lib/bec_lib/devicemanager.py @@ -95,12 +95,10 @@ def __contains__(self, key: object) -> bool: return super().__contains__(key) def __getattr__(self, attr): - if attr.startswith("_"): - # if dunder attributes are would not be caught, they - # would raise a DeviceConfigError and kill the - # IPython completer - # pylint: disable=no-member - return super().__getattr__(attr) + if attr.startswith("__"): + # Keep missing dunder lookups as AttributeError so + # introspection and IPython completion can probe safely. + raise AttributeError(f"{type(self).__name__!r} object has no attribute {attr!r}") dev = self.get(attr) if not dev: raise DeviceConfigError(f"Device {attr} does not exist.") diff --git a/bec_lib/tests/test_devices.py b/bec_lib/tests/test_devices.py index dbd4dbdd9..c5b6e7aa6 100644 --- a/bec_lib/tests/test_devices.py +++ b/bec_lib/tests/test_devices.py @@ -593,6 +593,11 @@ def test_device_container_wm_raises_for_missing_device(dev_container): dev_container.wm("missing") +def test_device_container_getattr_raises_for_missing_single_underscore_device(dev_container): + with pytest.raises(DeviceConfigError, match="Device _something does not exist\\."): + dev_container._something + + def test_device_container_wm_raises_for_unmatched_glob(dev_container): with pytest.raises(DeviceConfigError, match="No devices match pattern miss\\*\\."): dev_container.wm("miss*")