QCoDeS [UNRELEASED DRAFT] (2026-09-28)

Breaking Changes:

  • Remove the deprecated TypeVars and type aliases that were retained as runtime compatibility imports after the conversion to PEP 695 type parameter syntax. Imports of typing.TypeVar and typing.ParamSpec are now banned: use PEP 695 type parameter syntax unless a default value requires the corresponding typing_extensions type on Python 3.12. (#8543)

Improved:

  • 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. (#8356)

  • Exceptions raised by QCoDeS drivers are now more specific.

    The ruff rule TRY002 (raise-vanilla-class) has been enabled. 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.

    In addition, two catch-all exception handlers have been narrowed to the specific exceptions that the guarded code can actually raise: reading a screenshot from the Keysight Infiniium now only handles OSError and VisaIOError, and waiting for the Tektronix AWG5014 to become ready only handles VisaIOError. Both previously swallowed unrelated errors.

    Finally, the coding style section of the contributor documentation now points at ruff check and ruff format, configured in pyproject.toml, rather than the long removed pycodestyle and flake8 based workflow. (#8368)

  • Adding metadata that SQLite cannot store in a column now fails with a message naming the tag and its type.

    A nested dict or a sequence previously surfaced as sqlite3.ProgrammingError: Error binding parameter 1: type 'list' is not supported, which said nothing about which tag was at fault. validate_dynamic_column_data now rejects such values with a TypeError suggesting serialization, alongside its existing checks for invalid tags and None. Values that NumPy registers a SQLite adapter for, such as scalars and arrays, are unaffected. (#8518)

Improved Drivers:

  • The Infiniium driver now tests explicitly for the type of parent instrument when required and used cast where pyvisa types the return of read_binary_values/query_binary_values as a Sequence[float] regardless of the requested container. This replaces five type checker suppressions. (#8457)

New:

  • Added split raw data storage for the DataSet: an opt-in results backend selected via the dataset.raw_data_backend config option (set it to "sqlite_per_dataset_db") that writes raw measurement data into individual per-dataset SQLite files while keeping all metadata in the main database. This prevents the main DB file from growing excessively large and allows users to have a single main SQLite QCoDeS database on their PC as opposed to many that grow infinitely. When selected, no results table is created in the main database at all - it holds only metadata - and all public DataSet APIs continue to work unchanged. Backend-specific settings (such as the per-dataset file folder raw_data_path) live under dataset.raw_data_backend_config.

    Management helpers included:

    • update_raw_data_paths(): update stored paths after raw data files have been moved to a new folder.

    • purge_orphaned_datasets(): find and remove dataset records whose raw data files no longer exist on disk (dry_run=True by default).

    • cleanup_datasets(): remove datasets (DB records and raw data files) by age, sample name, or file size (dry_run=True by default). (#8219)

  • The method for setting a parameter’s cache from a raw value as reported by the instrument is now public as Parameter.cache.set_from_raw_value. It is the raw-side counterpart of Parameter.cache.set and applies the same conversions as a get (get_parser, scale, offset and val_mapping). This is useful for drivers that populate the caches of many parameters from a single bulk instrument reply without issuing a get per parameter. The previous private _set_from_raw_value is kept as a backwards-compatible alias. (#8484)

Under the hood:

  • 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. (#8356)

  • CombinedParameter.parameter is now a small dataclass holding the name, full_name, label and unit of the combined parameter, replacing a lambda that had those attributes attached to it. It remains callable, and calling it returns None as before. (#8364)

  • Two additional ruff lint rules have been enabled.

    TRY004 (type-check-without-type-error) ensures 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.

    S110 (try-except-pass) flags exception handlers that silently pass. No behaviour changed as a result: the only remaining silent handlers are in teardown paths that must never raise or log, and they are now explicitly marked as intentional. (#8368)

  • 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. (#8498)