From f41bf8f9e799048c4067f30fb0fca9d04b9fd22a Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Fri, 31 Jul 2026 14:09:58 +0200 Subject: [PATCH 1/7] style: Enable ruff N999 (invalid-module-name) Instrument driver modules that are part of the public qcodes namespace cannot be renamed without breaking user code, so they are exempted via lint.pep8-naming.extend-ignore-names. Test modules with invalid names are renamed to lower case. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- docs/changes/newsfragments/8356.underthehood | 3 + pyproject.toml | 154 +++++++++++++++++- .../dond/{test_doNd.py => test_dond.py} | 0 ...est_commandList.py => test_commandlist.py} | 0 ...ssageBuilder.py => test_messagebuilder.py} | 0 ...ilent_E8257D.py => test_agilent_e8257d.py} | 0 ...AimTTi_PL601P.py => test_aimtti_pl601p.py} | 0 ..._M5065.py => test_coppermountain_m5065.py} | 0 ..._M5180.py => test_coppermountain_m5180.py} | 0 ...s_TM620.py => test_cryomagnetics_tm620.py} | 0 ...Keithley_2450.py => test_keithley_2450.py} | 0 ...ithley_3706A.py => test_keithley_3706a.py} | 0 ...ysight_33XXX.py => test_keysight_33xxx.py} | 0 ...ight_N6705B.py => test_keysight_n6705b.py} | 0 ...{test_MercuryiPS.py => test_mercuryips.py} | 0 ...Rigol_DS1074Z.py => test_rigol_ds1074z.py} | 0 ...{test_RS_SGS100A.py => test_rs_sgs100a.py} | 0 ...AWG5014C.py => test_tektronix_awg5014c.py} | 0 ...x_AWG5208.py => test_tektronix_awg5208.py} | 0 ...G70000A.py => test_tektronix_awg70000a.py} | 0 20 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 docs/changes/newsfragments/8356.underthehood rename tests/dataset/dond/{test_doNd.py => test_dond.py} (100%) rename tests/drivers/keysight_b1500/{test_commandList.py => test_commandlist.py} (100%) rename tests/drivers/keysight_b1500/{test_MessageBuilder.py => test_messagebuilder.py} (100%) rename tests/drivers/{test_Agilent_E8257D.py => test_agilent_e8257d.py} (100%) rename tests/drivers/{test_AimTTi_PL601P.py => test_aimtti_pl601p.py} (100%) rename tests/drivers/{test_CopperMountain_M5065.py => test_coppermountain_m5065.py} (100%) rename tests/drivers/{test_CopperMountain_M5180.py => test_coppermountain_m5180.py} (100%) rename tests/drivers/{test_cryomagnetics_TM620.py => test_cryomagnetics_tm620.py} (100%) rename tests/drivers/{test_Keithley_2450.py => test_keithley_2450.py} (100%) rename tests/drivers/{test_keithley_3706A.py => test_keithley_3706a.py} (100%) rename tests/drivers/{test_Keysight_33XXX.py => test_keysight_33xxx.py} (100%) rename tests/drivers/{test_Keysight_N6705B.py => test_keysight_n6705b.py} (100%) rename tests/drivers/{test_MercuryiPS.py => test_mercuryips.py} (100%) rename tests/drivers/{test_Rigol_DS1074Z.py => test_rigol_ds1074z.py} (100%) rename tests/drivers/{test_RS_SGS100A.py => test_rs_sgs100a.py} (100%) rename tests/drivers/{test_tektronix_AWG5014C.py => test_tektronix_awg5014c.py} (100%) rename tests/drivers/{test_tektronix_AWG5208.py => test_tektronix_awg5208.py} (100%) rename tests/drivers/{test_tektronix_AWG70000A.py => test_tektronix_awg70000a.py} (100%) diff --git a/docs/changes/newsfragments/8356.underthehood b/docs/changes/newsfragments/8356.underthehood new file mode 100644 index 000000000000..b98ca93675b5 --- /dev/null +++ b/docs/changes/newsfragments/8356.underthehood @@ -0,0 +1,3 @@ +Enable the ruff rule ``N999`` (``invalid-module-name``). Instrument driver modules that are part of the +public ``qcodes`` namespace are exempted via ``lint.pep8-naming.extend-ignore-names`` in ``pyproject.toml`` +so that no public module names change. Test modules with invalid names have been renamed to lower case. diff --git a/pyproject.toml b/pyproject.toml index 0ff7239f30e9..acdd6757b1ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,7 +258,7 @@ extend-select = [ # it may be worth fixing some or these in the future # PYI036 disable until https://github.com/astral-sh/ruff/issues/9794 is fixed ignore = ["E501", "G004", "PLR2004", "PLR0913", "PLR0911", "PLR0912", "PLR0915", "PLW0602", "PLW0603", "PLW2901", "PYI036", "PLR0917", - "N999", "BLE001", "TRY004", "TRY002", "S110"] # new defaults of 0.16.0 that we don't want to enable yet + "BLE001", "TRY004", "TRY002", "S110"] # new defaults of 0.16.0 that we don't want to enable yet # we want to explicitly use the micro symbol # not the greek letter @@ -283,6 +283,158 @@ banned-module-level-imports = [ "xarray", "cf_xarray","pandas", "opencensus", "tqdm.dask", "dask", "matplotlib", "IPython", "ruamel", "tabulate", "h5netcdf", "PIL", "qcodes_loop"] + +[tool.ruff.lint.pep8-naming] +# N999 (invalid-module-name): these instrument driver modules are part of +# the public qcodes namespace so they cannot be renamed to lower case +# without breaking user code. New modules should use lower case names. +extend-ignore-names = [ + "_Agilent_344xxA", + "_AimTTi_PL_P", + "_Keithley_2600", + "_Keysight_N5232B", + "_M5065", + "_M5180", + "_M5xxx", + "_TM620", + "Agilent_34401A", + "Agilent_34410A", + "Agilent_34411A", + "Agilent_E8257D", + "Agilent_E8267C", + "Aim_TTi_PL068_P", + "Aim_TTi_PL155_P", + "Aim_TTi_PL303_P", + "Aim_TTi_PL303QMD_P", + "Aim_TTi_PL303QMT_P", + "Aim_TTi_PL601_P", + "Aim_TTi_QL355_TP", + "AMI430_visa", + "ATS", + "ATS_acquisition_controllers", + "ATS9360", + "ATS9373", + "ATS9440", + "ATS9870", + "AWG5014", + "AWG5208", + "AWG70000A", + "AWG70002A", + "AWGFileParser", + "Base_SPDT", + "BaselSP983", + "BaselSP983a", + "BaselSP983c", + "Decadac", + "DP8xx", + "DPO7200xx", + "DynaCool", + "HP_8133A", + "HP_83650A", + "HP_8753D", + "Infiniium", + "Ithaco_1211", + "Keithley_2000", + "Keithley_2400", + "Keithley_2450", + "Keithley_2601B", + "Keithley_2602A", + "Keithley_2602B", + "Keithley_2604B", + "Keithley_2611B", + "Keithley_2612B", + "Keithley_2614B", + "Keithley_2634B", + "Keithley_2635B", + "Keithley_2636B", + "Keithley_3706A", + "Keithley_6500", + "Keithley_7510", + "Keithley_s46", + "Keysight_33210a", + "Keysight_33250a", + "Keysight_33510b", + "Keysight_33511b", + "Keysight_33512b", + "Keysight_33521b", + "Keysight_33522b", + "Keysight_33611a", + "Keysight_33612a", + "Keysight_33621a", + "Keysight_33622a", + "Keysight_34410A_submodules", + "Keysight_34411A_submodules", + "Keysight_34460A_submodules", + "Keysight_34461A_submodules", + "Keysight_34465A_submodules", + "Keysight_34470A_submodules", + "Keysight_344xxA_submodules", + "Keysight_B2962A", + "Keysight_N5173B", + "Keysight_N5183B", + "Keysight_N5222B", + "Keysight_N5230C", + "Keysight_N5245A", + "Keysight_N6705B", + "Keysight_N9030B", + "Keysight_P5002B", + "Keysight_P5004B", + "Keysight_P9374A", + "KeysightAgilent_33XXX", + "KeysightB1500_base", + "KeysightB1500_module", + "KeysightB1500_sampling_measurement", + "KeysightB1511B", + "KeysightB1517A", + "KeysightB1520A", + "KeysightB1530A", + "KtM960x", + "KtM960xDefs", + "KtMAwg", + "KtMAwgDefs", + "Lakeshore_model_325", + "Lakeshore_model_336", + "Lakeshore_model_372", + "MercuryiPS_VISA", + "N51x1", + "N52xx", + "QDac_channels", + "Rigol_DG1062", + "Rigol_DG4000", + "Rigol_DP821", + "Rigol_DP831", + "Rigol_DP832", + "Rigol_DS1074Z", + "Rigol_DS4000", + "Rohde_Schwarz_ZNB20", + "Rohde_Schwarz_ZNB8", + "RTO1000", + "SG384", + "SGS100A", + "SignalHound_USB_SA124B", + "SR560", + "SR830", + "SR860", + "SR865", + "SR865A", + "SR86x", + "Tektronix_70001A", + "Tektronix_70001B", + "Tektronix_70002B", + "Tektronix_DPO5000", + "Tektronix_DPO7000", + "Tektronix_DPO70000", + "Tektronix_DSA70000", + "Tektronix_MSO5000", + "Tektronix_MSO70000", + "TPS2012", + "USBHIDMixin", + "Weinschel_8320", + "Yokogawa_GS200", + "ZNB", + "ZNB20", +] + [tool.ruff.lint.pydocstyle] convention = "google" diff --git a/tests/dataset/dond/test_doNd.py b/tests/dataset/dond/test_dond.py similarity index 100% rename from tests/dataset/dond/test_doNd.py rename to tests/dataset/dond/test_dond.py diff --git a/tests/drivers/keysight_b1500/test_commandList.py b/tests/drivers/keysight_b1500/test_commandlist.py similarity index 100% rename from tests/drivers/keysight_b1500/test_commandList.py rename to tests/drivers/keysight_b1500/test_commandlist.py diff --git a/tests/drivers/keysight_b1500/test_MessageBuilder.py b/tests/drivers/keysight_b1500/test_messagebuilder.py similarity index 100% rename from tests/drivers/keysight_b1500/test_MessageBuilder.py rename to tests/drivers/keysight_b1500/test_messagebuilder.py diff --git a/tests/drivers/test_Agilent_E8257D.py b/tests/drivers/test_agilent_e8257d.py similarity index 100% rename from tests/drivers/test_Agilent_E8257D.py rename to tests/drivers/test_agilent_e8257d.py diff --git a/tests/drivers/test_AimTTi_PL601P.py b/tests/drivers/test_aimtti_pl601p.py similarity index 100% rename from tests/drivers/test_AimTTi_PL601P.py rename to tests/drivers/test_aimtti_pl601p.py diff --git a/tests/drivers/test_CopperMountain_M5065.py b/tests/drivers/test_coppermountain_m5065.py similarity index 100% rename from tests/drivers/test_CopperMountain_M5065.py rename to tests/drivers/test_coppermountain_m5065.py diff --git a/tests/drivers/test_CopperMountain_M5180.py b/tests/drivers/test_coppermountain_m5180.py similarity index 100% rename from tests/drivers/test_CopperMountain_M5180.py rename to tests/drivers/test_coppermountain_m5180.py diff --git a/tests/drivers/test_cryomagnetics_TM620.py b/tests/drivers/test_cryomagnetics_tm620.py similarity index 100% rename from tests/drivers/test_cryomagnetics_TM620.py rename to tests/drivers/test_cryomagnetics_tm620.py diff --git a/tests/drivers/test_Keithley_2450.py b/tests/drivers/test_keithley_2450.py similarity index 100% rename from tests/drivers/test_Keithley_2450.py rename to tests/drivers/test_keithley_2450.py diff --git a/tests/drivers/test_keithley_3706A.py b/tests/drivers/test_keithley_3706a.py similarity index 100% rename from tests/drivers/test_keithley_3706A.py rename to tests/drivers/test_keithley_3706a.py diff --git a/tests/drivers/test_Keysight_33XXX.py b/tests/drivers/test_keysight_33xxx.py similarity index 100% rename from tests/drivers/test_Keysight_33XXX.py rename to tests/drivers/test_keysight_33xxx.py diff --git a/tests/drivers/test_Keysight_N6705B.py b/tests/drivers/test_keysight_n6705b.py similarity index 100% rename from tests/drivers/test_Keysight_N6705B.py rename to tests/drivers/test_keysight_n6705b.py diff --git a/tests/drivers/test_MercuryiPS.py b/tests/drivers/test_mercuryips.py similarity index 100% rename from tests/drivers/test_MercuryiPS.py rename to tests/drivers/test_mercuryips.py diff --git a/tests/drivers/test_Rigol_DS1074Z.py b/tests/drivers/test_rigol_ds1074z.py similarity index 100% rename from tests/drivers/test_Rigol_DS1074Z.py rename to tests/drivers/test_rigol_ds1074z.py diff --git a/tests/drivers/test_RS_SGS100A.py b/tests/drivers/test_rs_sgs100a.py similarity index 100% rename from tests/drivers/test_RS_SGS100A.py rename to tests/drivers/test_rs_sgs100a.py diff --git a/tests/drivers/test_tektronix_AWG5014C.py b/tests/drivers/test_tektronix_awg5014c.py similarity index 100% rename from tests/drivers/test_tektronix_AWG5014C.py rename to tests/drivers/test_tektronix_awg5014c.py diff --git a/tests/drivers/test_tektronix_AWG5208.py b/tests/drivers/test_tektronix_awg5208.py similarity index 100% rename from tests/drivers/test_tektronix_AWG5208.py rename to tests/drivers/test_tektronix_awg5208.py diff --git a/tests/drivers/test_tektronix_AWG70000A.py b/tests/drivers/test_tektronix_awg70000a.py similarity index 100% rename from tests/drivers/test_tektronix_AWG70000A.py rename to tests/drivers/test_tektronix_awg70000a.py From 31ae87e89125bfedb3cfc464dc488cf276a40e28 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Fri, 31 Jul 2026 14:29:19 +0200 Subject: [PATCH 2/7] docs: Document that instrument driver modules use snake_case names Update the Contributor guide and the "Creating Instrument Drivers" example notebook to state that driver modules should be named using lower case snake_case, with vendor/model capitalization only in the class name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- CONTRIBUTING.rst | 8 +++++++ docs/changes/newsfragments/8356.improved | 4 ++++ .../Creating-Instrument-Drivers.ipynb | 21 ++++++++++++------- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 docs/changes/newsfragments/8356.improved diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 72e55526e8c4..d199f40c7948 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -244,6 +244,14 @@ Coding Style - Use `PEP8 `__ style. Not only is this style good for readability in an absolute sense, but consistent styling helps us all read each other's code. +- This includes module names: all modules, including instrument driver + modules, should be named using lower case ``snake_case``, e.g. + ``weinschel_8320.py`` and not ``Weinschel_8320.py``. Vendor and model + capitalization belongs in the class name (``Weinschel8320``), not in the + module name. This is enforced by the ruff rule ``N999`` + (``invalid-module-name``). A number of existing driver modules predate this + rule and are exempted in ``pyproject.toml`` because renaming them would + break user code; new modules should not be added to that exemption list. - There is a command-line tool (``pip install pycodestyle``) you can run after writing code to validate its style. - A lot of editors have plugins that will check this for you diff --git a/docs/changes/newsfragments/8356.improved b/docs/changes/newsfragments/8356.improved new file mode 100644 index 000000000000..3244dfdc7227 --- /dev/null +++ b/docs/changes/newsfragments/8356.improved @@ -0,0 +1,4 @@ +The documentation on naming instrument drivers (in the Contributor guide and in the +"Creating Instrument Drivers" example notebook) has been updated to state that instrument driver +modules should be named using lower case ``snake_case``, e.g. ``weinschel_8320.py``. Vendor and +model capitalization belongs in the class name (``Weinschel8320``) and not in the module name. diff --git a/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb b/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb index 62d7174e64c8..5699f5e638f9 100644 --- a/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb +++ b/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb @@ -124,13 +124,13 @@ "The same rules should apply for QCoDeS-contrib-drivers with the exception that all drivers are stored in subfolders of the drivers folder. \n", "\n", "### Naming the Instrument class\n", - "A driver for an instrument with model `Model` and from the vendor `Vendor` should be stored in the file:\n", + "A driver for an instrument with model `Model` and from the vendor `Vendor` should be stored in the module:\n", "\n", "```\n", - "qcodes\\instrument_drivers\\{Vendor}\\{Vendor}_{Model}.py \n", + "qcodes\\instrument_drivers\\{Vendor}\\{vendor}_{model}.py \n", "```\n", - "using snake case with an underscore between the vendor and model name but starting the\n", - "vendor name with upper case.\n", + "i.e. the module (file) name should be all lower case snake case with an underscore between the\n", + "vendor name and the model name.\n", "\n", "The primary instrument class should be named as follows:\n", "```\n", @@ -139,12 +139,17 @@ "```\n", "E.g Vendor followed by Model number in CamelCase.\n", "\n", - "Note that we use vendor names starting with upper case for both folders and file names.\n", + "Note that we use vendor names starting with upper case for folders but all lower case for module\n", + "(file) names. Module names must be valid lower case snake case identifiers; this is enforced by the\n", + "`N999` (`invalid-module-name`) ruff rule. A number of existing driver modules predate this rule and\n", + "are still named using `Vendor_Model` capitalization. Those are exempted via\n", + "`lint.pep8-naming.extend-ignore-names` in `pyproject.toml`, since renaming them would break user\n", + "code, but no new exemptions should be added.\n", "\n", "It is also fine to use an acronym for instrument vendors when there are well established. E.g. drivers for `American Magnetics Inc.` instruments\n", "may use the acronym `AMI` to refer to the vendor.\n", "\n", - "As an example the driver for the Weinschel 8320 should be stored in the file `qcodes\\instrument_drivers\\Weinschel\\Weinschel_8320.py` and the \n", + "As an example the driver for the Weinschel 8320 should be stored in the module `qcodes\\instrument_drivers\\Weinschel\\weinschel_8320.py` and the \n", "class named `Weinschel8320` \n", "\n", "### Naming InstrumentModule classes\n", @@ -161,11 +166,11 @@ "\n", "As an example have a look at the Keysight 344xxA series of digital multi meters. To implement drivers for such instruments it is preferable\n", "to implement a private base class such as `_Keysight344xxA`. This class should be stored either in a `private` subfolder of the Vendor folder or\n", - "in a file starting with an underscore i.e. `_Keysight344xxA.py`. If possible, we prefer a format where `x` is used to signal the parts of the model numbers that \n", + "in a module starting with an underscore i.e. `_keysight_344xxa.py`. If possible, we prefer a format where `x` is used to signal the parts of the model numbers that \n", "may change. Along with this class subclasses for each of the supported models should be implemented. These may either make small modifications to the baseclass as needed \n", "or be empty subclasses if no modifications are needed. \n", "\n", - "E.g. subclasses of the Keysight 344xxA driver for the specific model `34410A` should be named as `Keysight34410A` and stored in `Keysight34410A.py`.\n", + "E.g. subclasses of the Keysight 344xxA driver for the specific model `34410A` should be named as `Keysight34410A` and stored in `keysight_34410a.py`.\n", "\n" ] }, From d8ce9b547104794af5946039b14db8296fa2a2d9 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 3 Aug 2026 09:25:33 +0200 Subject: [PATCH 3/7] style: Enable ruff BLE001 (blind-except) Rename the private module level loggers `_LOG` to `_LOGGER` so that ruff recognises them as loggers, switch several catch-all handlers to `Logger.exception` so that the traceback is logged, narrow the exceptions caught in the Triton driver, chain the exception raised by AWG70000A and explicitly mark the remaining intentional catch-alls with `noqa: BLE001`. The example notebooks deliberately catch broad exceptions to demonstrate that an operation is rejected, so `docs/*` ignores the rule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- .../changes/newsfragments/8356.underthehood.1 | 4 +++ pyproject.toml | 6 ++-- src/qcodes/dataset/data_set.py | 6 ++-- src/qcodes/dataset/database_extract_runs.py | 30 +++++++++---------- .../dataset/exporters/export_to_xarray.py | 12 ++++---- src/qcodes/dataset/threading.py | 2 +- src/qcodes/extensions/_refactor.py | 6 ++-- src/qcodes/extensions/installation.py | 4 +-- src/qcodes/instrument/instrument.py | 2 +- .../instrument_drivers/Keysight/Infiniium.py | 4 +-- .../instrument_drivers/oxford/triton.py | 2 +- .../signal_hound/SignalHound_USB_SA124B.py | 4 +-- .../instrument_drivers/tektronix/AWG5014.py | 5 ++-- .../instrument_drivers/tektronix/AWG70000A.py | 2 +- src/qcodes/interactive_widget.py | 2 +- src/qcodes/parameters/combined_parameter.py | 4 +-- .../multi_channel_instrument_parameter.py | 4 +-- src/qcodes/plotting/matplotlib_helpers.py | 4 +-- .../sphinx_extensions/parse_parameter_attr.py | 2 +- src/qcodes/station.py | 4 +-- src/qcodes/utils/attribute_helpers.py | 4 +-- src/qcodes/utils/installation_info.py | 4 +-- src/qcodes/utils/threading_utils.py | 6 ++-- src/qcodes/validators/validators.py | 2 +- .../test_parameter_on_set_callback.py | 2 +- tests/test_logger.py | 6 ++-- 26 files changed, 69 insertions(+), 64 deletions(-) create mode 100644 docs/changes/newsfragments/8356.underthehood.1 diff --git a/docs/changes/newsfragments/8356.underthehood.1 b/docs/changes/newsfragments/8356.underthehood.1 new file mode 100644 index 000000000000..85c23d8517cc --- /dev/null +++ b/docs/changes/newsfragments/8356.underthehood.1 @@ -0,0 +1,4 @@ +Enable the ruff rule ``BLE001`` (``blind-except``). Places where QCoDeS intentionally catches a +broad exception are now explicitly marked, and several catch-all handlers now log the full traceback +using ``Logger.exception`` rather than only the exception message. The private module level loggers +named ``_LOG`` have been renamed to ``_LOGGER``. diff --git a/pyproject.toml b/pyproject.toml index acdd6757b1ae..0d708b4e07fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,7 +258,7 @@ extend-select = [ # it may be worth fixing some or these in the future # PYI036 disable until https://github.com/astral-sh/ruff/issues/9794 is fixed ignore = ["E501", "G004", "PLR2004", "PLR0913", "PLR0911", "PLR0912", "PLR0915", "PLW0602", "PLW0603", "PLW2901", "PYI036", "PLR0917", - "BLE001", "TRY004", "TRY002", "S110"] # new defaults of 0.16.0 that we don't want to enable yet + "TRY004", "TRY002", "S110"] # new defaults of 0.16.0 that we don't want to enable yet # we want to explicitly use the micro symbol # not the greek letter @@ -272,7 +272,9 @@ known-first-party = ["qcodes"] [tool.ruff.lint.per-file-ignores] # TID253 these imports are fine at module level # in tests and examples -"docs/*" = ["TID253"] +# BLE001 the example notebooks deliberately catch broad exceptions +# to demonstrate that an operation is rejected by QCoDeS +"docs/*" = ["TID253", "BLE001"] "tests/*" = ["TID253"] [tool.ruff.lint.flake8-tidy-imports] diff --git a/src/qcodes/dataset/data_set.py b/src/qcodes/dataset/data_set.py index f36ad008772f..9364b2f85fb1 100644 --- a/src/qcodes/dataset/data_set.py +++ b/src/qcodes/dataset/data_set.py @@ -1452,11 +1452,11 @@ def _flush_data_to_database(self, block: bool = False) -> None: else: log.debug("Successfully wrote result to disk") self._results = [] - except Exception as e: + except Exception: if writer_status.write_in_background: - log.warning(f"Could not enqueue result; {e}") + log.exception("Could not enqueue result") else: - log.warning(f"Could not commit to database; {e}") + log.exception("Could not commit to database") else: log.debug("No results to flush") diff --git a/src/qcodes/dataset/database_extract_runs.py b/src/qcodes/dataset/database_extract_runs.py index bfbccc36092c..0c25bf58af86 100644 --- a/src/qcodes/dataset/database_extract_runs.py +++ b/src/qcodes/dataset/database_extract_runs.py @@ -33,7 +33,7 @@ if TYPE_CHECKING: from qcodes.dataset.data_set_protocol import DataSetProtocol -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) _TRACER = trace.get_tracer(__name__) @@ -219,9 +219,9 @@ def export_datasets_and_create_metadata_db( with closing(connect(source_db_path)) as source_con: run_ids = sorted(get_runs(source_con)) - _LOG.debug(f"Found {len(run_ids)} datasets to process") + _LOGGER.debug(f"Found {len(run_ids)} datasets to process") if not run_ids: - _LOG.warning( + _LOGGER.warning( f"No datasets found in source database {source_db_path}, nothing to export" ) return {} @@ -246,7 +246,7 @@ def export_datasets_and_create_metadata_db( except Exception as e: raise RuntimeError(f"Failed to create export directory {export_path}") from e - _LOG.info( + _LOGGER.info( f"Starting NetCDF export process from {source_db_path} to {export_path}, " f"and creating metadata-only database file {target_db_path}." ) @@ -279,7 +279,7 @@ def export_datasets_and_create_metadata_db( ) processed_experiments[exp_id] = target_exp_id - _LOG.info( + _LOGGER.info( f"Created experiment `{exp_attrs['name']}` on `{exp_attrs['sample_name']}` with ID {target_exp_id} in target database" ) else: @@ -292,10 +292,10 @@ def export_datasets_and_create_metadata_db( result_status[run_id] = status except Exception: - _LOG.exception(f"Failed to process dataset {run_id}") + _LOGGER.exception(f"Failed to process dataset {run_id}") result_status[run_id] = "failed" - _LOG.info("Exporting complete.") + _LOGGER.info("Exporting complete.") return result_status @@ -331,16 +331,16 @@ def _process_single_dataset( existing_path = Path(existing_netcdf_path) # Check if the existing export path matches the desired export path if existing_path.exists() and existing_path.parent == export_path: - _LOG.debug( + _LOGGER.debug( f"Dataset {run_id} already exported to NetCDF at {existing_netcdf_path}" ) netcdf_export_path = existing_netcdf_path else: - _LOG.info( + _LOGGER.info( f"Dataset {run_id} was exported to different location, re-exporting to {export_path}" ) else: - _LOG.debug(f"Attempting to export dataset {run_id} to NetCDF") + _LOGGER.debug(f"Attempting to export dataset {run_id} to NetCDF") if netcdf_export_path is None: try: @@ -352,19 +352,19 @@ def _process_single_dataset( "Export appears to have succeeded but no path was recorded." ) except Exception: - _LOG.exception( + _LOGGER.exception( f"Failed to export dataset {run_id} to NetCDF, copying as-is" ) return _copy_dataset_as_is(dataset, source_conn, target_conn, target_exp_id) - _LOG.debug(f"Dataset {run_id} available as NetCDF at {netcdf_export_path}") + _LOGGER.debug(f"Dataset {run_id} available as NetCDF at {netcdf_export_path}") netcdf_dataset = load_from_netcdf( netcdf_export_path, path_to_db=target_conn.path_to_dbfile ) netcdf_dataset.write_metadata_to_db() - _LOG.info( + _LOGGER.info( f"Successfully wrote dataset metadata of {run_id} to {target_conn.path_to_dbfile}" ) @@ -383,8 +383,8 @@ def _copy_dataset_as_is( _extract_single_dataset_into_db( dataset_obj, target_conn_atomic, target_exp_id ) - _LOG.debug(f"Successfully copied dataset {dataset.run_id} as-is") + _LOGGER.debug(f"Successfully copied dataset {dataset.run_id} as-is") return "copied_as_is" except Exception: - _LOG.exception(f"Failed to copy dataset {dataset.run_id} as-is") + _LOGGER.exception(f"Failed to copy dataset {dataset.run_id} as-is") return "failed" diff --git a/src/qcodes/dataset/exporters/export_to_xarray.py b/src/qcodes/dataset/exporters/export_to_xarray.py index 1734831dd820..ad2bbbeb0c87 100644 --- a/src/qcodes/dataset/exporters/export_to_xarray.py +++ b/src/qcodes/dataset/exporters/export_to_xarray.py @@ -27,7 +27,7 @@ from qcodes.dataset.data_set_protocol import DataSetProtocol, ParameterData -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) def _calculate_index_shape(idx: pd.Index | pd.MultiIndex) -> dict[Hashable, int]: @@ -108,7 +108,7 @@ def _add_inferred_data_vars( if flat.shape[0] == expected_size: xr_dataset[inf.name] = (dims, flat.reshape(expected_shape)) else: - _LOG.warning( + _LOGGER.warning( "Cannot add inferred parameter '%s' to xarray dataset for '%s' " "(run_id=%s): data size %d does not match the dataset " "dimensions %s (size %d). This is likely a user error in the " @@ -145,10 +145,10 @@ def _load_to_xarray_dataset_dict_no_metadata( ) if shape_is_consistent and use_multi_index != "always": - _LOG.info("Exporting %s to xarray using direct method", name) + _LOGGER.info("Exporting %s to xarray using direct method", name) xr_dataset_dict[name] = _xarray_data_set_direct(dataset, name, sub_dict) else: - _LOG.info("Exporting %s to xarray via pandas index", name) + _LOGGER.info("Exporting %s to xarray via pandas index", name) index = _generate_pandas_index( sub_dict, dataset.description.interdeps, top_level_param_name=name ) @@ -217,7 +217,7 @@ def _xarray_data_set_from_pandas_multi_index( if export_with_multi_index: assert isinstance(df.index, pd.MultiIndex) - _LOG.info( + _LOGGER.info( "Exporting %s to xarray using a MultiIndex since on_grid=%s, shape=%s, use_multi_index=%s", name, on_grid, @@ -476,7 +476,7 @@ def xarray_to_h5netcdf_with_complex_numbers( from tqdm.dask import TqdmCallback with TqdmCallback(desc="Combining files"): - _LOG.info( + _LOGGER.info( "Writing netcdf file using Dask delayed writer.", extra={"file_name": str(file_path)}, ) diff --git a/src/qcodes/dataset/threading.py b/src/qcodes/dataset/threading.py index 606018a76dd1..56170d272d95 100644 --- a/src/qcodes/dataset/threading.py +++ b/src/qcodes/dataset/threading.py @@ -24,7 +24,7 @@ type ParamMeasT = "ParameterBase | Callable[[], None]" type OutType = "list[tuple[ParameterBase, ValuesType]]" -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) class _ParamCaller: diff --git a/src/qcodes/extensions/_refactor.py b/src/qcodes/extensions/_refactor.py index 9b40054130c6..a2497608b984 100644 --- a/src/qcodes/extensions/_refactor.py +++ b/src/qcodes/extensions/_refactor.py @@ -17,7 +17,7 @@ "qcodes-refactor requires that QCoDeS is installed with refactor extra dependencies." ) from er -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) @dataclass @@ -90,7 +90,7 @@ def visit_Arg(self, node: cst.Arg) -> None: # arg is parameter class self.annotations.parameter_class = e_value case cst.Arg(): - _LOG.info("Unexpected node %s", str(node)) + _LOGGER.info("Unexpected node %s", str(node)) def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.Call: call_name = _get_call_name(updated_node) @@ -169,7 +169,7 @@ def transform_files_in_folder(folder_path: Path) -> None: with open(file_name, "w", encoding="utf-8") as file: print(f"Modified {file_name}") file.write(modified_tree.code) - except Exception as ex: + except Exception as ex: # noqa: BLE001 a single unparsable file should not abort the refactor print(f"could not parse {filename}. Got {ex}") diff --git a/src/qcodes/extensions/installation.py b/src/qcodes/extensions/installation.py index d612ea3fbde7..f0aeee3cc3ee 100644 --- a/src/qcodes/extensions/installation.py +++ b/src/qcodes/extensions/installation.py @@ -8,7 +8,7 @@ from qcodes.station import SCHEMA_PATH, STATION_YAML_EXT, update_config_schema -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) def register_station_schema_with_vscode() -> None: @@ -53,7 +53,7 @@ def register_station_schema_with_vscode() -> None: try: data = json.load(f) except json.JSONDecodeError: - _LOG.warning( + _LOGGER.warning( "Could not parse VSCode settings file. Check that the file is valid JSON e.g. does not contain comments." ) return diff --git a/src/qcodes/instrument/instrument.py b/src/qcodes/instrument/instrument.py index 1a43075ae465..6dbcc74b705b 100644 --- a/src/qcodes/instrument/instrument.py +++ b/src/qcodes/instrument/instrument.py @@ -152,7 +152,7 @@ def __del__(self) -> None: """Close the instrument and remove its instance record.""" try: self.close() - except BaseException: + except BaseException: # noqa: BLE001 __del__ must never raise pass def close(self) -> None: diff --git a/src/qcodes/instrument_drivers/Keysight/Infiniium.py b/src/qcodes/instrument_drivers/Keysight/Infiniium.py index 8fdb6ee41adb..b451c2899f3e 100644 --- a/src/qcodes/instrument_drivers/Keysight/Infiniium.py +++ b/src/qcodes/instrument_drivers/Keysight/Infiniium.py @@ -1284,6 +1284,6 @@ def screenshot( f.write(screen_bytes) # type: ignore[arg-type] print(f"Screen image written to {img_path}") return np.asarray(pil_open(BytesIO(screen_bytes))) # type: ignore[arg-type] - except Exception as e: - self.log.error(f"Failed to save screenshot, Error occurred: \n{e}") + except Exception: + self.log.exception("Failed to save screenshot") return None diff --git a/src/qcodes/instrument_drivers/oxford/triton.py b/src/qcodes/instrument_drivers/oxford/triton.py index 05d9fd11e2e0..2a5cac3e2af5 100644 --- a/src/qcodes/instrument_drivers/oxford/triton.py +++ b/src/qcodes/instrument_drivers/oxford/triton.py @@ -332,7 +332,7 @@ def _get_response_value(self, msg: str) -> float | str | list[float] | None: ] try: return float(re.findall(r"[-+]?\d*\.\d+|\d+", msg)[0]) - except Exception: + except (ValueError, IndexError): return msg def get_idn(self) -> dict[str, str | None]: diff --git a/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py b/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py index 7cdf506eeb61..7636eec2ebaf 100644 --- a/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py +++ b/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py @@ -640,11 +640,11 @@ def close(self) -> None: try: self.abort() log.info("Running acquistion aborted.") - except Exception as e: + except Exception: # it's ok to catch any exception here # as we are tearing down the instrument we might # as well try to continue - log.warning(f"Could not abort acquisition: {e}") + log.exception("Could not abort acquisition") err = self.dll.saCloseDevice(self.deviceHandle) self.check_for_error(err, "saCloseDevice") diff --git a/src/qcodes/instrument_drivers/tektronix/AWG5014.py b/src/qcodes/instrument_drivers/tektronix/AWG5014.py index c0084abea479..e4ea72ea184b 100644 --- a/src/qcodes/instrument_drivers/tektronix/AWG5014.py +++ b/src/qcodes/instrument_drivers/tektronix/AWG5014.py @@ -1865,9 +1865,8 @@ def is_awg_ready(self) -> bool: try: self.ask("*OPC?") # makes the awg read again if there is a timeout - except Exception as e: - log.warning(e) - log.warning("AWG is not ready") + except Exception: + log.exception("AWG is not ready") self.visa_handle.read() return True diff --git a/src/qcodes/instrument_drivers/tektronix/AWG70000A.py b/src/qcodes/instrument_drivers/tektronix/AWG70000A.py index 15c5e4972c0b..dd5d489d2ac2 100644 --- a/src/qcodes/instrument_drivers/tektronix/AWG70000A.py +++ b/src/qcodes/instrument_drivers/tektronix/AWG70000A.py @@ -1087,7 +1087,7 @@ def make_SEQX_from_forged_sequence( try: fs_schema.validate(seq) except Exception as e: - raise InvalidForgedSequenceError(e) + raise InvalidForgedSequenceError(e) from e chan_list: list[str | int] = [] for pos1 in seq: diff --git a/src/qcodes/interactive_widget.py b/src/qcodes/interactive_widget.py index 4138e7e12470..568266c0da04 100644 --- a/src/qcodes/interactive_widget.py +++ b/src/qcodes/interactive_widget.py @@ -263,7 +263,7 @@ def _on_click(_: Button) -> None: display(nested_dict_browser(snapshot)) else: print("This dataset has no snapshot") - except Exception: + except Exception: # noqa: BLE001 the widget callback must never raise traceback.print_exc() tab.selected_index = i diff --git a/src/qcodes/parameters/combined_parameter.py b/src/qcodes/parameters/combined_parameter.py index 4a6adab034b0..d07076f0a352 100644 --- a/src/qcodes/parameters/combined_parameter.py +++ b/src/qcodes/parameters/combined_parameter.py @@ -18,7 +18,7 @@ from .parameter import Parameter -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) def combine( @@ -97,7 +97,7 @@ def __init__( self.parameter.label = label # type: ignore[attr-defined] if units is not None: - _LOG.warning( + _LOGGER.warning( f"`units` is deprecated for the " f"`CombinedParameter` class, use `unit` instead. {self!r}" ) diff --git a/src/qcodes/parameters/multi_channel_instrument_parameter.py b/src/qcodes/parameters/multi_channel_instrument_parameter.py index 074c335935cf..a173352ebdce 100644 --- a/src/qcodes/parameters/multi_channel_instrument_parameter.py +++ b/src/qcodes/parameters/multi_channel_instrument_parameter.py @@ -12,7 +12,7 @@ from .parameter_base import ParamRawDataType -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) class MultiChannelInstrumentParameter[InstrumentModuleType: "InstrumentModule"]( @@ -61,7 +61,7 @@ def set_raw(self, value: ParamRawDataType | Sequence[ParamRawDataType]) -> None: try: for chan in self._channels: getattr(chan, self._param_name).set(value) - except Exception as err: + except Exception as err: # noqa: BLE001 re-raised below unless the fallback below succeeds try: # Catch wrong length of value before any setting is done value_list = list(value) diff --git a/src/qcodes/plotting/matplotlib_helpers.py b/src/qcodes/plotting/matplotlib_helpers.py index c86ade62ad69..c77d9fbe449e 100644 --- a/src/qcodes/plotting/matplotlib_helpers.py +++ b/src/qcodes/plotting/matplotlib_helpers.py @@ -16,7 +16,7 @@ DEFAULT_COLOR_OVER = "Magenta" DEFAULT_COLOR_UNDER = "Cyan" -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) _EXTEND_TYPE = Literal["neither", "both", "min", "max"] @@ -215,7 +215,7 @@ def auto_color_scale_from_config( """ if colorbar is None: - _LOG.warning( + _LOGGER.warning( '"auto_color_scale_from_config" did not receive a colorbar ' "for scaling. Are you trying to scale a plot without " "colorbar?" diff --git a/src/qcodes/sphinx_extensions/parse_parameter_attr.py b/src/qcodes/sphinx_extensions/parse_parameter_attr.py index 06d334ddf49b..7a9a8ee7ae15 100644 --- a/src/qcodes/sphinx_extensions/parse_parameter_attr.py +++ b/src/qcodes/sphinx_extensions/parse_parameter_attr.py @@ -118,7 +118,7 @@ def eval_params_from_code(code: str, classname: str) -> dict[str, ParameterProxy try: name_code = extract_code_as_repr(stm) except Exception: - LOGGER.warning(f"Error while trying to parse attribute from {classname}") + LOGGER.exception(f"Error while trying to parse attribute from {classname}") continue if name_code is not None: name, proxy_param = name_code diff --git a/src/qcodes/station.py b/src/qcodes/station.py index 7401abfc5082..43a319bf0b26 100644 --- a/src/qcodes/station.py +++ b/src/qcodes/station.py @@ -266,7 +266,7 @@ def add_component( try: if not (isinstance(component, Parameter) and component.snapshot_exclude): component.snapshot(update="All" if update_snapshot else "Never") - except Exception: + except Exception: # noqa: BLE001 a failing snapshot must not prevent adding the component pass if name is None: name = getattr(component, "name", f"component{len(self.components)}") @@ -829,7 +829,7 @@ def instrument_names_from_module(module: ModuleType) -> tuple[str, ...]: ms = inspect.getmembers( importlib.import_module(s.name), inspect.isclass ) - except Exception: + except Exception: # noqa: BLE001 an uninspectable submodule should not abort the scan ms = [] new_members = [ f"{instr[1].__module__}.{instr[1].__name__}" diff --git a/src/qcodes/utils/attribute_helpers.py b/src/qcodes/utils/attribute_helpers.py index 04d181b0f224..010a75727546 100644 --- a/src/qcodes/utils/attribute_helpers.py +++ b/src/qcodes/utils/attribute_helpers.py @@ -119,9 +119,9 @@ def strip_attrs(obj: object, whitelist: "Sequence[str]" = ()) -> None: for key in lst: try: del obj.__dict__[key] - except Exception: + except Exception: # noqa: BLE001 best effort cleanup, never raise pass - except Exception: + except Exception: # noqa: BLE001 best effort cleanup, never raise pass diff --git a/src/qcodes/utils/installation_info.py b/src/qcodes/utils/installation_info.py index 2683f7b37b25..14c894988140 100644 --- a/src/qcodes/utils/installation_info.py +++ b/src/qcodes/utils/installation_info.py @@ -29,8 +29,8 @@ def is_qcodes_installed_editably() -> bool | None: ) e_pkgs = json.loads(pipproc.stdout.decode("utf-8")) answer = any(d["name"] == "qcodes" for d in e_pkgs) - except Exception as e: # we actually do want a catch-all here - log.warning(f"{type(e)}: {e!s}") + except Exception: # we actually do want a catch-all here + log.exception("Could not determine if QCoDeS is installed editably") answer = None return answer diff --git a/src/qcodes/utils/threading_utils.py b/src/qcodes/utils/threading_utils.py index ff207d2177d7..dd86e3b17357 100644 --- a/src/qcodes/utils/threading_utils.py +++ b/src/qcodes/utils/threading_utils.py @@ -5,7 +5,7 @@ if TYPE_CHECKING: from collections.abc import Callable, Sequence -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) class RespondingThread[T](threading.Thread): @@ -45,10 +45,10 @@ def __init__( self._output: T | None = None def run(self) -> None: - _LOG.debug(f"Executing {self._target} on thread: {threading.get_ident()}") + _LOGGER.debug(f"Executing {self._target} on thread: {threading.get_ident()}") try: self._output = self._target(*self._args, **self._kwargs) - except Exception as e: + except Exception as e: # noqa: BLE001 re-raised in the calling thread self._exception = e def output(self, timeout: float | None = None) -> T | None: diff --git a/src/qcodes/validators/validators.py b/src/qcodes/validators/validators.py index bcda83550e19..fa7a8689f1f1 100644 --- a/src/qcodes/validators/validators.py +++ b/src/qcodes/validators/validators.py @@ -758,7 +758,7 @@ def validate(self, value: Any, context: str = "") -> None: v.validate(value, context) if self._combiner == "OR": return - except Exception as e: + except Exception as e: # noqa: BLE001 validators may raise any exception type # collect the args from all validators so you can see why # each one that was tested failed args = args + list(e.args) diff --git a/tests/parameter/test_parameter_on_set_callback.py b/tests/parameter/test_parameter_on_set_callback.py index 951b8973a148..125e3fe8e164 100644 --- a/tests/parameter/test_parameter_on_set_callback.py +++ b/tests/parameter/test_parameter_on_set_callback.py @@ -70,7 +70,7 @@ def cleanup_db_connections(): for conn in open_connections: try: conn.close() - except Exception: + except Exception: # noqa: BLE001 best effort cleanup, never fail the test pass gc.collect() diff --git a/tests/test_logger.py b/tests/test_logger.py index 7bda84246591..f8b7e31dcd7a 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -26,7 +26,7 @@ NUM_PYTEST_LOGGERS = 4 -_LOG = logging.getLogger(__name__) +_LOGGER = logging.getLogger(__name__) @pytest.fixture(autouse=True) @@ -159,7 +159,7 @@ def test_set_level_without_starting_raises() -> None: def test_handler_level() -> None: logger.start_logger() with logger.LogCapture(level=logging.INFO) as logs: - _LOG.debug(TEST_LOG_MESSAGE) + _LOGGER.debug(TEST_LOG_MESSAGE) assert logs.value == "" with ( @@ -167,7 +167,7 @@ def test_handler_level() -> None: logger.handler_level(level=logging.DEBUG, handler=logs.string_handler), ): print(logs.string_handler) - _LOG.debug(TEST_LOG_MESSAGE) + _LOGGER.debug(TEST_LOG_MESSAGE) assert logs.value.strip() == TEST_LOG_MESSAGE From 525734b02920f838aa23c50042f2bf9cd26a2e67 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 3 Aug 2026 09:59:30 +0200 Subject: [PATCH 4/7] style: Enable ruff S110 (try-except-pass) `Station.add_component` now logs the traceback when snapshotting a component fails rather than silently swallowing it. The test cleanup fixture uses `contextlib.suppress`. The remaining silent handlers are in teardown paths (`Instrument.__del__` and `strip_attrs`) that must never raise and must not log since the logging machinery may already be torn down, so they are explicitly marked as intentional. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- docs/changes/newsfragments/8356.underthehood.2 | 4 ++++ pyproject.toml | 2 +- src/qcodes/instrument/instrument.py | 5 ++++- src/qcodes/station.py | 4 ++-- src/qcodes/utils/attribute_helpers.py | 6 ++++-- tests/parameter/test_parameter_on_set_callback.py | 6 ++---- 6 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 docs/changes/newsfragments/8356.underthehood.2 diff --git a/docs/changes/newsfragments/8356.underthehood.2 b/docs/changes/newsfragments/8356.underthehood.2 new file mode 100644 index 000000000000..7238774481fb --- /dev/null +++ b/docs/changes/newsfragments/8356.underthehood.2 @@ -0,0 +1,4 @@ +Enable the ruff rule ``S110`` (``try-except-pass``). ``Station.add_component`` no longer silently +swallows an error raised while snapshotting a component but logs it including the traceback. The +remaining silent handlers are in teardown paths that must never raise or log and are explicitly +marked as intentional. diff --git a/pyproject.toml b/pyproject.toml index 0d708b4e07fb..e750437e733c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,7 +258,7 @@ extend-select = [ # it may be worth fixing some or these in the future # PYI036 disable until https://github.com/astral-sh/ruff/issues/9794 is fixed ignore = ["E501", "G004", "PLR2004", "PLR0913", "PLR0911", "PLR0912", "PLR0915", "PLW0602", "PLW0603", "PLW2901", "PYI036", "PLR0917", - "TRY004", "TRY002", "S110"] # new defaults of 0.16.0 that we don't want to enable yet + "TRY004", "TRY002"] # new defaults of 0.16.0 that we don't want to enable yet # we want to explicitly use the micro symbol # not the greek letter diff --git a/src/qcodes/instrument/instrument.py b/src/qcodes/instrument/instrument.py index 6dbcc74b705b..846119ceb38e 100644 --- a/src/qcodes/instrument/instrument.py +++ b/src/qcodes/instrument/instrument.py @@ -150,9 +150,12 @@ def __repr__(self) -> str: def __del__(self) -> None: """Close the instrument and remove its instance record.""" + # `__del__` must never raise and may run during interpreter shutdown + # where other modules are already torn down, so this deliberately + # swallows everything without logging. try: self.close() - except BaseException: # noqa: BLE001 __del__ must never raise + except BaseException: # noqa: BLE001, S110 pass def close(self) -> None: diff --git a/src/qcodes/station.py b/src/qcodes/station.py index 43a319bf0b26..74d9e128c40d 100644 --- a/src/qcodes/station.py +++ b/src/qcodes/station.py @@ -266,8 +266,8 @@ def add_component( try: if not (isinstance(component, Parameter) and component.snapshot_exclude): component.snapshot(update="All" if update_snapshot else "Never") - except Exception: # noqa: BLE001 a failing snapshot must not prevent adding the component - pass + except Exception: + log.exception("Failed to snapshot component while adding it to the station") if name is None: name = getattr(component, "name", f"component{len(self.components)}") namestr = str(name) diff --git a/src/qcodes/utils/attribute_helpers.py b/src/qcodes/utils/attribute_helpers.py index 010a75727546..742899511a54 100644 --- a/src/qcodes/utils/attribute_helpers.py +++ b/src/qcodes/utils/attribute_helpers.py @@ -114,14 +114,16 @@ def strip_attrs(obj: object, whitelist: "Sequence[str]" = ()) -> None: whitelist: List of names that are not stripped from the object. """ + # this is called during teardown/deletion so it must never raise and + # must not log: the logging machinery may already be torn down. try: lst = set(obj.__dict__.keys()) - set(whitelist) for key in lst: try: del obj.__dict__[key] - except Exception: # noqa: BLE001 best effort cleanup, never raise + except Exception: # noqa: BLE001, S110 pass - except Exception: # noqa: BLE001 best effort cleanup, never raise + except Exception: # noqa: BLE001, S110 pass diff --git a/tests/parameter/test_parameter_on_set_callback.py b/tests/parameter/test_parameter_on_set_callback.py index 125e3fe8e164..3daa2379730e 100644 --- a/tests/parameter/test_parameter_on_set_callback.py +++ b/tests/parameter/test_parameter_on_set_callback.py @@ -3,7 +3,7 @@ import threading import time from collections import Counter -from contextlib import nullcontext +from contextlib import nullcontext, suppress from typing import TYPE_CHECKING, Any import pytest @@ -68,10 +68,8 @@ def cleanup_db_connections(): ] for conn in open_connections: - try: + with suppress(Exception): conn.close() - except Exception: # noqa: BLE001 best effort cleanup, never fail the test - pass gc.collect() From 2eb8ddb22ce9b4d1c1261ba2bbf77d66c0aa7846 Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 3 Aug 2026 10:45:55 +0200 Subject: [PATCH 5/7] style: Enable ruff TRY002 (raise-vanilla-class) Replace the bare `Exception` raised in a number of drivers with a specific builtin exception: `ValueError` for invalid arguments, `RuntimeError` for using the instrument in an unsupported state and `NotImplementedError` for the unimplemented AlazarTech ATS acquisition modes. All of these are subclasses of `Exception` so existing code that catches `Exception` is unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- docs/changes/newsfragments/8356.improved.1 | 5 +++++ .../writing_drivers/Creating-Instrument-Drivers.ipynb | 2 +- pyproject.toml | 2 +- src/qcodes/instrument_drivers/AlazarTech/ATS.py | 6 +++--- src/qcodes/instrument_drivers/AlazarTech/ATS9360.py | 2 +- src/qcodes/instrument_drivers/AlazarTech/ATS9373.py | 2 +- src/qcodes/instrument_drivers/AlazarTech/ATS9870.py | 2 +- .../AlazarTech/ATS_acquisition_controllers.py | 2 +- .../Keysight/keysightb1500/KeysightB1500_base.py | 2 +- .../Keysight/keysightb1500/KeysightB1517A.py | 4 ++-- .../Keysight/keysightb1500/KeysightB1520A.py | 2 +- .../Keysight/keysightb1500/message_builder.py | 2 +- src/qcodes/instrument_drivers/rigol/Rigol_DG4000.py | 2 +- src/qcodes/instrument_drivers/tektronix/AWG5014.py | 8 ++++---- tests/dataset/test_sqlite_connection.py | 2 +- 15 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 docs/changes/newsfragments/8356.improved.1 diff --git a/docs/changes/newsfragments/8356.improved.1 b/docs/changes/newsfragments/8356.improved.1 new file mode 100644 index 000000000000..0dcd3ae6c712 --- /dev/null +++ b/docs/changes/newsfragments/8356.improved.1 @@ -0,0 +1,5 @@ +Enable the ruff rule ``TRY002`` (``raise-vanilla-class``). A number of drivers raised a bare +``Exception``; these now raise a specific builtin exception instead. Invalid arguments raise +``ValueError``, using the instrument in an unsupported state raises ``RuntimeError`` and selecting an +unimplemented acquisition mode on the AlazarTech ATS raises ``NotImplementedError``. Code that +catches ``Exception`` is unaffected since all of these are subclasses of ``Exception``. diff --git a/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb b/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb index 5699f5e638f9..2c5b8432608b 100644 --- a/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb +++ b/docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb @@ -470,7 +470,7 @@ "\n", " self._handle = self._ATS_dll.AlazarGetBoardBySystemID(system_id, board_id)\n", " if not self._handle:\n", - " raise Exception(\n", + " raise RuntimeError(\n", " f\"AlazarTech_ATS not found at system {system_id}, board {board_id}\"\n", " )\n", "\n", diff --git a/pyproject.toml b/pyproject.toml index e750437e733c..83e17ef83486 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,7 +258,7 @@ extend-select = [ # it may be worth fixing some or these in the future # PYI036 disable until https://github.com/astral-sh/ruff/issues/9794 is fixed ignore = ["E501", "G004", "PLR2004", "PLR0913", "PLR0911", "PLR0912", "PLR0915", "PLW0602", "PLW0603", "PLW2901", "PYI036", "PLR0917", - "TRY004", "TRY002"] # new defaults of 0.16.0 that we don't want to enable yet + "TRY004"] # new defaults of 0.16.0 that we don't want to enable yet # we want to explicitly use the micro symbol # not the greek letter diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS.py b/src/qcodes/instrument_drivers/AlazarTech/ATS.py index dd37c8244686..4e4725021f91 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS.py @@ -145,7 +145,7 @@ def __init__( self._handle = self.api.get_board_by_system_id(system_id, board_id) if not self._handle: - raise Exception( + raise RuntimeError( f"AlazarTech_ATS not found at system {system_id}, board {board_id}" ) @@ -380,7 +380,7 @@ def acquire[OutputType]( # noqa: D417 (missing args documentation) # endregion mode = self.mode.get() if mode not in ("TS", "NPT"): - raise Exception( + raise NotImplementedError( "Only the 'TS' and 'NPT' modes are implemented at this point" ) @@ -681,7 +681,7 @@ def get_sample_rate(self, include_decimation: bool = True) -> float: # is set as an integer and not value mapped so we use a different # parameter to represent it elif self.sample_rate.get() == "EXTERNAL_CLOCK": - raise Exception( + raise RuntimeError( "External clock is used, alazar driver " "could not determine sample speed." ) diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS9360.py b/src/qcodes/instrument_drivers/AlazarTech/ATS9360.py index fee20c01d7ed..b318a06c0c0d 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS9360.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS9360.py @@ -426,7 +426,7 @@ def __init__( model = self.get_idn()["model"] if model != "ATS9360": - raise Exception( + raise RuntimeError( f"The Alazar board kind is not 'ATS9360', found '{model!s}' instead." ) diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS9373.py b/src/qcodes/instrument_drivers/AlazarTech/ATS9373.py index a9f06f25b36f..f593115492d2 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS9373.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS9373.py @@ -442,7 +442,7 @@ def __init__( model = self.get_idn()["model"] if model != "ATS9373": - raise Exception( + raise RuntimeError( f"The Alazar board kind is not 'ATS9373', found '{model!s}' instead." ) diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS9870.py b/src/qcodes/instrument_drivers/AlazarTech/ATS9870.py index 8b859042aba2..f1c13168ea23 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS9870.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS9870.py @@ -400,6 +400,6 @@ def __init__( model = self.get_idn()["model"] if model != "ATS9870": - raise Exception( + raise RuntimeError( f"The Alazar board kind is not 'ATS9870', found '{model!s}' instead." ) diff --git a/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py b/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py index a6e6d3ddd10e..4198df03c6c1 100644 --- a/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +++ b/src/qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py @@ -143,7 +143,7 @@ def post_acquire(self) -> float: # (r es1[1] - res2[1]) % 360] return alazar.signal_to_volt(1, res1[0] + 127.5) else: - raise Exception("Could not find CHANNEL_B during data extraction") + raise RuntimeError("Could not find CHANNEL_B during data extraction") def fit(self, buf: npt.NDArray) -> tuple[float, float]: """ diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py index f5c1d6fc3005..eb7f73d9b4a2 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1500_base.py @@ -664,7 +664,7 @@ def get_raw(self) -> tuple[tuple[float, ...], ...]: smu = cast("KeysightB1517A", smu) if not smu.setup_fnc_already_run: - raise Exception( + raise RuntimeError( f"Sweep setup has not yet been run successfully on {smu.full_name}" ) diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py index 48925aeb21da..a01b3baca041 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1517A.py @@ -1084,7 +1084,7 @@ def _get_number_of_samples(self) -> int: sample_number = self._timing_parameters["number"] return sample_number else: - raise Exception("set timing parameters first") + raise RuntimeError("set timing parameters first") def _get_time_axis(self) -> npt.NDArray: sample_rate = self._timing_parameters["interval"] @@ -1097,7 +1097,7 @@ def _total_measurement_time(self) -> float: self._timing_parameters["interval"] is None or self._timing_parameters["number"] is None ): - raise Exception("set timing parameters first") + raise RuntimeError("set timing parameters first") sample_number = self._timing_parameters["number"] sample_rate = self._timing_parameters["interval"] diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py index 8c968ce67032..2eda33050d2d 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/KeysightB1520A.py @@ -1189,7 +1189,7 @@ def root_instrument(self) -> "KeysightB1500": def get_raw(self) -> tuple[tuple[float, ...], tuple[float, ...]]: if not self.instrument.setup_fnc_already_run: - raise Exception("Sweep setup has not yet been run successfully") + raise RuntimeError("Sweep setup has not yet been run successfully") delay_time = self.instrument.cv_sweep.step_delay() diff --git a/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py b/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py index 8162ada68347..387549dab15e 100644 --- a/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py +++ b/src/qcodes/instrument_drivers/Keysight/keysightb1500/message_builder.py @@ -70,7 +70,7 @@ def __init__(self) -> None: def message(self) -> str: joined = str(self._msg) if len(joined) > 250: - raise Exception( + raise ValueError( f"Command is too long ({len(joined)}>256-termchars) " f"and will overflow input buffer of instrument. " f"(Consider using the ST/END/DO/RU commands for very long " diff --git a/src/qcodes/instrument_drivers/rigol/Rigol_DG4000.py b/src/qcodes/instrument_drivers/rigol/Rigol_DG4000.py index dfafbc0c1519..ef67b679638a 100644 --- a/src/qcodes/instrument_drivers/rigol/Rigol_DG4000.py +++ b/src/qcodes/instrument_drivers/rigol/Rigol_DG4000.py @@ -759,7 +759,7 @@ def _upload_data(self, data: "Sequence[float] | npt.NDArray") -> None: self.write("DATA VOLATILE," + string) else: - raise Exception( + raise ValueError( "Data length of " + str(len(data)) + " is not in the range of 1 to 16384" diff --git a/src/qcodes/instrument_drivers/tektronix/AWG5014.py b/src/qcodes/instrument_drivers/tektronix/AWG5014.py index e4ea72ea184b..878d088fb5d5 100644 --- a/src/qcodes/instrument_drivers/tektronix/AWG5014.py +++ b/src/qcodes/instrument_drivers/tektronix/AWG5014.py @@ -1753,7 +1753,7 @@ def _pack_waveform( An array of unsigned 16 bit integers. Raises: - Exception: if the lengths of w, m1, and m2 don't match + ValueError: if the lengths of w, m1, and m2 don't match TypeError: if the waveform contains values outside (-1, 1) TypeError: if the markers contain values that are not 0 or 1 @@ -1761,7 +1761,7 @@ def _pack_waveform( # Input validation if not ((len(wf) == len(m1)) and (len(m1) == len(m2))): - raise Exception("error: sizes of the waveforms do not match") + raise ValueError("error: sizes of the waveforms do not match") if np.min(wf) < -1 or np.max(wf) > 1: raise TypeError( "Waveform values out of bonds. Allowed values: -1 to 1 (inclusive)" @@ -1886,7 +1886,7 @@ def send_waveform_to_list( wfmname: waveform name Raises: - Exception: if the lengths of w, m1, and m2 don't match + ValueError: if the lengths of w, m1, and m2 don't match TypeError: if the waveform contains values outside (-1, 1) TypeError: if the markers contain values that are not 0 or 1 @@ -1897,7 +1897,7 @@ def send_waveform_to_list( # Input validation if not ((len(w) == len(m1)) and (len(m1) == len(m2))): - raise Exception("error: sizes of the waveforms do not match") + raise ValueError("error: sizes of the waveforms do not match") if min(w) < -1 or max(w) > 1: raise TypeError( "Waveform values out of bonds. Allowed values: -1 to 1 (inclusive)" diff --git a/tests/dataset/test_sqlite_connection.py b/tests/dataset/test_sqlite_connection.py index 62eb6e33b4b4..9b09d80ae08c 100644 --- a/tests/dataset/test_sqlite_connection.py +++ b/tests/dataset/test_sqlite_connection.py @@ -99,7 +99,7 @@ def test_atomic_with_exception() -> None: atomic(sqlite_conn) as atomic_conn, ): atomic_conn.execute("PRAGMA user_version(42)") - raise Exception("intended exception") + raise RuntimeError("intended exception") assert error_caused_by(e, "intended exception") assert 25 == sqlite_conn.execute("PRAGMA user_version").fetchall()[0][0] From e78b6c19081ef8456a119cfbab480ab4f5742b7d Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 3 Aug 2026 11:20:37 +0200 Subject: [PATCH 6/7] refactor: Narrow catch-all exception handlers Now that the drivers raise specific exceptions, several of the handlers added when enabling BLE001 can be narrowed to the exceptions that the guarded code actually raises: `VisaIOError` when waiting for the AWG5014, `OSError`/`VisaIOError` when reading an Infiniium screenshot, the `OSError` raised by `check_for_error` when aborting a SignalHound acquisition and the subprocess/json errors when querying pip. The remaining blind handlers guard user supplied validators, arbitrary module imports and teardown paths, so their `noqa` comments now state why. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- docs/changes/newsfragments/8356.improved.2 | 4 ++++ src/qcodes/instrument_drivers/Keysight/Infiniium.py | 2 +- .../signal_hound/SignalHound_USB_SA124B.py | 8 ++++---- src/qcodes/instrument_drivers/tektronix/AWG5014.py | 2 +- src/qcodes/station.py | 2 +- src/qcodes/utils/installation_info.py | 10 ++++++++-- src/qcodes/validators/validators.py | 2 +- 7 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 docs/changes/newsfragments/8356.improved.2 diff --git a/docs/changes/newsfragments/8356.improved.2 b/docs/changes/newsfragments/8356.improved.2 new file mode 100644 index 000000000000..1e021dbab114 --- /dev/null +++ b/docs/changes/newsfragments/8356.improved.2 @@ -0,0 +1,4 @@ +Several catch-all exception handlers have been narrowed to the specific exceptions that the guarded +code can actually raise. Notably reading a screenshot from the Keysight Infiniium, aborting an +acquisition on the SignalHound USB SA124B, waiting for the Tektronix AWG5014 to become ready and +querying pip in :func:`qcodes.utils.is_qcodes_installed_editably` no longer swallow unrelated errors. diff --git a/src/qcodes/instrument_drivers/Keysight/Infiniium.py b/src/qcodes/instrument_drivers/Keysight/Infiniium.py index b451c2899f3e..57c94830e64b 100644 --- a/src/qcodes/instrument_drivers/Keysight/Infiniium.py +++ b/src/qcodes/instrument_drivers/Keysight/Infiniium.py @@ -1284,6 +1284,6 @@ def screenshot( f.write(screen_bytes) # type: ignore[arg-type] print(f"Screen image written to {img_path}") return np.asarray(pil_open(BytesIO(screen_bytes))) # type: ignore[arg-type] - except Exception: + except (OSError, VisaIOError): self.log.exception("Failed to save screenshot") return None diff --git a/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py b/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py index 7636eec2ebaf..40cf8440e8c1 100644 --- a/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py +++ b/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py @@ -640,10 +640,10 @@ def close(self) -> None: try: self.abort() log.info("Running acquistion aborted.") - except Exception: - # it's ok to catch any exception here - # as we are tearing down the instrument we might - # as well try to continue + except (OSError, ValueError): + # `check_for_error` raises OSError if the dll reports a failure and + # `saStatus` raises ValueError for an unknown status code. As we are + # tearing down the instrument we might as well try to continue. log.exception("Could not abort acquisition") err = self.dll.saCloseDevice(self.deviceHandle) diff --git a/src/qcodes/instrument_drivers/tektronix/AWG5014.py b/src/qcodes/instrument_drivers/tektronix/AWG5014.py index 878d088fb5d5..1469fd3d5ac3 100644 --- a/src/qcodes/instrument_drivers/tektronix/AWG5014.py +++ b/src/qcodes/instrument_drivers/tektronix/AWG5014.py @@ -1865,7 +1865,7 @@ def is_awg_ready(self) -> bool: try: self.ask("*OPC?") # makes the awg read again if there is a timeout - except Exception: + except VisaIOError: log.exception("AWG is not ready") self.visa_handle.read() return True diff --git a/src/qcodes/station.py b/src/qcodes/station.py index 74d9e128c40d..1f5258e36d47 100644 --- a/src/qcodes/station.py +++ b/src/qcodes/station.py @@ -829,7 +829,7 @@ def instrument_names_from_module(module: ModuleType) -> tuple[str, ...]: ms = inspect.getmembers( importlib.import_module(s.name), inspect.isclass ) - except Exception: # noqa: BLE001 an uninspectable submodule should not abort the scan + except Exception: # noqa: BLE001 importing an arbitrary third party module may raise anything ms = [] new_members = [ f"{instr[1].__module__}.{instr[1].__name__}" diff --git a/src/qcodes/utils/installation_info.py b/src/qcodes/utils/installation_info.py index 14c894988140..2e20088889c3 100644 --- a/src/qcodes/utils/installation_info.py +++ b/src/qcodes/utils/installation_info.py @@ -28,8 +28,14 @@ def is_qcodes_installed_editably() -> bool | None: stdout=subprocess.PIPE, ) e_pkgs = json.loads(pipproc.stdout.decode("utf-8")) - answer = any(d["name"] == "qcodes" for d in e_pkgs) - except Exception: # we actually do want a catch-all here + answer = any( + isinstance(pkg, dict) and pkg.get("name") == "qcodes" for pkg in e_pkgs + ) + except (OSError, subprocess.SubprocessError, ValueError, TypeError): + # OSError if python/pip cannot be executed, SubprocessError if pip + # returns a non zero exit code, ValueError (JSONDecodeError, + # UnicodeDecodeError) if the output cannot be decoded as json and + # TypeError if the decoded json is not a list of packages log.exception("Could not determine if QCoDeS is installed editably") answer = None diff --git a/src/qcodes/validators/validators.py b/src/qcodes/validators/validators.py index fa7a8689f1f1..22d61ae4fd19 100644 --- a/src/qcodes/validators/validators.py +++ b/src/qcodes/validators/validators.py @@ -758,7 +758,7 @@ def validate(self, value: Any, context: str = "") -> None: v.validate(value, context) if self._combiner == "OR": return - except Exception as e: # noqa: BLE001 validators may raise any exception type + except Exception as e: # noqa: BLE001 Validator is public and user defined subclasses may raise anything # collect the args from all validators so you can see why # each one that was tested failed args = args + list(e.args) From 05696250ae874becbb1222651c88afaffd39f1fa Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Mon, 3 Aug 2026 11:45:18 +0200 Subject: [PATCH 7/7] style: Enable ruff TRY004 (type-check-without-type-error) New code should raise `TypeError` when rejecting a value because of its type. The 46 existing type checks that raise `ValueError` or `RuntimeError` are deliberately left unchanged and marked with `noqa: TRY004`: `TypeError` is not a subclass of either, so changing them would break user code that catches the current exception. `RUF100` will flag the `noqa` as unused if such a call site is ever changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8be348f8-5322-4e8d-b75a-3d00baca556f --- CONTRIBUTING.rst | 6 ++++++ docs/changes/newsfragments/8356.underthehood.3 | 5 +++++ pyproject.toml | 10 ++++++++-- src/qcodes/dataset/descriptions/dependencies.py | 2 +- src/qcodes/dataset/descriptions/param_spec.py | 4 ++-- src/qcodes/dataset/descriptions/rundescriber.py | 2 +- src/qcodes/dataset/descriptions/versioning/v0.py | 2 +- src/qcodes/dataset/experiment_container.py | 2 +- src/qcodes/dataset/measurement_extensions.py | 4 ++-- src/qcodes/dataset/measurements.py | 6 +++--- src/qcodes/dataset/sqlite/connection.py | 2 +- src/qcodes/dataset/sqlite/queries.py | 2 +- src/qcodes/extensions/_refactor.py | 2 +- src/qcodes/instrument/channel.py | 6 +++--- src/qcodes/instrument/delegate/delegate_instrument.py | 2 +- .../instrument_drivers/Keithley/Keithley_2450.py | 4 ++-- .../instrument_drivers/Keithley/_Keithley_2600.py | 2 +- src/qcodes/instrument_drivers/Keysight/N52xx.py | 2 +- .../american_magnetics/AMI430_visa.py | 6 +++--- .../instrument_drivers/mock_instruments/__init__.py | 2 +- .../instrument_drivers/oxford/MercuryiPS_VISA.py | 8 ++++---- .../signal_hound/SignalHound_USB_SA124B.py | 10 +++++----- .../instrument_drivers/stanford_research/SR830.py | 4 ++-- src/qcodes/instrument_drivers/tektronix/AWG70000A.py | 2 +- src/qcodes/logger/logger.py | 4 ++-- src/qcodes/parameters/_paramspec.py | 2 +- src/qcodes/parameters/parameter_with_setpoints.py | 6 +++--- src/qcodes/plotting/matplotlib_helpers.py | 2 +- src/qcodes/station.py | 2 +- 29 files changed, 65 insertions(+), 48 deletions(-) create mode 100644 docs/changes/newsfragments/8356.underthehood.3 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d199f40c7948..9c61634af5cb 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -252,6 +252,12 @@ Coding Style (``invalid-module-name``). A number of existing driver modules predate this rule and are exempted in ``pyproject.toml`` because renaming them would break user code; new modules should not be added to that exemption list. +- Raise a specific exception rather than a bare ``Exception``, and raise + ``TypeError`` when rejecting a value because of its type. These are enforced + by the ruff rules ``TRY002`` and ``TRY004``. Some pre-existing type checks + raise ``ValueError`` or ``RuntimeError`` instead; those carry an explicit + ``# noqa: TRY004`` because ``TypeError`` is not a subclass of either, so + changing them would break user code that catches the current exception. - There is a command-line tool (``pip install pycodestyle``) you can run after writing code to validate its style. - A lot of editors have plugins that will check this for you diff --git a/docs/changes/newsfragments/8356.underthehood.3 b/docs/changes/newsfragments/8356.underthehood.3 new file mode 100644 index 000000000000..2601ae3decc9 --- /dev/null +++ b/docs/changes/newsfragments/8356.underthehood.3 @@ -0,0 +1,5 @@ +Enable the ruff rule ``TRY004`` (``type-check-without-type-error``) so that new code raises +``TypeError`` when rejecting a value because of its type. Existing type checks that raise +``ValueError`` or ``RuntimeError`` are unchanged and carry an explicit ``noqa`` comment, since +``TypeError`` is not a subclass of either and changing them would break user code that catches the +current exception. diff --git a/pyproject.toml b/pyproject.toml index 83e17ef83486..8ce858d03073 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -257,8 +257,14 @@ extend-select = [ # PLxxxx are pylint lints that generate a fair amount of warnings # it may be worth fixing some or these in the future # PYI036 disable until https://github.com/astral-sh/ruff/issues/9794 is fixed -ignore = ["E501", "G004", "PLR2004", "PLR0913", "PLR0911", "PLR0912", "PLR0915", "PLW0602", "PLW0603", "PLW2901", "PYI036", "PLR0917", - "TRY004"] # new defaults of 0.16.0 that we don't want to enable yet +ignore = ["E501", "G004", "PLR2004", "PLR0913", "PLR0911", "PLR0912", "PLR0915", "PLW0602", "PLW0603", "PLW2901", "PYI036", "PLR0917"] + +# TRY004 (type-check-without-type-error) is enabled but a number of pre-existing +# type checks raise ValueError or RuntimeError rather than TypeError. Since +# TypeError is not a subclass of those, changing them would break user code that +# catches the current exception, so those call sites carry an explicit +# `# noqa: TRY004` instead. New code should raise TypeError for an invalid type. +# RUF100 will flag the noqa as unused if such a call site is ever changed. # we want to explicitly use the micro symbol # not the greek letter diff --git a/src/qcodes/dataset/descriptions/dependencies.py b/src/qcodes/dataset/descriptions/dependencies.py index 35be8cb36002..254067252ae9 100644 --- a/src/qcodes/dataset/descriptions/dependencies.py +++ b/src/qcodes/dataset/descriptions/dependencies.py @@ -107,7 +107,7 @@ def add_inferences(self, inferences: ParamSpecTree | None) -> None: def add_standalones(self, standalones: tuple[ParamSpecBase, ...]) -> None: for ps in standalones: if not isinstance(ps, ParamSpecBase): - raise ValueError("Invalid standalones") from TypeError( + raise ValueError("Invalid standalones") from TypeError( # noqa: TRY004 "Standalones must be a sequence of ParamSpecs" ) self.add_paramspecs(list(standalones)) diff --git a/src/qcodes/dataset/descriptions/param_spec.py b/src/qcodes/dataset/descriptions/param_spec.py index 8a965080cca6..0be4fff4b70f 100644 --- a/src/qcodes/dataset/descriptions/param_spec.py +++ b/src/qcodes/dataset/descriptions/param_spec.py @@ -62,7 +62,7 @@ def __init__( depends_on = [] if depends_on is None else depends_on if isinstance(inferred_from, str): - raise ValueError( + raise ValueError( # noqa: TRY004 f"ParamSpec {self.name} got " f"string {inferred_from} as inferred_from. " f"It needs a " @@ -73,7 +73,7 @@ def __init__( ) if isinstance(depends_on, str): - raise ValueError( + raise ValueError( # noqa: TRY004 f"ParamSpec {self.name} got " f"string {depends_on} as depends_on. It needs a " f"Sequence of ParamSpecs or strings" diff --git a/src/qcodes/dataset/descriptions/rundescriber.py b/src/qcodes/dataset/descriptions/rundescriber.py index 697a47ea4419..81a7837208d3 100644 --- a/src/qcodes/dataset/descriptions/rundescriber.py +++ b/src/qcodes/dataset/descriptions/rundescriber.py @@ -36,7 +36,7 @@ def __init__( self, interdeps: InterDependencies_, shapes: Shapes | None = None ) -> None: if not isinstance(interdeps, InterDependencies_): - raise ValueError( + raise ValueError( # noqa: TRY004 "The interdeps arg must be of type: " "InterDependencies_. " f"Got {type(interdeps)}." diff --git a/src/qcodes/dataset/descriptions/versioning/v0.py b/src/qcodes/dataset/descriptions/versioning/v0.py index 84cb1b5453f5..17931757b220 100644 --- a/src/qcodes/dataset/descriptions/versioning/v0.py +++ b/src/qcodes/dataset/descriptions/versioning/v0.py @@ -17,7 +17,7 @@ class InterDependencies: # noqa: PLW1641 def __init__(self, *paramspecs: ParamSpec) -> None: for paramspec in paramspecs: if not isinstance(paramspec, ParamSpec): - raise ValueError( + raise ValueError( # noqa: TRY004 "Got invalid input. All paramspecs must be " f"ParamSpecs, but {paramspec} is of type " f"{type(paramspec)}." diff --git a/src/qcodes/dataset/experiment_container.py b/src/qcodes/dataset/experiment_container.py index d7b52e8c9997..6c1b716ab538 100644 --- a/src/qcodes/dataset/experiment_container.py +++ b/src/qcodes/dataset/experiment_container.py @@ -276,7 +276,7 @@ def load_experiment(exp_id: int, conn: AtomicConnection | None = None) -> Experi """ conn = conn_from_dbpath_or_conn(conn=conn, path_to_db=None) if not isinstance(exp_id, int): - raise ValueError("Experiment ID must be an integer") + raise ValueError("Experiment ID must be an integer") # noqa: TRY004 experiment = Experiment(exp_id=exp_id, conn=conn) _set_default_experiment_id(path_to_dbfile(conn), experiment.exp_id) return experiment diff --git a/src/qcodes/dataset/measurement_extensions.py b/src/qcodes/dataset/measurement_extensions.py index 24c40b0ecd99..c11e2fc3d903 100644 --- a/src/qcodes/dataset/measurement_extensions.py +++ b/src/qcodes/dataset/measurement_extensions.py @@ -148,9 +148,9 @@ def parse_dond_into_args( if isinstance(par, AbstractSweep): sweep_instances.append(par) elif isinstance(par, TogetherSweep): - raise ValueError("dond_into does not support TogetherSweeps") + raise ValueError("dond_into does not support TogetherSweeps") # noqa: TRY004 elif isinstance(par, Sequence): - raise ValueError("dond_into does not support multiple datasets") + raise ValueError("dond_into does not support multiple datasets") # noqa: TRY004 elif (isinstance(par, ParameterBase) and par.gettable) or callable(par): params_meas.append(par) return sweep_instances, params_meas diff --git a/src/qcodes/dataset/measurements.py b/src/qcodes/dataset/measurements.py index 6e024c787fb9..27203d739ae0 100644 --- a/src/qcodes/dataset/measurements.py +++ b/src/qcodes/dataset/measurements.py @@ -854,7 +854,7 @@ def write_period(self) -> float: @write_period.setter def write_period(self, wp: float) -> None: if not isinstance(wp, Number): - raise ValueError("The write period must be a number (of seconds).") + raise ValueError("The write period must be a number (of seconds).") # noqa: TRY004 wp_float = float(wp) if wp_float < 1e-3: raise ValueError("The write period must be at least 1 ms.") @@ -1267,7 +1267,7 @@ def _register_parameter_with_setpoints( my_setpoints = list(setpoints) if setpoints else [] for sp in parameter.setpoints: if not isinstance(sp, Parameter): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "The setpoints of a ParameterWithSetpoints must be a Parameter" ) spname = sp.register_name @@ -1400,7 +1400,7 @@ def unregister_parameter(self, parameter: SetpointsType) -> None: elif isinstance(parameter, str): param_name = parameter else: - raise ValueError( + raise ValueError( # noqa: TRY004 "Wrong input type. Must be a QCoDeS parameter or" " the name (a string) of a parameter." ) diff --git a/src/qcodes/dataset/sqlite/connection.py b/src/qcodes/dataset/sqlite/connection.py index b450c3c8194e..f829df181a8d 100644 --- a/src/qcodes/dataset/sqlite/connection.py +++ b/src/qcodes/dataset/sqlite/connection.py @@ -63,7 +63,7 @@ def atomic(conn: AtomicConnection) -> Generator[AtomicConnection, None, None]: """ with DelayedKeyboardInterrupt(context={"reason": "sqlite atomic operation"}): if not isinstance(conn, AtomicConnection): - raise ValueError( + raise ValueError( # noqa: TRY004 "atomic context manager only accepts " "AtomicConnection database connection objects." ) diff --git a/src/qcodes/dataset/sqlite/queries.py b/src/qcodes/dataset/sqlite/queries.py index 31292df97ebf..c42eeb676f17 100644 --- a/src/qcodes/dataset/sqlite/queries.py +++ b/src/qcodes/dataset/sqlite/queries.py @@ -731,7 +731,7 @@ def _get_layout_id( elif isinstance(parameter, str): name = parameter else: - raise ValueError( + raise ValueError( # noqa: TRY004 "Wrong parameter type, must be ParamSpec or str, " f"received {type(parameter)}." ) diff --git a/src/qcodes/extensions/_refactor.py b/src/qcodes/extensions/_refactor.py index a2497608b984..e6b6711cbc73 100644 --- a/src/qcodes/extensions/_refactor.py +++ b/src/qcodes/extensions/_refactor.py @@ -144,7 +144,7 @@ def _create_updated_node( if isinstance(stm.body, cst.BaseSuite) or isinstance( comment.body, cst.BaseSuite ): - raise RuntimeError("Unexpected result from parsing code.") + raise RuntimeError("Unexpected result from parsing code.") # noqa: TRY004 new_node = stm.body[0] new_node = new_node.with_changes(value=call_node) diff --git a/src/qcodes/instrument/channel.py b/src/qcodes/instrument/channel.py index 1dbc25deabca..f7f7503bfd51 100644 --- a/src/qcodes/instrument/channel.py +++ b/src/qcodes/instrument/channel.py @@ -162,13 +162,13 @@ def __init__( if not isinstance(chan_type, type) or not issubclass( chan_type, InstrumentModule ): - raise ValueError( + raise ValueError( # noqa: TRY004 "ChannelTuple can only hold instances of type InstrumentModule" ) if not isinstance(multichan_paramclass, type) or not issubclass( multichan_paramclass, MultiChannelInstrumentParameter ): - raise ValueError( + raise ValueError( # noqa: TRY004 "multichan_paramclass must be a (subclass of) " "MultiChannelInstrumentParameter" ) @@ -896,7 +896,7 @@ class ChannelTupleValidator(Validator[InstrumentChannel]): def __init__(self, channel_list: ChannelTuple) -> None: # Save the base parameter list if not isinstance(channel_list, ChannelTuple): - raise ValueError( + raise ValueError( # noqa: TRY004 "channel_list must be a ChannelTuple " "object containing the " "channels that should be validated" diff --git a/src/qcodes/instrument/delegate/delegate_instrument.py b/src/qcodes/instrument/delegate/delegate_instrument.py index 27a3c9f308fb..32ac1d673efc 100644 --- a/src/qcodes/instrument/delegate/delegate_instrument.py +++ b/src/qcodes/instrument/delegate/delegate_instrument.py @@ -204,7 +204,7 @@ def _create_and_add_parameters( elif isinstance(paths, abc.Sequence): path_list = paths else: - raise ValueError( + raise ValueError( # noqa: TRY004 "Parameter paths should be either a string or Sequence of \ strings." ) diff --git a/src/qcodes/instrument_drivers/Keithley/Keithley_2450.py b/src/qcodes/instrument_drivers/Keithley/Keithley_2450.py index 9167ce3c13b1..792739d9ff56 100644 --- a/src/qcodes/instrument_drivers/Keithley/Keithley_2450.py +++ b/src/qcodes/instrument_drivers/Keithley/Keithley_2450.py @@ -703,7 +703,7 @@ def _set_sense_function(self, value: str) -> None: sense_function = self.sense_function.inverse_val_mapping[value] sense = self.submodules[f"_sense_{sense_function}"] if not isinstance(sense, Keithley2450Sense): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expect Sense Module to be of type Keithley2450Sense got {type(sense)}" ) sense.sweep.setpoints = (self.source.sweep_axis,) @@ -736,7 +736,7 @@ def _set_source_function(self, value: str) -> None: ) self.sense.sweep.setpoints = (source.sweep_axis,) if not isinstance(source, Keithley2450Source): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expect Source Module to be of type " f"Keithley2450Source got {type(source)}" ) diff --git a/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py b/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py index ff0411dc55aa..a6fb362e9fae 100644 --- a/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py +++ b/src/qcodes/instrument_drivers/Keithley/_Keithley_2600.py @@ -1094,7 +1094,7 @@ def setup_fastsweep( inner_param = cast("Parameter", inner.param) inner_channel = infer_channel(inner_param) if not isinstance(inner_channel, Keithley2600Channel): - raise ValueError( + raise ValueError( # noqa: TRY004 "Inner sweep parameter must belong to a Keithley2600Channel." ) inner_channel_name = inner_channel.channel diff --git a/src/qcodes/instrument_drivers/Keysight/N52xx.py b/src/qcodes/instrument_drivers/Keysight/N52xx.py index 86f5e46a099d..655e39edda4b 100644 --- a/src/qcodes/instrument_drivers/Keysight/N52xx.py +++ b/src/qcodes/instrument_drivers/Keysight/N52xx.py @@ -686,7 +686,7 @@ def __init__( trace1 = self.traces[0] params = trace1.parameters if not isinstance(params, dict): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expected trace.parameters to be a dict got {type(params)}" ) for param in params.values(): diff --git a/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py b/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py index 5dd985b72db4..804f05b23cae 100644 --- a/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py +++ b/src/qcodes/instrument_drivers/american_magnetics/AMI430_visa.py @@ -670,14 +670,14 @@ def __init__( super().__init__(name, **kwargs) if not isinstance(name, str): - raise ValueError("Name should be a string") + raise ValueError("Name should be a string") # noqa: TRY004 for instrument, arg_name in zip( (instrument_x, instrument_y, instrument_z), ("instrument_x", "instrument_y", "instrument_z"), ): if not isinstance(instrument, (AMIModel430, str)): - raise ValueError( + raise ValueError( # noqa: TRY004 f"Instruments need to be instances of the class AMIModel430 " f"or be valid names of already instantiated instances " f"of AMIModel430 class; {arg_name} argument is " @@ -717,7 +717,7 @@ def find_ami430_with_name(ami430_name: str) -> AMIModel430: # Conversion to float makes related driver logic simpler self._field_limit = float(field_limit) else: - raise ValueError( + raise ValueError( # noqa: TRY004 "field limit should either be a number or " "an iterable of callable field limit functions." ) diff --git a/src/qcodes/instrument_drivers/mock_instruments/__init__.py b/src/qcodes/instrument_drivers/mock_instruments/__init__.py index eb2a4ba632d5..42f13c0d6558 100644 --- a/src/qcodes/instrument_drivers/mock_instruments/__init__.py +++ b/src/qcodes/instrument_drivers/mock_instruments/__init__.py @@ -1362,7 +1362,7 @@ def __init__( elif isinstance(channel, InstrumentChannel): self._dac_channel = channel else: - raise ValueError('Unknown input type for "channel".') + raise ValueError('Unknown input type for "channel".') # noqa: TRY004 super().__init__(parent, name, **kwargs) diff --git a/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py b/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py index afd59796c5b6..756279e9fd0a 100644 --- a/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py +++ b/src/qcodes/instrument_drivers/oxford/MercuryiPS_VISA.py @@ -521,7 +521,7 @@ def _set_target(self, coordinate: str, target: float) -> None: cartesian_targ = self._target_vector.get_components("x", "y", "z") for targ, worker in zip(cartesian_targ, self.submodules.values()): if not isinstance(worker, OxfordMercuryWorkerPS): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expected an OxfordMercuryWorkerPS but got {type(worker)}" ) worker.field_target(targ) @@ -558,7 +558,7 @@ def _ramp_simultaneously(self) -> None: """ for worker in self.submodules.values(): if not isinstance(worker, OxfordMercuryWorkerPS): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expected an OxfordMercuryWorkerPS but got {type(worker)}" ) worker.ramp_to_target() @@ -573,7 +573,7 @@ def _ramp_simultaneously_blocking(self) -> None: for worker in self.submodules.values(): if not isinstance(worker, OxfordMercuryWorkerPS): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expected an OxfordMercuryWorkerPS but got {type(worker)}" ) # wait for the ramp to finish, we don't care about the order @@ -675,7 +675,7 @@ def ramp(self, mode: str = "safe") -> None: for cur, worker in zip(meas_vals, self.submodules.values()): if not isinstance(worker, OxfordMercuryWorkerPS): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 f"Expected an OxfordMercuryWorkerPS but got {type(worker)}" ) if worker.field_target() != cur and worker.field_ramp_rate() == 0: diff --git a/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py b/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py index 40cf8440e8c1..ba29e744506d 100644 --- a/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py +++ b/src/qcodes/instrument_drivers/signal_hound/SignalHound_USB_SA124B.py @@ -32,7 +32,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def set_raw(self, value: Any) -> None: if not isinstance(self.instrument, SignalHoundUSBSA124B): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "TraceParameter only works with 'SignalHound_USB_SA124B'" ) self.instrument._parameters_synced = False @@ -67,7 +67,7 @@ class ScaleParameter(TraceParameter): def set_raw(self, value: str) -> None: if not isinstance(self.instrument, SignalHoundUSBSA124B): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "ScaleParameter only works with 'SignalHound_USB_SA124B'" ) if value in ("log-scale", "log-full-scale"): @@ -92,7 +92,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: def set_raw(self, value: Any) -> None: if not isinstance(self.instrument, SignalHoundUSBSA124B): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "SweepTraceParameter only works with 'SignalHound_USB_SA124B'" ) self.instrument._trace_updated = False @@ -151,7 +151,7 @@ def set_sweep(self, sweep_len: int, start_freq: float, stepsize: float) -> None: """ if not isinstance(self.instrument, SignalHoundUSBSA124B): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "'FrequencySweep' is only implemented for 'SignalHound_USB_SA124B'" ) end_freq = start_freq + stepsize * (sweep_len - 1) @@ -164,7 +164,7 @@ def get_raw(self) -> npt.NDArray: if self.instrument is None: raise RuntimeError("No instrument is attached to 'FrequencySweep'") if not isinstance(self.instrument, SignalHoundUSBSA124B): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "'FrequencySweep' is only implemented for 'SignalHound_USB_SA124B'" ) if not self.instrument._trace_updated: diff --git a/src/qcodes/instrument_drivers/stanford_research/SR830.py b/src/qcodes/instrument_drivers/stanford_research/SR830.py index d5febc480ef3..74c232717b78 100644 --- a/src/qcodes/instrument_drivers/stanford_research/SR830.py +++ b/src/qcodes/instrument_drivers/stanford_research/SR830.py @@ -44,7 +44,7 @@ def __init__(self, name: str, channel: int, **kwargs: Any) -> None: ) if not isinstance(self.root_instrument, SR830): - raise ValueError( + raise ValueError( # noqa: TRY004 "Invalid parent instrument. ChannelBuffer can only live on an SR830." ) @@ -112,7 +112,7 @@ def __init__(self, name: str, instrument: SR830, channel: int) -> None: ) if not isinstance(instrument, SR830): - raise ValueError( + raise ValueError( # noqa: TRY004 "Invalid parent instrument. ChannelBuffer can only live on an SR830." ) diff --git a/src/qcodes/instrument_drivers/tektronix/AWG70000A.py b/src/qcodes/instrument_drivers/tektronix/AWG70000A.py index dd5d489d2ac2..dfd3707b15af 100644 --- a/src/qcodes/instrument_drivers/tektronix/AWG70000A.py +++ b/src/qcodes/instrument_drivers/tektronix/AWG70000A.py @@ -917,7 +917,7 @@ def _makeWFMXFileHeader(num_samples: int, markers_included: bool) -> str: offsetdigits = 9 if not isinstance(num_samples, int): - raise ValueError("num_samples must be of type int.") + raise ValueError("num_samples must be of type int.") # noqa: TRY004 if num_samples < 2400: raise ValueError("num_samples must be at least 2400.") diff --git a/src/qcodes/logger/logger.py b/src/qcodes/logger/logger.py index 83a0c0a1af2d..713a98c1fc3a 100644 --- a/src/qcodes/logger/logger.py +++ b/src/qcodes/logger/logger.py @@ -129,7 +129,7 @@ def get_level_name(level: str | int) -> str: elif isinstance(level, int): return logging.getLevelName(level) else: - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "get_level_name: " f"Cannot to convert level {level} of type " f"{type(level)} to logging level name. Need " @@ -148,7 +148,7 @@ def get_level_code(level: str | int) -> int: elif isinstance(level, str): return logging.getLevelNamesMapping()[level] else: - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "get_level_code: " f"Cannot to convert level {level} of type " f"{type(level)} to logging level code. Need " diff --git a/src/qcodes/parameters/_paramspec.py b/src/qcodes/parameters/_paramspec.py index 8f50956c9a1a..17b3c7cb20dc 100644 --- a/src/qcodes/parameters/_paramspec.py +++ b/src/qcodes/parameters/_paramspec.py @@ -32,7 +32,7 @@ def __init__( """ if not isinstance(paramtype, str): - raise ValueError("Paramtype must be a string.") + raise ValueError("Paramtype must be a string.") # noqa: TRY004 if paramtype.lower() not in self.allowed_types: raise ValueError(f"Illegal paramtype. Must be on of {self.allowed_types}") if not name.isidentifier(): diff --git a/src/qcodes/parameters/parameter_with_setpoints.py b/src/qcodes/parameters/parameter_with_setpoints.py index 9653be69281f..439f364c9494 100644 --- a/src/qcodes/parameters/parameter_with_setpoints.py +++ b/src/qcodes/parameters/parameter_with_setpoints.py @@ -63,7 +63,7 @@ def __init__( kwargs.setdefault("snapshot_value", False) vals = kwargs.get("vals") if not isinstance(vals, Arrays): - raise ValueError( + raise ValueError( # noqa: TRY004 f"A ParameterWithSetpoints must have an Arrays " f"validator got {type(vals)}" ) @@ -114,7 +114,7 @@ def validate_consistent_shape(self) -> None: """ if not isinstance(self.vals, Arrays): - raise ValueError( + raise ValueError( # noqa: TRY004 f"Can only validate shapes for parameters " f"with Arrays validator. {self.name} does " f"not have an Arrays validator." @@ -123,7 +123,7 @@ def validate_consistent_shape(self) -> None: setpoints_shape_list: list[int | Callable[[], int] | None] = [] for sp in self.setpoints: if not isinstance(sp.vals, Arrays): - raise ValueError( + raise ValueError( # noqa: TRY004 f"Can only validate shapes for parameters " f"with Arrays validator. {sp.name} is " f"a setpoint vector but does not have an " diff --git a/src/qcodes/plotting/matplotlib_helpers.py b/src/qcodes/plotting/matplotlib_helpers.py index c77d9fbe449e..ce85b2774c89 100644 --- a/src/qcodes/plotting/matplotlib_helpers.py +++ b/src/qcodes/plotting/matplotlib_helpers.py @@ -93,7 +93,7 @@ def apply_color_scale_limits( # browse the input data and make sure that `data_lim` and `new_lim` are # available if not isinstance(colorbar.mappable, matplotlib.collections.QuadMesh): - raise RuntimeError( + raise RuntimeError( # noqa: TRY004 "Can only scale mesh data, but received " f'"{type(colorbar.mappable)}" instead' ) diff --git a/src/qcodes/station.py b/src/qcodes/station.py index 1f5258e36d47..4390fb34654d 100644 --- a/src/qcodes/station.py +++ b/src/qcodes/station.py @@ -716,7 +716,7 @@ def update_monitor() -> None: else resolve_instrument_identifier(instr, ".".join(parts[:-1])) ) if isinstance(local_instr, ChannelTuple): - raise RuntimeError("A parameter cannot be added to an ChannelTuple") + raise RuntimeError("A parameter cannot be added to an ChannelTuple") # noqa: TRY004 add_parameter_from_dict(local_instr, parts[-1], options) self.add_component(instr, update_snapshot=update_snapshot) update_monitor()