Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,24 @@ reportUnusedClass = "none"
reportUnnecessaryCast = "none" # mypy already checks this. If it fails for pyright its because mypy requires it
reportUnnecessaryContains = "none"

[tool.ty]

[tool.ty.rules]

# we catch these via pyright or mypy so ignore here
# deprecated: we trigger deprecated on use of deprecated methods in other deprecated methods.
# unresolved-imports: we have a lot of imports that are only available with various libraries for specific instrument drivers
# unused-ignore-comment: mypy already checks for unused-ignores so it they are unused by ty its because mypy requires them
unresolved-import = "ignore"
deprecated = "ignore"
unused-type-ignore-comment = "ignore"

[[tool.ty.overrides]]
include = ["src/qcodes/instrument_drivers/Harvard/Decadac.py"]

[tool.ty.overrides.rules]
unresolved-attribute = "ignore"

[tool.pytest.ini_options]
minversion = "7.2"
testpaths = "tests"
Expand Down
9 changes: 7 additions & 2 deletions src/qcodes/dataset/dond/do_nd.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
_set_write_period,
catch_interrupts,
)
from qcodes.dataset.experiment_container import Experiment
from qcodes.dataset.measurements import Measurement
from qcodes.dataset.threading import (
SequentialParamsCaller,
Expand All @@ -42,7 +43,7 @@
MultiAxesTupleListWithDataSet,
ParamMeasT,
)
from qcodes.dataset.experiment_container import Experiment


