Skip to content

QCoDeS 0.61.0

Choose a tag to compare

@jenshnielsen jenshnielsen released this 08 Oct 11:48
· 305 commits to main since this release
e5f62a9

QCoDeS 0.61.0 (2026-10-08)

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

  • Fixed NetCDF export of measurements without setpoint dependencies, including
    scalar and array-valued parameters measured with do0d or dond.
    The exported data retains its shape using parameter-specific dimensions. #8570

  • A second interrupt during a database write rolls the open transaction back instead of leaving the file locked.

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

  • Instruments can now opt in to a logger of their own, rather than sharing a
    single logger with every other instrument in the process. Setting the class
    attribute default_logger_scope to "instrument" makes Instrument.log
    (and VisaInstrument.visa_log) use a logger named after the driver class and
    the instrument's name_parts, so that levels, handlers and propagate can
    be configured per instrument::

    class MyDriver(VisaInstrument):
        default_logger_scope = "instrument"
    

    The scoped loggers mirror the instrument hierarchy below the module-qualified
    driver class. For an instrument myinst of MyDriver, defined in the
    module vendor.driver, the loggers are named::

    vendor.driver.MyDriver                    the driver class
    vendor.driver.MyDriver.myinst             Instrument.log
    vendor.driver.MyDriver.myinst.my_module   log of a submodule
    vendor.driver.MyDriver.myinst.com.visa    VisaInstrument.visa_log
    

    A submodule is named after the key it is added with in add_submodule, for
    example my_module for self.add_submodule("my_module", MyModule(self)),
    and not after its own name or class. A submodule that is not added as an
    individual submodule, such as a channel that only exists in a channel list, is
    named after its own name.

    Each logger is a child of the previous one, so a level or handler can be set
    for a whole driver class, for everything that belongs to one instrument
    (its own messages, its submodules and its VISA traffic), or for a single
    submodule or the VISA traffic of one instrument. Setting the level for a whole
    driver also works before any of its instruments exist, so it can be configured
    via logger_levels in qcodesrc.json.

    Because module-qualified scoped loggers are no longer below
    qcodes.instrument.instrument_base, opted-in drivers do not inherit levels
    configured on that shared logger or on qcodes.instrument.instrument_base.com.visa.
    Configure the module-qualified driver class logger instead.

    The default is unchanged: without opting in, all instruments keep sharing one
    logger exactly as before. #8523

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

  • The test suite no longer depends on deepdiff. The tests that used it now compare the data directly
    using InterDependencies equality and numpy.testing.assert_array_equal. #8552