Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use typeddicts for better kwargs typing in Instrument classes #6012

Merged
merged 23 commits into from
May 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e708003
Add typed kwargs to Instrument/InstrumentBase
jenshnielsen Apr 27, 2024
3c7c78a
Add types for visa kwargs
jenshnielsen Apr 27, 2024
cd77b7c
Convert some instrument drivers to use typed kwargs
jenshnielsen Apr 27, 2024
0128409
Port agilent drivers to typed kwargs
jenshnielsen Apr 28, 2024
f3ffd50
Fix some type checking issues
jenshnielsen Apr 28, 2024
e194024
remove special case for visa kwargs
jenshnielsen Apr 29, 2024
650d457
mark as not required
jenshnielsen Apr 29, 2024
e5a2469
work around docs build error
jenshnielsen Apr 29, 2024
73eb309
Document Visa Args
jenshnielsen Apr 29, 2024
5cd0e4c
Port AimTTi drivers to explicit kwargs
jenshnielsen Apr 29, 2024
9cf3360
port ami driver to explicit kwargs
jenshnielsen Apr 29, 2024
64c6449
fix missing type category
jenshnielsen Apr 30, 2024
60c672c
Convert instruments upto and including ithaco
jenshnielsen Apr 30, 2024
9b1cbe3
add types to InstrumentModule
jenshnielsen Apr 30, 2024
92a70ff
update driver example notebooks with typed kwargs
jenshnielsen Apr 30, 2024
a0e8866
Alternative way to change terminator and timeout in subclass
jenshnielsen May 13, 2024
2c31212
Set terminator via attribute
jenshnielsen May 14, 2024
33eb2c8
Port Keithley to VisaKWargs dict
jenshnielsen May 14, 2024
77b808c
Keysight before B models
jenshnielsen May 14, 2024
144c51b
use typed kwargs for remaining Keysight drivers
jenshnielsen May 17, 2024
7620f47
remove redundant union[int,float]
jenshnielsen May 17, 2024
2bad66b
update docs to use default terminator arg
jenshnielsen May 17, 2024
90451da
Add changelog for 6012
jenshnielsen May 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/changes/newsfragments/6012.breaking
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
The keyword arguments expected to be passed to ``InstrumentBase`` and ``VisaInstrument`` subclasses are now
documented as TypedDics classes that can be used to type `**kwargs` in the subclass constructors.
See `Creating QCoDeS instrument drivers` for usage examples.

This also means that the these arguments **must** be passed as keyword arguments, and not as positional arguments.
This specifically includeds passing ``label`` and ``metadata`` to direct subclasses of ``Instrument`` as well as
``terminator`` to subclasses of ``VisaInstrument``.