LOG = logging.getLogger(__name__)
SweepVarType = Any
Expand Down Expand Up @@ -400,8 +401,12 @@ def _get_experiments(
experiments_internal: Sequence[Experiment | None] = [
experiments
] * n_experiments_required
else:
elif not isinstance(experiments, Experiment):
experiments_internal = experiments
else:
raise TypeError(
f"Invalid type for experiments got {experiments} of type {type(experiments)}"
)

if len(experiments_internal) != n_experiments_required:
raise ValueError(
Expand Down
4 changes: 3 additions & 1 deletion src/qcodes/extensions/infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,10 @@ def get_parent_instruments_from_chain_of_type(

param_chain = get_parameter_chain(parameter)
return tuple(
# cast is required since mypy as of 1.19.1 cannot infer the type narrowing based
# on isinstance checks inside comprehensions
[
cast("TInstrument", param.instrument)
cast("TInstrument", param.instrument) # ty: ignore[redundant-cast]
for param in param_chain
if isinstance(param.instrument, instrument_type)
]
Expand Down
108 changes: 49 additions & 59 deletions src/qcodes/instrument/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,42 +198,42 @@ def __init__(
)

@overload
def __getitem__(self, i: int) -> InstrumentModuleType: ...
def __getitem__(self, index: int) -> InstrumentModuleType: ...

@overload
def __getitem__(self: Self, i: slice | tuple[int, ...]) -> Self: ...
def __getitem__(self: Self, index: slice | tuple[int, ...]) -> Self: ...

def __getitem__(
self: Self, i: int | slice | tuple[int, ...]
self: Self, index: int | slice | tuple[int, ...]
) -> InstrumentModuleType | Self:
"""
Return either a single channel, or a new :class:`ChannelTuple`
containing only the specified channels

Args:
i: Either a single channel index or a slice of channels
index: Either a single channel index or a slice of channels
to get

"""
if isinstance(i, slice):
if isinstance(index, slice):
return type(self)(
self._parent,
self._name,
self._chan_type,
self._channels[i],
self._channels[index],
multichan_paramclass=self._paramclass,
snapshotable=self._snapshotable,
)
elif isinstance(i, tuple):
elif isinstance(index, tuple):
return type(self)(
self._parent,
self._name,
self._chan_type,
[self._channels[j] for j in i],
[self._channels[j] for j in index],
multichan_paramclass=self._paramclass,
snapshotable=self._snapshotable,
)
return self._channels[i]
return self._channels[index]

def __iter__(self) -> Iterator[InstrumentModuleType]:
return iter(self._channels)
Expand All @@ -244,8 +244,8 @@ def __reversed__(self) -> Iterator[InstrumentModuleType]:
def __len__(self) -> int:
return len(self._channels)

def __contains__(self, item: object) -> bool:
return item in self._channels
def __contains__(self, value: object) -> bool:
return value in self._channels

def __repr__(self) -> str:
return (
Expand Down Expand Up @@ -315,35 +315,31 @@ def name_parts(self) -> list[str]:
name_parts.append(self.short_name)
return name_parts

# the parameter obj should be called value but that would
# be an incompatible change
def index( # pyright: ignore[reportIncompatibleMethodOverride]
def index(
self,
obj: InstrumentModuleType,
value: InstrumentModuleType,
start: int = 0,
stop: int = sys.maxsize,
) -> int:
"""
Return the index of the given object

Args:
obj: The object to find in the channel list.
value: The object to find in the channel list.
start: Index to start searching from.
stop: Index to stop searching at.

"""
return self._channels.index(obj, start, stop)
return self._channels.index(value, start, stop)

def count( # pyright: ignore[reportIncompatibleMethodOverride]
self, obj: InstrumentModuleType
) -> int:
def count(self, value: InstrumentModuleType) -> int:
"""Returns number of instances of the given object in the list

Args:
obj: The object to find in the ChannelTuple.
value: The object to find in the ChannelTuple.

"""
return self._channels.count(obj)
return self._channels.count(value)

def get_channels_by_name(self: Self, *names: str) -> Self:
"""
Expand Down Expand Up @@ -717,15 +713,15 @@ def __init__(
self._locked = False

@overload
def __delitem__(self, key: int) -> None: ...
def __delitem__(self, index: int) -> None: ...

@overload
def __delitem__(self, key: slice) -> None: ...
def __delitem__(self, index: slice) -> None: ...

def __delitem__(self, key: int | slice) -> None:
def __delitem__(self, index: int | slice) -> None:
if self._locked:
raise AttributeError("Cannot delete from a locked channel list")
self._channels.__delitem__(key)
self._channels.__delitem__(index)
self._channel_mapping = {
channel.short_name: channel for channel in self._channels
}
Expand Down Expand Up @@ -759,27 +755,25 @@ def __setitem__(
channel.short_name: channel for channel in self._channels
}

def append( # pyright: ignore[reportIncompatibleMethodOverride]
self, obj: InstrumentModuleType
) -> None:
def append(self, value: InstrumentModuleType) -> None:
"""
Append a Channel to this list. Requires that the ChannelList is not
locked and that the channel is of the same type as the ones in the list.

Args:
obj: New channel to add to the list.
value: New channel to add to the list.

"""
if self._locked:
raise AttributeError("Cannot append to a locked channel list")
if not isinstance(obj, self._chan_type):
if not isinstance(value, self._chan_type):
raise TypeError(
f"All items in a channel list must be of the same "
f"type. Adding {type(obj).__name__} to a "
f"type. Adding {type(value).__name__} to a "
f"list of {self._chan_type.__name__}."
)
self._channel_mapping[obj.short_name] = obj
self._channels.append(obj)
self._channel_mapping[value.short_name] = value
self._channels.append(value)

def clear(self) -> None:
"""
Expand All @@ -791,63 +785,59 @@ def clear(self) -> None:
self._channels.clear()
self._channel_mapping.clear()

def remove( # pyright: ignore[reportIncompatibleMethodOverride]
self, obj: InstrumentModuleType
) -> None:
def remove(self, value: InstrumentModuleType) -> None:
"""
Removes obj from ChannelList if not locked.
Removes value from ChannelList if not locked.

Args:
obj: Channel to remove from the list.
value: Channel to remove from the list.

"""
if self._locked:
raise AttributeError("Cannot remove from a locked channel list")
else:
self._channels.remove(obj)
self._channel_mapping.pop(obj.short_name)
self._channels.remove(value)
self._channel_mapping.pop(value.short_name)

def extend( # pyright: ignore[reportIncompatibleMethodOverride]
self, objects: Iterable[InstrumentModuleType]
) -> None:
def extend(self, values: Iterable[InstrumentModuleType]) -> None:
"""
Insert an iterable of objects into the list of channels.
Insert an iterable of InstrumentModules into the list of channels.

Args:
objects: A list of objects to add into the
values: A list of InstrumentModules to add into the
:class:`ChannelList`.

"""
# objects may be a generator but we need to iterate over it twice
# values may be a generator but we need to iterate over it twice
# below so copy it into a tuple just in case.
if self._locked:
raise AttributeError("Cannot extend a locked channel list")
objects_tuple = tuple(objects)
if not all(isinstance(obj, self._chan_type) for obj in objects_tuple):
values_tuple = tuple(values)
if not all(isinstance(value, self._chan_type) for value in values_tuple):
raise TypeError("All items in a channel list must be of the same type.")
self._channels.extend(objects_tuple)
self._channel_mapping.update({obj.short_name: obj for obj in objects_tuple})
self._channels.extend(values_tuple)
self._channel_mapping.update(
{value.short_name: value for value in values_tuple}
)

def insert( # pyright: ignore[reportIncompatibleMethodOverride]
self, index: int, obj: InstrumentModuleType
) -> None:
def insert(self, index: int, value: InstrumentModuleType) -> None:
"""
Insert an object into the ChannelList at a specific index.

Args:
index: Index to insert object.
obj: Object of type chan_type to insert.
value: Object of type chan_type to insert.

"""
if self._locked:
raise AttributeError("Cannot insert into a locked channel list")
if not isinstance(obj, self._chan_type):
if not isinstance(value, self._chan_type):
raise TypeError(
f"All items in a channel list must be of the same "
f"type. Adding {type(obj).__name__} to a list of {self._chan_type.__name__}."
f"type. Adding {type(value).__name__} to a list of {self._chan_type.__name__}."
)
self._channels.insert(index, obj)
self._channel_mapping[obj.short_name] = obj
self._channels.insert(index, value)
self._channel_mapping[value.short_name] = value

def get_validator(self) -> ChannelTupleValidator:
"""
Expand Down
2 changes: 1 addition & 1 deletion src/qcodes/instrument/mockers/ami430.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def _handle_messages(self, msg):
if callable(handler):
# some of the callables in the dict does not take arguments.
# ignore that warning for now since this is mock code only
rval = handler(args) # pyright: ignore[reportCallIssue]
rval = handler(args) # pyright: ignore[reportCallIssue] # ty: ignore[ too-many-positional-arguments]
else:
rval = handler

Expand Down
7 changes: 4 additions & 3 deletions src/qcodes/instrument_drivers/AlazarTech/dll_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,22 +62,23 @@ def _mark_params_as_updated(*args: Any) -> None:
def _check_error_code(
return_code: int, func: Callable[..., Any], arguments: tuple[Any, ...]
) -> tuple[Any, ...]:
func_name: str = getattr(func, "__name__", "UnknownFunction")
if return_code not in {API_SUCCESS, API_DMA_IN_PROGRESS}:
argrepr = repr(arguments)
if len(argrepr) > 100:
argrepr = argrepr[:96] + "...]"

logger.error(
f"Alazar API returned code {return_code} from function "
f"{func.__name__} with args {argrepr}"
f"{func_name} with args {argrepr}"
)

if return_code not in ERROR_CODES:
raise RuntimeError(
f"unknown error {return_code} from function {func.__name__} with args: {argrepr}"
f"unknown error {return_code} from function {func_name} with args: {argrepr}"
)
raise RuntimeError(
f"error {return_code}: {ERROR_CODES[ReturnCode(return_code)]} from function {func.__name__} with args: {argrepr}"
f"error {return_code}: {ERROR_CODES[ReturnCode(return_code)]} from function {func_name} with args: {argrepr}"
)

return arguments
Expand Down
7 changes: 5 additions & 2 deletions src/qcodes/instrument_drivers/tektronix/AWG5014.py
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,9 @@ def get_sq_mode(self) -> str:
def _pack_record(
self, name: str, value: float | str | Sequence[Any] | npt.NDArray, dtype: str
) -> bytes:
def _pack_numpy_array(array: npt.NDArray) -> bytes:
return array.astype("<u2").tobytes()

"""
Packs awg_file record into a struct in the folowing way:
struct.pack(fmtstring, namesize, datasize, name, data)
Expand Down Expand Up @@ -945,8 +948,8 @@ def _pack_record(
else:
assert isinstance(value, (abc.Sequence, np.ndarray))
if dtype[-1] == "H" and isinstance(value, np.ndarray):
# numpy conversion is fast
record_data = value.astype("<u2").tobytes()
# numpy conversion is fast so use that when possible
record_data = _pack_numpy_array(value)
else:
# argument unpacking is slow
record_data = struct.pack("<" + dtype, *value)
Expand Down
13 changes: 11 additions & 2 deletions src/qcodes/logger/instrument_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ class InstrumentFilter(logging.Filter):
"""

def __init__(self, instruments: InstrumentBase | Sequence[InstrumentBase]):
# avoid importing qcodes.instrument at module level to prevent circular imports
from qcodes.instrument import InstrumentBase # noqa: PLC0415

super().__init__()
if not isinstance(instruments, collections.abc.Sequence):
if isinstance(instruments, InstrumentBase):
instrument_seq: Sequence[str] = (instruments.full_name,)
else:
instrument_seq = [inst.full_name for inst in instruments]
Expand Down Expand Up @@ -188,8 +191,14 @@ def filter_instrument(
handlers = (myhandler,)
elif not isinstance(handler, collections.abc.Sequence):
handlers = (handler,)
else:
elif isinstance(handler, collections.abc.Sequence) and not isinstance(
handler, logging.Handler
):
handlers = handler
else:
raise TypeError(
f"handler must be a Handler or a Sequence of Handlers got {type(handler)}"
)

instrument_filter = InstrumentFilter(instrument)
for h in handlers:
Expand Down
Loading
Loading