Source code for semi_cr.core.station.hydration

"""Hydration module for the NQCP station configuration. Hydration is what turns our configuration into more
"live"-like objects, such as Chip and Connector. It is akin to the QCoDeS way of creating a rich object from the
configuration, only here we declare a _handler_ per type, treating the types as regular strings instead of the QCoDeS
way of pointing to some class. That allows the two approaches to coexist."""

from __future__ import annotations

import logging
from collections.abc import Callable, Mapping
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any

from semi_cr.core.station.chip import Chip
from semi_cr.core.station.graph import Connector
from semi_cr.core.station.instruments import LabInstrument
from semi_cr.core.station.instruments.dmm import DMMBlueprint
from semi_cr.core.station.instruments.opx1000 import OPX1000Blueprint
from semi_cr.core.station.instruments.spirack import SPIRackBlueprint

if TYPE_CHECKING:
    from semi_cr.core.lab.station.base import ConfigBundle
    from semi_cr.core.station.station import NQCPStation

CHIP_TYPE = "semi_cr.core.lab.instrument.chip.Chip"
CONNECTOR_TYPE = "semi_cr.core.lab.instrument.connector.Connector"
OPX1000_TYPE = "semi_cr.core.lab.drivers.opx_config.OPX1000"
SPIRACK_TYPE = "semi_cr.core.lab.drivers.spirack.QcodesSPIRack"
DMM_TYPE = "semi_cr.core.lab.instrument.routable_dmm.QcodesDMM"

InstrumentSpec = tuple[str, Mapping[str, Any]]
StaticLoader = Callable[[InstrumentSpec, Any], object]


[docs] @dataclass(frozen=True) class UnloadedSpec: name: str type: str config: Mapping[str, Any]
[docs] @dataclass class HydratedStationObjects: chips: list[Chip] = field(default_factory=list) connectors: list[Connector] = field(default_factory=list) instruments: list[LabInstrument] = field(default_factory=list) unloaded_specs: list[UnloadedSpec] = field(default_factory=list)
[docs] class MissingStaticLoaderError(Exception): pass
[docs] def load_chip(spec: InstrumentSpec, station: NQCPStation) -> Chip: return Chip.from_instrument_spec(spec, station)
[docs] def load_connector(spec: InstrumentSpec, station: NQCPStation) -> Connector: return Connector.from_instrument_spec(spec)
[docs] def load_opx1000(spec: InstrumentSpec, station: NQCPStation) -> OPX1000Blueprint: return OPX1000Blueprint.from_instrument_spec(spec)
[docs] def load_spirack(spec: InstrumentSpec, station: NQCPStation) -> SPIRackBlueprint: return SPIRackBlueprint.from_instrument_spec(spec)
[docs] def load_dmm(spec: InstrumentSpec, station: NQCPStation) -> DMMBlueprint: return DMMBlueprint.from_instrument_spec(spec)
STATIC_LOADERS: Mapping[str, StaticLoader] = { CHIP_TYPE: load_chip, CONNECTOR_TYPE: load_connector, OPX1000_TYPE: load_opx1000, SPIRACK_TYPE: load_spirack, DMM_TYPE: load_dmm, }
[docs] def hydrate_station_config(config_bundle: ConfigBundle, station: NQCPStation) -> HydratedStationObjects: hydrated = HydratedStationObjects() for spec in config_bundle.get_items(): name, config = spec try: loaded = hydrate_instrument_spec(spec, station) except MissingStaticLoaderError as error: spec_type = str(config.get("type", "<missing>")) logging.error("%s", error) hydrated.unloaded_specs.append( UnloadedSpec( name=name, type=spec_type, config=config, ) ) continue if isinstance(loaded, Chip): hydrated.chips.append(loaded) elif isinstance(loaded, Connector): hydrated.connectors.append(loaded) elif isinstance(loaded, LabInstrument): hydrated.instruments.append(loaded) return hydrated
[docs] def hydrate_instrument_spec(spec: InstrumentSpec, station: NQCPStation) -> object: name, config = spec spec_type = config.get("type", "") loader = STATIC_LOADERS.get(spec_type) if loader is None: raise MissingStaticLoaderError(f"No static loader for instrument {name!r} with type {spec_type!r}") return loader(spec, station)