Skip to content

Commit ddaa400

Browse files
fix(instrument): silence get_idn warning on virtual instruments
``Instrument.get_idn`` calls ``self.ask("*IDN?")`` which in turn invokes ``ask_raw``. The base ``Instrument`` class leaves ``ask_raw`` as a stub that raises ``NotImplementedError``; virtual instruments that do not override it therefore always produced a warning on every call to ``get_idn``. This is noisy in practice, for example when a virtual instrument is registered in a station and the station queries its IDN. Treat ``NotImplementedError`` specifically as the expected signal that the instrument is virtual and return the default ``None`` dict without a warning. All other exceptions are still surfaced via ``self.log.warning`` so genuine communication failures remain visible. Adds focused regression tests covering both the silenced virtual-instrument path and the still-noisy unexpected-error path. Closes #4611 Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
1 parent 36967bf commit ddaa400

3 files changed

Lines changed: 66 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Calling :meth:`~qcodes.instrument.Instrument.get_idn` on a virtual instrument that
2+
does not implement ``ask_raw`` no longer logs a spurious warning. Only
3+
``NotImplementedError`` is treated as an expected virtual-instrument signal;
4+
unrelated errors during ``*IDN?`` handling are still reported as warnings.

src/qcodes/instrument/instrument.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ def get_idn(self) -> dict[str, str | None]:
9191
semicolon and colon are also common separators so we accept them here
9292
as well.
9393
94+
Virtual instruments that do not define ``ask_raw`` will produce a dict
95+
with ``None`` entries (and ``name`` as the model) without emitting a
96+
warning, since the absence of real hardware communication is expected.
97+
9498
Returns:
9599
A dict containing vendor, model, serial, and firmware.
96100
@@ -109,6 +113,11 @@ def get_idn(self) -> dict[str, str | None]:
109113
# in case parts at the end are missing, fill in None
110114
if len(idparts) < 4:
111115
idparts += [None] * (4 - len(idparts))
116+
except NotImplementedError:
117+
# Virtual instruments inherit from Instrument without overriding
118+
# ``ask_raw``; treat this as an expected condition rather than a
119+
# misconfiguration, so the user does not see a spurious warning.
120+
idparts = [None, self.name, None, None]
112121
except Exception:
113122
self.log.warning(
114123
f"Error getting or interpreting *IDN?: {idstr!r}", exc_info=True

tests/test_instrument.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,59 @@ def test_get_idn(testdummy: DummyInstrument) -> None:
256256
assert testdummy.get_idn() == idn
257257

258258

259+
def test_get_idn_on_virtual_instrument_does_not_warn(
260+
caplog: pytest.LogCaptureFixture,
261+
) -> None:
262+
"""Virtual instruments that inherit from ``Instrument`` without overriding
263+
``ask_raw`` must not log a warning when ``get_idn`` is called; the missing
264+
hardware communication layer is expected. The returned dict should still be
265+
structurally valid.
266+
"""
267+
268+
class VirtualInstrument(Instrument):
269+
"""A bare virtual instrument with no ``ask_raw`` implementation."""
270+
271+
virtual = VirtualInstrument(name="virtual_no_ask")
272+
try:
273+
with caplog.at_level("WARNING"):
274+
idn = virtual.get_idn()
275+
assert idn == {
276+
"vendor": None,
277+
"model": "virtual_no_ask",
278+
"serial": None,
279+
"firmware": None,
280+
}
281+
assert "Error getting or interpreting *IDN?" not in caplog.text
282+
finally:
283+
virtual.close()
284+
285+
286+
def test_get_idn_still_warns_on_other_errors(
287+
caplog: pytest.LogCaptureFixture,
288+
) -> None:
289+
"""Unexpected errors in ``*IDN?`` handling should still be surfaced via a
290+
warning so real misbehaviour is not silenced by the virtual-instrument
291+
short-circuit."""
292+
293+
class BrokenInstrument(Instrument):
294+
def ask_raw(self, cmd: str) -> str:
295+
raise RuntimeError("communication failure")
296+
297+
broken = BrokenInstrument(name="broken_ask")
298+
try:
299+
with caplog.at_level("WARNING"):
300+
idn = broken.get_idn()
301+
assert idn == {
302+
"vendor": None,
303+
"model": "broken_ask",
304+
"serial": None,
305+
"firmware": None,
306+
}
307+
assert "Error getting or interpreting *IDN?" in caplog.text
308+
finally:
309+
broken.close()
310+
311+
259312
def test_repr(testdummy: DummyInstrument) -> None:
260313
assert repr(testdummy) == "<DummyInstrument: testdummy>"
261314

0 commit comments

Comments
 (0)