From a85769600e2ac72cdbc7f4c3fa564bf759ee7c30 Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Thu, 7 Nov 2024 10:56:12 -0800 Subject: [PATCH 1/4] Add methods to find parameter chain links by type --- src/qcodes/extensions/__init__.py | 8 +++++++ src/qcodes/extensions/infer.py | 37 +++++++++++++++++++++++++++++-- tests/extensions/test_infer.py | 28 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/qcodes/extensions/__init__.py b/src/qcodes/extensions/__init__.py index a18aba947c00..9cb766d3cbe1 100644 --- a/src/qcodes/extensions/__init__.py +++ b/src/qcodes/extensions/__init__.py @@ -8,7 +8,11 @@ from .infer import ( InferAttrs, InferError, + get_chain_links_of_type, + get_instrument_from_param, + get_parameter_chain, get_root_parameter, + get_sole_chain_link_of_type, infer_channel, infer_instrument, infer_instrument_module, @@ -21,7 +25,11 @@ "DriverTestCase", "InferAttrs", "InferError", + "get_chain_links_of_type", + "get_instrument_from_param", + "get_parameter_chain", "get_root_parameter", + "get_sole_chain_link_of_type", "infer_channel", "infer_instrument", "infer_instrument_module", diff --git a/src/qcodes/extensions/infer.py b/src/qcodes/extensions/infer.py index e3361fa4fff6..df2e5eb7ff12 100644 --- a/src/qcodes/extensions/infer.py +++ b/src/qcodes/extensions/infer.py @@ -1,16 +1,18 @@ from __future__ import annotations from collections.abc import Sequence -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING, ClassVar, TypeVar, cast from qcodes.instrument import Instrument, InstrumentBase, InstrumentModule -from qcodes.parameters import DelegateParameter, Parameter +from qcodes.parameters import DelegateParameter, Parameter, ParameterBase if TYPE_CHECKING: from collections.abc import Iterable DOES_NOT_EXIST = "Does not exist" +C = TypeVar("C", bound=ParameterBase) + class InferError(AttributeError): ... @@ -220,3 +222,34 @@ def _merge_user_and_class_attrs( return set.union(set((alt_source_attrs,)), set(InferAttrs.known_attrs())) else: return set.union(set(alt_source_attrs), set(InferAttrs.known_attrs())) + + +def get_chain_links_of_type( + link_param_type: type[C] | tuple[type[C], ...], parameter: Parameter +) -> tuple[C, ...]: + """Gets all parameters in a chain of linked parameters that match a given type""" + chain_links: list[C] = [ + cast(C, param) + for param in get_parameter_chain(parameter) + if isinstance(param, link_param_type) + ] + return tuple(chain_links) + + +def get_sole_chain_link_of_type( + link_param_type: type[C] | tuple[type[C], ...], parameter: Parameter +) -> C: + """Gets the one parameter in a chain of linked parameters that matches a given type""" + + chain_links = get_chain_links_of_type( + link_param_type=link_param_type, parameter=parameter + ) + if len(chain_links) != 1: + if isinstance(link_param_type, type): + error_msg_1 = f"Expected only a single chain link of type {link_param_type.__name__} but found {len(chain_links)}: \n" + elif isinstance(link_param_type, tuple): + type_strs = [link_type.__name__ for link_type in link_param_type] + error_msg_1 = f"Expected only a single chain link of types {type_strs} but found {len(chain_links)}: \n" + + raise ValueError(error_msg_1 + f"{[link.name for link in chain_links]}") + return chain_links[0] diff --git a/tests/extensions/test_infer.py b/tests/extensions/test_infer.py index d0453b96e12d..9313b8696fc9 100644 --- a/tests/extensions/test_infer.py +++ b/tests/extensions/test_infer.py @@ -13,6 +13,8 @@ get_root_parameter, infer_channel, infer_instrument, + get_chain_links_of_type, + get_sole_chain_link_of_type, ) from qcodes.instrument import Instrument, InstrumentBase, InstrumentModule from qcodes.parameters import DelegateParameter, ManualParameter, Parameter @@ -280,3 +282,29 @@ def test_get_root_parameter_with_loops(good_inst_delegates): with pytest.raises(InferError) as exc_info: get_root_parameter(good_inst_del_2, "linked_parameter") assert "generated a loop of linking parameters" in str(exc_info.value) + + +def test_chain_links_of_type(): + root_param = ManualParameter("root", initial_value=0) + user_link = UserLinkingParameter("user_link", linked_parameter=root_param) + delegate_link = DelegateParameter("delegate_link", source=user_link) + user_link_2 = UserLinkingParameter("user_link_2", linked_parameter=delegate_link) + + InferAttrs.add("linked_parameter") + user_links = get_chain_links_of_type(UserLinkingParameter, user_link_2) + assert set(user_links) == set([user_link, user_link_2]) + + +def test_sole_chain_link_of_type(): + root_param = ManualParameter("root", initial_value=0) + user_link = UserLinkingParameter("user_link", linked_parameter=root_param) + delegate_link = DelegateParameter("delegate_link", source=user_link) + user_link_2 = UserLinkingParameter("user_link_2", linked_parameter=delegate_link) + + InferAttrs.add("linked_parameter") + user_links = get_chain_links_of_type(UserLinkingParameter, delegate_link) + assert set(user_links) == set([user_link]) + + with pytest.raises(ValueError) as exc_info: + user_links = get_sole_chain_link_of_type(UserLinkingParameter, user_link_2) + assert "Expected only a single chain link of type" in str(exc_info.value) From 820fb7cf6b38104c80ffc3893a421738d40c9546 Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Thu, 7 Nov 2024 11:17:12 -0800 Subject: [PATCH 2/4] Add newsfragment --- docs/changes/newsfragments/6599.new | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/newsfragments/6599.new diff --git a/docs/changes/newsfragments/6599.new b/docs/changes/newsfragments/6599.new new file mode 100644 index 000000000000..ea54f392efa8 --- /dev/null +++ b/docs/changes/newsfragments/6599.new @@ -0,0 +1 @@ +Added a new feature to find links in a parameter chain by parameter type \ No newline at end of file From 3aa875cda71ca81237e31c083c4ed073b271e249 Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Thu, 7 Nov 2024 11:51:22 -0800 Subject: [PATCH 3/4] Run pre-commit --- docs/changes/newsfragments/6599.new | 2 +- tests/extensions/test_infer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changes/newsfragments/6599.new b/docs/changes/newsfragments/6599.new index ea54f392efa8..5bff7f1693a2 100644 --- a/docs/changes/newsfragments/6599.new +++ b/docs/changes/newsfragments/6599.new @@ -1 +1 @@ -Added a new feature to find links in a parameter chain by parameter type \ No newline at end of file +Added a new feature to find links in a parameter chain by parameter type diff --git a/tests/extensions/test_infer.py b/tests/extensions/test_infer.py index 9313b8696fc9..b689f45e9619 100644 --- a/tests/extensions/test_infer.py +++ b/tests/extensions/test_infer.py @@ -9,12 +9,12 @@ InferAttrs, InferError, _merge_user_and_class_attrs, + get_chain_links_of_type, get_parameter_chain, get_root_parameter, + get_sole_chain_link_of_type, infer_channel, infer_instrument, - get_chain_links_of_type, - get_sole_chain_link_of_type, ) from qcodes.instrument import Instrument, InstrumentBase, InstrumentModule from qcodes.parameters import DelegateParameter, ManualParameter, Parameter From da2d5f421d936f873f97bc8812b850f7d3ea295a Mon Sep 17 00:00:00 2001 From: Samantha Ho Date: Fri, 8 Nov 2024 08:39:10 -0800 Subject: [PATCH 4/4] Modify tests for better code coverage --- tests/extensions/test_infer.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/extensions/test_infer.py b/tests/extensions/test_infer.py index b689f45e9619..13920ff68b42 100644 --- a/tests/extensions/test_infer.py +++ b/tests/extensions/test_infer.py @@ -302,9 +302,15 @@ def test_sole_chain_link_of_type(): user_link_2 = UserLinkingParameter("user_link_2", linked_parameter=delegate_link) InferAttrs.add("linked_parameter") - user_links = get_chain_links_of_type(UserLinkingParameter, delegate_link) - assert set(user_links) == set([user_link]) + sole_user_link = get_sole_chain_link_of_type(UserLinkingParameter, delegate_link) + assert sole_user_link == user_link with pytest.raises(ValueError) as exc_info: - user_links = get_sole_chain_link_of_type(UserLinkingParameter, user_link_2) + _ = get_sole_chain_link_of_type(UserLinkingParameter, user_link_2) assert "Expected only a single chain link of type" in str(exc_info.value) + + with pytest.raises(ValueError) as exc_info: + _ = get_sole_chain_link_of_type( + (UserLinkingParameter, DelegateParameter), user_link_2 + ) + assert "Expected only a single chain link of types" in str(exc_info.value)