Skip to content

Commit

Permalink
Limit Wildcards support to the LabOne wildcards
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasah committed May 25, 2023
1 parent a7d61c4 commit f8ba759
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 147 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# zhinst-toolkit Changelog

## Version 0.5.4
## Version 0.6.0
* Revert full support of `fnmatch` wildcards and instead use the LabOne wildcard support.
This means only `*` symbols are supported. A `*` in the middle of the path matches
everything instead of a `/`. A `*` at the end of the path matches everything.
* `device.factory_reset` now raises an exception if the factory reset was not successful (`#243`).
* Fixed issue where calling a `Node` with `dir()` returned duplicate values on some nodes.

Expand Down
4 changes: 3 additions & 1 deletion docs/source/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,6 @@ subtree
misconfiguration
pre
forwardwave
backwardwave
backwardwave
labone
LabOne
12 changes: 12 additions & 0 deletions src/zhinst/toolkit/nodetree/connection_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ def _get_value(self, path: str) -> t.Any:
return value()
return value

def _resolve_wildcards(self, path: str) -> t.List[str]:
path_raw = path.replace("/\\*/", "/[^/]*/")
path_raw_regex = re.compile(path_raw)
return list(filter(path_raw_regex.match, self._values.keys()))

def _set_value(self, path: str, value: t.Any) -> None:
"""Set the value for a given path.
Expand All @@ -54,6 +59,13 @@ def _set_value(self, path: str, value: t.Any) -> None:
path: Key in the internal values dictionary.
value: New value of the path.
"""
paths = self._resolve_wildcards(path)
if not paths:
raise KeyError(path)
for path in paths:
self._do_set_value(path, value)

def _do_set_value(self, path: str, value: t.Any) -> None:
if callable(self._values[path]):
self._values[path](value)
else:
Expand Down
15 changes: 15 additions & 0 deletions src/zhinst/toolkit/nodetree/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from contextlib import contextmanager
from functools import lru_cache
from collections.abc import Mapping
import re

# TypedDict is available in the typing module since 3.8
# Ift we only support 3.8 we should switch to t.TypedDict
Expand Down Expand Up @@ -73,6 +74,20 @@ def create_or_append_set_transaction(nodetree) -> t.Generator[None, None, None]:
yield


def resolve_wildcards_labone(path: str, nodes: t.List[str]) -> t.List[str]:
"""Resolves potential wildcards.
Also will resolve partial nodes to its leaf nodes.
Returns:
List of matched nodes in the raw path format
"""
node_raw = re.escape(path)
node_raw = node_raw.replace("/\\*/", "/[^/]*/").replace("/\\*", "/*") + "(/.*)?$"
node_raw_regex = re.compile(node_raw)
return list(filter(node_raw_regex.match, nodes))