All drivers shipping with qcodes for Vendors from A-K have been updated.
The remaining drivers will be updated in a subsequent pull request.
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,12 @@
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Logging hadn't been started.\n",
"Activating auto-logging. Current session state plus future input saved.\n",
"Filename : C:\\Users\\jenielse\\.qcodes\\logs\\command_history.log\n",
"Mode : append\n",
"Output logging : True\n",
"Raw input log : False\n",
"Timestamping : True\n",
"State : active\n",
"Qcodes Logfile : C:\\Users\\jenielse\\.qcodes\\logs\\220617-25816-qcodes.log\n"
]
}
],
"outputs": [],
"source": [
"from typing import TYPE_CHECKING\n",
"\n",
"if TYPE_CHECKING:\n",
" from typing_extensions import Unpack\n",
"import os\n",
"\n",
"import numpy as np\n",
Expand All @@ -44,7 +32,7 @@
" load_or_create_experiment,\n",
" plot_dataset,\n",
")\n",
"from qcodes.instrument import Instrument\n",
"from qcodes.instrument import Instrument, InstrumentBaseKWArgs\n",
"from qcodes.parameters import Parameter, ParameterWithSetpoints"
]
},
Expand Down Expand Up @@ -111,7 +99,7 @@
"\n",
"class OzzyLowScope(Instrument):\n",
"\n",
" def __init__(self, name, **kwargs):\n",
" def __init__(self, name: str, **kwargs: \"Unpack[InstrumentBaseKWArgs]\"):\n",
"\n",
" super().__init__(name, **kwargs)\n",
"\n",
Expand Down Expand Up @@ -494,7 +482,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.13"
"version": "3.12.1"
},
"toc": {
"base_numbering": 1,
Expand Down
46 changes: 33 additions & 13 deletions docs/examples/writing_drivers/Creating-Instrument-Drivers.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,23 @@
"import ctypes # only for DLL-based instrument\n",
"import sys\n",
"import time\n",
"from typing import Any\n",
"from typing import TYPE_CHECKING, Any\n",
"\n",
"if TYPE_CHECKING:\n",
" from typing_extensions import (\n",
" Unpack, # can be imported from typing if python >= 3.12\n",
" )\n",
"\n",
"import numpy as np\n",
"\n",
"from qcodes import validators as vals\n",
"from qcodes.instrument import Instrument, InstrumentChannel, VisaInstrument\n",
"from qcodes.instrument import (\n",
" Instrument,\n",
" InstrumentBaseKWArgs,\n",
" InstrumentChannel,\n",
" VisaInstrument,\n",
" VisaInstrumentKWArgs,\n",
")\n",
"from qcodes.instrument_drivers.AlazarTech.utils import TraceParameter\n",
"from qcodes.parameters import ManualParameter, MultiParameter, Parameter"
]
Expand Down Expand Up @@ -196,10 +207,17 @@
" \"\"\"\n",
"\n",
" # all instrument constructors should accept **kwargs and pass them on to\n",
" # super().__init__\n",
" def __init__(self, name, address, **kwargs):\n",
" # supplying the terminator means you don't need to remove it from every response\n",
" super().__init__(name, address, terminator=\"\\r\", **kwargs)\n",
" # super().__init__ By using Unpack[VisaKWArgs] we ensure that the instrument class takes exactly the same keyword args as the base class\n",
" # Visa instruments are also required to take name, address and terminator as arguments.\n",
" # name and address should be positional arguments. To overwrite the default terminator or timeout\n",
" # the attribute default_terminator or default_timeout should be defined as a class attribute in the instrument class\n",
" # as shown below for the default_terminator\n",
" default_terminator = \"\\r\"\n",
"\n",
" def __init__(\n",
" self, name: str, address: str, **kwargs: \"Unpack[VisaInstrumentKWArgs]\"\n",
" ):\n",
" super().__init__(name, address, **kwargs)\n",
"\n",
" self.attenuation = Parameter(\n",
" \"attenuation\",\n",
Expand Down Expand Up @@ -266,20 +284,21 @@
" SMUA and SMUB.\n",
" \"\"\"\n",
"\n",
" def __init__(self, parent: Instrument, name: str, channel: str) -> None:\n",
" def __init__(self, parent: Instrument, name: str, channel: str, **kwargs: \"Unpack[InstrumentBaseKWArgs]\") -> None:\n",
" \"\"\"\n",
" Args:\n",
" parent: The Instrument instance to which the channel is\n",
" to be attached.\n",
" name: The 'colloquial' name of the channel\n",
" channel: The name used by the Keithley, i.e. either\n",
" 'smua' or 'smub'\n",
" **kwargs: Forwarded to base class.\n",
" \"\"\"\n",
"\n",
" if channel not in [\"smua\", \"smub\"]:\n",
" raise ValueError('channel must be either \"smub\" or \"smua\"')\n",
"\n",
" super().__init__(parent, name)\n",
" super().__init__(parent, name, **kwargs)\n",
" self.model = self._parent.model\n",
"\n",
" self.volt = Parameter(\n",
Expand Down Expand Up @@ -342,15 +361,16 @@
" This is the qcodes driver for the Keithley2600 Source-Meter series,\n",
" tested with Keithley2614B\n",
" \"\"\"\n",
" default_terminator = \"\\n\"\n",
"\n",
" def __init__(self, name: str, address: str, **kwargs) -> None:\n",
" def __init__(self, name: str, address: str, **kwargs: \"Unpack[VisaInstrumentKWArgs]\") -> None:\n",
" \"\"\"\n",
" Args:\n",
" name: Name to use internally in QCoDeS\n",
" address: VISA ressource address\n",
" **kwargs: kwargs are forwarded to the base class.\n",
" \"\"\"\n",
" super().__init__(name, address, terminator=\"\\n\", **kwargs)\n",
" super().__init__(name, address, **kwargs)\n",
"\n",
" model = self.ask(\"localnode.model\")\n",
"\n",
Expand Down Expand Up @@ -416,7 +436,7 @@
"class AlazarTechATS(Instrument):\n",
" dll_path = \"C:\\\\WINDOWS\\\\System32\\\\ATSApi\"\n",
"\n",
" def __init__(self, name, system_id=1, board_id=1, dll_path=None, **kwargs):\n",
" def __init__(self, name, system_id=1, board_id=1, dll_path=None, **kwargs: \"Unpack[InstrumentBaseKWArgs]\"):\n",
" super().__init__(name, **kwargs)\n",
"\n",
" # connect to the DLL\n",
Expand Down Expand Up @@ -471,7 +491,7 @@
"class SomeDLLInstrument(Instrument):\n",
" dll_path = \"C:\\\\WINDOWS\\\\System32\\\\ATSApi\"\n",
"\n",
" def __init__(self, name, dll_path=None, **kwargs):\n",
" def __init__(self, name, dll_path=None, **kwargs: \"Unpack[InstrumentBaseKWArgs]\"):\n",
" super().__init__(name, **kwargs)\n",
"\n",
" if sys.platform != \"win32\":\n",
Expand Down Expand Up @@ -557,7 +577,7 @@
" This is a virtual driver only and will not talk to your instrument.\n",
" \"\"\"\n",
"\n",
" def __init__(self, name, **kwargs):\n",
" def __init__(self, name: str, **kwargs: \"Unpack[InstrumentBaseKWArgs]\"):\n",
" super().__init__(name, **kwargs)\n",
"\n",
" # ManualParameter has an \"initial_value\" kwarg, but if you use this\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,26 @@
}
],
"source": [
"from typing import TYPE_CHECKING\n",
"\n",
"import numpy as np\n",
"\n",
"import qcodes.validators as vals\n",
"from qcodes.instrument.visa import VisaInstrument\n",
"from qcodes.instrument.visa import VisaInstrument, VisaInstrumentKWArgs\n",
"\n",
"if TYPE_CHECKING:\n",
" from typing_extensions import Unpack\n",
"\n",
"\n",
"class Weinschel8320(VisaInstrument):\n",
" \"\"\"\n",
" QCoDeS driver for the stepped attenuator\n",
" Weinschel is formerly known as Aeroflex/Weinschel\n",
" \"\"\"\n",
" default_terminator = \"\\r\"\n",
"\n",
" def __init__(self, name, address, **kwargs):\n",
" super().__init__(name, address, terminator='\\r', **kwargs)\n",
" def __init__(self, name: str, address: str, **kwargs: 'Unpack[VisaInstrumentKWArgs]'):\n",
" super().__init__(name, address, **kwargs)\n",
"\n",
" self.add_parameter('attenuation', unit='dB',\n",
" set_cmd='ATTN ALL {:02.0f}',\n",
Expand Down
6 changes: 4 additions & 2 deletions src/qcodes/instrument/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@

from .channel import ChannelList, ChannelTuple, InstrumentChannel, InstrumentModule
from .instrument import Instrument, find_or_create_instrument
from .instrument_base import InstrumentBase
from .instrument_base import InstrumentBase, InstrumentBaseKWArgs
from .ip import IPInstrument
from .visa import VisaInstrument
from .visa import VisaInstrument, VisaInstrumentKWArgs

__all__ = [
"ChannelList",
"ChannelTuple",
"IPInstrument",
"Instrument",
"InstrumentBase",
"InstrumentBaseKWArgs",
"InstrumentChannel",
"InstrumentModule",
"VisaInstrument",
"VisaInstrumentKWArgs",
"find_or_create_instrument",
]
12 changes: 7 additions & 5 deletions src/qcodes/instrument/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
from .instrument_base import InstrumentBase

if TYPE_CHECKING:
from typing_extensions import Unpack

from .instrument import Instrument
from .instrument_base import InstrumentBaseKWArgs


class InstrumentModule(InstrumentBase):
Expand All @@ -33,15 +36,14 @@ class InstrumentModule(InstrumentBase):
Args:
parent: The instrument to which this module should be
attached.

name: The name of this module.
**kwargs: Forwarded to the base class.

"""

def __init__(self,
parent: InstrumentBase,
name: str,
**kwargs: Any) -> None:
def __init__(
self, parent: InstrumentBase, name: str, **kwargs: Unpack[InstrumentBaseKWArgs]
) -> None:
# need to specify parent before `super().__init__` so that the right
# `full_name` is available in that scope. `full_name` is used for
# registering the filter for the log messages. It is composed by
Expand Down
14 changes: 4 additions & 10 deletions src/qcodes/instrument/instrument.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@
from qcodes.utils import strip_attrs
from qcodes.validators import Anything

from .instrument_base import InstrumentBase
from .instrument_base import InstrumentBase, InstrumentBaseKWArgs
from .instrument_meta import InstrumentMeta

if TYPE_CHECKING:
from collections.abc import Mapping
from typing_extensions import Unpack

from qcodes.logger.instrument_logger import InstrumentLoggerAdapter


log = logging.getLogger(__name__)


Expand Down Expand Up @@ -66,16 +65,11 @@ class Instrument(InstrumentBase, metaclass=instrument_meta_class):
_type: type[Instrument] | None = None
_instances: weakref.WeakSet[Instrument] = weakref.WeakSet()

def __init__(
self,
name: str,
metadata: Mapping[Any, Any] | None = None,
label: str | None = None,
) -> None:
def __init__(self, name: str, **kwargs: Unpack[InstrumentBaseKWArgs]) -> None:
jenshnielsen marked this conversation as resolved.
Show resolved Hide resolved

self._t0 = time.time()

super().__init__(name=name, metadata=metadata, label=label)
super().__init__(name=name, **kwargs)

self.add_parameter("IDN", get_cmd=self.get_idn, vals=Anything())

Expand Down
21 changes: 21 additions & 0 deletions src/qcodes/instrument/instrument_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import TYPE_CHECKING, Any, ClassVar

import numpy as np
from typing_extensions import TypedDict

from qcodes.logger import get_instrument_logger
from qcodes.metadatable import Metadatable, MetadatableWithName
Expand All @@ -16,6 +17,8 @@
if TYPE_CHECKING:
from collections.abc import Callable, Mapping, Sequence

from typing_extensions import NotRequired

from qcodes.instrument.channel import ChannelTuple, InstrumentModule
from qcodes.logger.instrument_logger import InstrumentLoggerAdapter

Expand All @@ -24,6 +27,24 @@
log = logging.getLogger(__name__)


class InstrumentBaseKWArgs(TypedDict):
"""
This TypedDict defines the type of the kwargs that can be passed to the InstrumentBase class.
A subclass of VisaInstrument should take ``**kwargs: Unpack[InstrumentBaseKWArgs]`` as input
and forward this to the super class to ensure that it can accept all the arguments defined here.
"""

metadata: NotRequired[Mapping[Any, Any] | None]
"""
Additional static metadata to add to this
instrument's JSON snapshot.
"""
label: NotRequired[str | None]
"""
Nicely formatted name of the instrument; if None,
the ``name`` is used.
"""

class InstrumentBase(MetadatableWithName, DelegateAttributes):
"""
Base class for all QCodes instruments and instrument channels
Expand Down