diff --git a/docs/changes/newsfragments/6009.improved b/docs/changes/newsfragments/6009.improved new file mode 100644 index 00000000000..3ee1c4c820b --- /dev/null +++ b/docs/changes/newsfragments/6009.improved @@ -0,0 +1,2 @@ +Station.get_component and Instrument.get_component has gained the ability to lookup a component +on an instrument that is only added as a member of a chanellist/tuple and not added as an individual component. diff --git a/src/qcodes/instrument/instrument_base.py b/src/qcodes/instrument/instrument_base.py index 4a720c26b9b..8a1ee2d0cf1 100644 --- a/src/qcodes/instrument/instrument_base.py +++ b/src/qcodes/instrument/instrument_base.py @@ -257,6 +257,13 @@ def get_component(self, full_name: str) -> MetadatableWithName: def _get_component_by_name( self, potential_top_level_name: str, remaining_name_parts: list[str] ) -> MetadatableWithName: + + log.debug( + "trying to find component %s on %s, remaining %s", + potential_top_level_name, + self.full_name, + remaining_name_parts, + ) component: MetadatableWithName | None = None sub_component_name_map = { @@ -264,6 +271,13 @@ def _get_component_by_name( for sub_component in self.submodules.values() } + channel_name_map: dict[str, InstrumentModule] = {} + for channel_list in self._channel_lists.values(): + local_channels_name_map: dict[str, InstrumentModule] = { + channel.short_name: channel for channel in channel_list + } + channel_name_map.update(local_channels_name_map.items()) + if potential_top_level_name in self.parameters: component = self.parameters[potential_top_level_name] elif potential_top_level_name in self.functions: @@ -283,13 +297,18 @@ def _get_component_by_name( remaining_name = "_".join(remaining_name_parts) component = component.get_component(remaining_name) remaining_name_parts = [] + elif potential_top_level_name in channel_name_map: + component = channel_name_map[potential_top_level_name] + if len(remaining_name_parts) > 0: + remaining_name_parts.reverse() + remaining_name = "_".join(remaining_name_parts) + component = component.get_component(remaining_name) + remaining_name_parts = [] if component is not None: if len(remaining_name_parts) == 0: return component - remaining_name_parts.reverse() - if len(remaining_name_parts) == 0: raise KeyError( f"Found component {self.full_name} but could not " @@ -299,7 +318,7 @@ def _get_component_by_name( new_potential_top_level_name = ( f"{potential_top_level_name}_{remaining_name_parts.pop()}" ) - remaining_name_parts.reverse() + component = self._get_component_by_name( new_potential_top_level_name, remaining_name_parts ) diff --git a/src/qcodes/instrument_drivers/mock_instruments/__init__.py b/src/qcodes/instrument_drivers/mock_instruments/__init__.py index b84c57eca15..60721aa907f 100644 --- a/src/qcodes/instrument_drivers/mock_instruments/__init__.py +++ b/src/qcodes/instrument_drivers/mock_instruments/__init__.py @@ -450,7 +450,7 @@ def turn_on(self) -> None: pass -class DummyChannelInstrument(Instrument): +class DummyChannelInstrument(DummyBase): """ Dummy instrument with channels """ @@ -474,6 +474,25 @@ def __init__( self.add_submodule("channels", channels.to_channel_tuple()) +class DummyChannelOnlyInstrument(DummyBase): + """ + Dummy instrument with channels that have not been added as individual submodules. + Also use module names with _ in them to check that we can handle that. + """ + + def __init__(self, name: str, **kwargs: Any): + super().__init__(name, **kwargs) + + channels = ChannelList(self, "Temp_Sensors", DummyChannel) + channel_ids: Sequence[str] = ("A_a", "B_b", "C_c", "D_d", "E_e", "F_f") + channel_names = tuple(f"Chan{chan_name}" for chan_name in channel_ids) + + for chan_name, chan_id in zip(channel_names, channel_ids): + channel = DummyChannel(self, chan_name, chan_id) + channels.append(channel) + self.add_submodule("channels", channels.to_channel_tuple()) + + class MultiGetter(MultiParameter): """ Test parameters with complicated return values diff --git a/tests/test_station.py b/tests/test_station.py index a34c2e41cbe..110d883b5cf 100644 --- a/tests/test_station.py +++ b/tests/test_station.py @@ -15,6 +15,7 @@ from qcodes.instrument import Instrument from qcodes.instrument_drivers.mock_instruments import ( DummyChannelInstrument, + DummyChannelOnlyInstrument, DummyInstrument, ) from qcodes.monitor import Monitor @@ -891,8 +892,9 @@ def test_station_config_created_with_multiple_config_files() -> None: def test_get_component_by_name() -> None: instr = DummyChannelInstrument(name="dummy") + instr2 = DummyChannelOnlyInstrument(name="some_other_dummy") param = Parameter(name="param", set_cmd=None, get_cmd=None) - station = Station(instr, param) + station = Station(instr, instr2, param) assert station.get_component("dummy") is instr assert station.get_component("dummy_A") is instr.A @@ -900,6 +902,13 @@ def test_get_component_by_name() -> None: assert station.get_component("dummy_A_temperature") is instr.A.temperature assert station.get_component("dummy_ChanA_temperature") is instr.A.temperature + assert station.get_component("some_other_dummy") is instr2 + assert station.get_component("some_other_dummy_ChanA_a") is instr2.channels[0] + assert ( + station.get_component("some_other_dummy_ChanA_a_temperature") + is instr2.channels[0].temperature + ) + assert station.get_component("param") is param