class NodeDict(Mapping):
"""Mapping of dictionary structure results.
Expand Down
133 changes: 24 additions & 109 deletions src/zhinst/toolkit/nodetree/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
from functools import lru_cache

from zhinst.toolkit.nodetree.helper import (
create_or_append_set_transaction,
lazy_property,
NodeDict,
lazy_property,
resolve_wildcards_labone,
)

from zhinst.core.errors import CoreError

if t.TYPE_CHECKING: # pragma: no cover
from zhinst.toolkit.nodetree import NodeTree

Expand Down Expand Up @@ -563,8 +561,8 @@ def _resolve_wildcards(self) -> t.List[str]:
Returns:
List of matched nodes in the raw path format
"""
return fnmatch.filter(
self._root.raw_dict.keys(), self._root.node_to_raw_path(self) + "*"
return resolve_wildcards_labone(
self._root.node_to_raw_path(self), self._root.raw_dict.keys()
)

def _parse_get_value(
Expand Down Expand Up @@ -711,16 +709,7 @@ def _get_wildcard(
result_raw = self._root.connection.get(self.node_info.path, **kwargs)
except TypeError:
del kwargs["settingsonly"]
try:
result_raw = self._root.connection.get(self.node_info.path, **kwargs)
except (RuntimeError, TypeError):
# resolve wildecard and get the value of the resulting leaf nodes
nodes_raw = self._resolve_wildcards()
result_raw = self._root.connection.get(",".join(nodes_raw), **kwargs)
except RuntimeError:
# resolve wildecard and get the value of the resulting leaf nodes
nodes_raw = self._resolve_wildcards()
result_raw = self._root.connection.get(",".join(nodes_raw), **kwargs)
result_raw = self._root.connection.get(self.node_info.path, **kwargs)
if not result_raw:
raise KeyError(self.node_info.path)
if not kwargs["flat"]:
Expand Down Expand Up @@ -831,7 +820,7 @@ def _set(
TypeError: Connection does not support deep set
"""
writable = self.node_info.writable
if writable:
if writable or self.node_info.contains_wildcards:
if parse:
value = self.node_info.set_parser(value)
if self._root.transaction.in_progress():
Expand All @@ -852,35 +841,12 @@ def _set(
else:
raise
return None
if writable is None and (
self.node_info.contains_wildcards or self.node_info.is_partial
):
self._set_wildcard(value, parse=parse, **kwargs)
return None
if self.node_info.is_partial:
return self["*"](value, deep=deep, enum=enum, parse=parse, **kwargs)
if writable is False:
raise AttributeError(f"{self.node_info.path} is read-only.")
raise KeyError(self.node_info.path)

def _set_wildcard(self, value: t.Any, parse: bool = True, **kwargs) -> None:
"""Performs a transactional set on all nodes that match the wildcard.
The kwargs will be forwarded to the mapped zhinst.core function call.
Args:
value: value
parse: Flag if the SetParser, if present, should be applied or not.
(default=True)
Raises:
KeyError: if the wildcard does not resolve to a valid node
"""
nodes_raw = self._resolve_wildcards()
if not nodes_raw:
raise KeyError(self._root.node_to_raw_path(self))
with create_or_append_set_transaction(self._root):
for node_raw in nodes_raw:
self._root.raw_path_to_node(node_raw)(value, parse=parse, **kwargs)

def _set_deep(self, value: t.Any, **kwargs) -> t.Any:
"""Set the node value from device.
Expand Down Expand Up @@ -1004,11 +970,7 @@ def subscribe(self) -> None:
try:
self._root.connection.subscribe(self.node_info.path)
except RuntimeError as error:
nodes_raw = self._resolve_wildcards()
if not nodes_raw:
raise KeyError(self.node_info.path) from error
for node_raw in nodes_raw:
self._root.connection.subscribe(node_raw)
raise KeyError(self.node_info.path) from error

def unsubscribe(self) -> None:
"""Unsubscribe this node (its child lead nodes).
Expand All @@ -1019,11 +981,7 @@ def unsubscribe(self) -> None:
try:
self._root.connection.unsubscribe(self.node_info.path)
except RuntimeError as error:
nodes_raw = self._resolve_wildcards()
if not nodes_raw:
raise KeyError(self.node_info.path) from error
for node_raw in nodes_raw:
self._root.connection.unsubscribe(node_raw)
raise KeyError(self.node_info.path) from error

def get_as_event(self) -> None:
"""Trigger an event for that node (its child lead nodes).
Expand All @@ -1033,11 +991,7 @@ def get_as_event(self) -> None:
try:
self._root.connection.getAsEvent(self.node_info.path)
except RuntimeError as error:
nodes_raw = self._resolve_wildcards()
if not nodes_raw:
raise KeyError(self.node_info.path) from error
for node_raw in nodes_raw:
self._root.connection.getAsEvent(node_raw)
raise KeyError(self.node_info.path) from error

def child_nodes(
self,
Expand All @@ -1050,7 +1004,6 @@ def child_nodes(
basechannelonly: bool = False,
excludestreaming: bool = False,
excludevectors: bool = False,
full_wildcard: bool = False,
) -> t.Generator["Node", None, None]:
"""Generator for all child nodes that matches the filters.
Expand Down Expand Up @@ -1086,62 +1039,24 @@ def child_nodes(
of multiple channels (default: False).
excludestreaming: Exclude streaming nodes (default: False).
excludevectors: Exclude vector nodes (default: False).
full_wildcard: Enables full wildcard support. Per default
only the asterisk wildcard is supported. (Automatically sets
recursive and leavesonly) (default = False)
Returns:
Generator of all child nodes that match the filters
"""
raw_path = self._root.node_to_raw_path(self)
try:
raw_result = self._root.connection.listNodes(
raw_path,
recursive=recursive,
leavesonly=leavesonly,
settingsonly=settingsonly,
streamingonly=streamingonly,
subscribedonly=subscribedonly,
basechannelonly=basechannelonly,
excludestreaming=excludestreaming,
excludevectors=excludevectors,
)
for node_raw in raw_result:
yield self._root.raw_path_to_node(node_raw)
except CoreError as error:
if error.code != 32768: # Replace with correct error in 23.02
raise
if not full_wildcard:
raise RuntimeError(
"The node contains wildcards that the DataServer can not resolve. "
"Use the `full_wildcard` flag to search for child nodes manually."
) from error
nodes_raw = self._resolve_wildcards()
for node_raw in nodes_raw:
node = self._root.raw_path_to_node(node_raw)
if not (
(settingsonly and not node.node_info.is_setting)
or (excludevectors and node.node_info.is_vector)
or (
basechannelonly
and any(number != 0 for number in re.findall(r"\d+", node_raw))
)
or (
(excludestreaming or subscribedonly or streamingonly)
and not self._root.connection.listNodes(
node_raw[:-1] + "*", # TODO remove once listNodes is fixed
recursive=recursive,
leavesonly=leavesonly,
settingsonly=settingsonly,
streamingonly=streamingonly,
subscribedonly=subscribedonly,
basechannelonly=basechannelonly,
excludestreaming=excludestreaming,
excludevectors=excludevectors,
)
)
):
yield node
raw_result = self._root.connection.listNodes(
raw_path,
recursive=recursive,
leavesonly=leavesonly,
settingsonly=settingsonly,
streamingonly=streamingonly,
subscribedonly=subscribedonly,
basechannelonly=basechannelonly,
excludestreaming=excludestreaming,
excludevectors=excludevectors,
)
for node_raw in raw_result:
yield self._root.raw_path_to_node(node_raw)

@lru_cache()
def is_valid(self) -> bool:
Expand Down
20 changes: 4 additions & 16 deletions tests/test_hdawg.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,8 @@ def test_enable_qccs_mode(mock_connection, hdawg):
("/dev1234/dios/0/interface", 0),
("/dev1234/dios/0/mode", "qccs"),
("/dev1234/dios/0/drive", 12),
("/dev1234/awgs/0/dio/strobe/slope", "off"),
("/dev1234/awgs/1/dio/strobe/slope", "off"),
("/dev1234/awgs/2/dio/strobe/slope", "off"),
("/dev1234/awgs/3/dio/strobe/slope", "off"),
("/dev1234/awgs/0/dio/valid/polarity", "none"),
("/dev1234/awgs/1/dio/valid/polarity", "none"),
("/dev1234/awgs/2/dio/valid/polarity", "none"),
("/dev1234/awgs/3/dio/valid/polarity", "none"),
("/dev1234/awgs/*/dio/strobe/slope", "off"),
("/dev1234/awgs/*/dio/valid/polarity", "none"),
]
)
mock_connection.reset_mock()
Expand All @@ -55,14 +49,8 @@ def test_enable_qccs_mode(mock_connection, hdawg):
("/dev1234/dios/0/interface", 0),
("/dev1234/dios/0/mode", "qccs"),
("/dev1234/dios/0/drive", 12),
("/dev1234/awgs/0/dio/strobe/slope", "off"),
("/dev1234/awgs/1/dio/strobe/slope", "off"),
("/dev1234/awgs/2/dio/strobe/slope", "off"),
("/dev1234/awgs/3/dio/strobe/slope", "off"),
("/dev1234/awgs/0/dio/valid/polarity", "none"),
("/dev1234/awgs/1/dio/valid/polarity", "none"),
("/dev1234/awgs/2/dio/valid/polarity", "none"),
("/dev1234/awgs/3/dio/valid/polarity", "none"),
("/dev1234/awgs/*/dio/strobe/slope", "off"),
("/dev1234/awgs/*/dio/valid/polarity", "none"),
]
)

Expand Down
Loading

0 comments on commit f8ba759

Please sign in to comment.