diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 52903232..e8c167db 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,7 +41,7 @@ repos: - id: check-github-actions - id: check-github-workflows - repo: https://github.com/commitizen-tools/commitizen - rev: v3.14.1 + rev: v3.15.0 hooks: - id: commitizen stages: [commit-msg] @@ -125,7 +125,7 @@ repos: always_run: true args: [., --min=10] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.2.2 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/CHANGELOG.md b/CHANGELOG.md index 16580b2d..fb5d1193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ Things to be included in the next release go here. - Updated the `get_model_series()` function to use a regex mapping instead of complicated logic to reduce maintenance costs. +### Fixed + +- Updated import statements for the `ReadOnlyCachedProperty` decorator to allow PyCharm auto-complete to work properly. + ______________________________________________________________________ ## v1.2.0 (2024-02-09) diff --git a/docs/contributing/add_new_driver.md b/docs/contributing/add_new_driver.md index db23a446..ccc1e424 100644 --- a/docs/contributing/add_new_driver.md +++ b/docs/contributing/add_new_driver.md @@ -25,7 +25,7 @@ This guide will walk through the steps needed to add a new device driver. - See other `__init__.py` files for examples 04. Update the `SupportedModels` enum exposed in `tm_devices/helpers/__init__.py` -05. Update the `_SUPPORTED_MODEL_REGEX_MAPPING` regex constant inside +05. Update the `___SUPPORTED_MODEL_REGEX_STRING` regex constant inside `tm_devices/helpers/functions.py` to include a mapping of the new driver name (model series) to a regex string matching the appropriate model strings 06. Update the `DEVICE_DRIVER_MODEL_MAPPING` lookup inside diff --git a/pyproject.toml b/pyproject.toml index 68045c4b..d3ec60b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,7 @@ poetry = ">=1.5.1" pre-commit = ">=2.20.0" pre-commit-update = ">=0.1.3" pylint = {extras = ["spelling"], version = "3.0.3"} # Update this by running scripts/update_development_dependencies.py -pyright = "1.1.350" # Update this by running scripts/update_development_dependencies.py +pyright = "1.1.351" # Update this by running scripts/update_development_dependencies.py pyroma = ">=4.2" pytest = ">=7.1.2" pytest-cov = ">=3.0.0" @@ -113,7 +113,7 @@ pytest-html = ">=4.0" pytest-order = ">=1.0.1" pytest-profiling = ">=1.7.0" python-semantic-release = ">=8.5.1" -ruff = "0.2.1" # Update this by running scripts/update_development_dependencies.py +ruff = "0.2.2" # Update this by running scripts/update_development_dependencies.py safety = ">=2.1.1" sphinx-autoapi = ">=2.0.0" sphinx-copybutton = ">=0.5.1" diff --git a/src/tm_devices/driver_mixins/licensed_mixin.py b/src/tm_devices/driver_mixins/licensed_mixin.py index b7136e50..add2ff71 100644 --- a/src/tm_devices/driver_mixins/licensed_mixin.py +++ b/src/tm_devices/driver_mixins/licensed_mixin.py @@ -2,13 +2,14 @@ from abc import ABC, abstractmethod from typing import final, Tuple -from tm_devices.helpers import ReadOnlyCachedProperty +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class LicensedMixin(ABC): """A mixin class which adds methods and properties for handling licenses.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def license_list(self) -> Tuple[str, ...]: """Return the list of licenses installed on the device.""" diff --git a/src/tm_devices/driver_mixins/usb_drives_mixin.py b/src/tm_devices/driver_mixins/usb_drives_mixin.py index 1b9d18e2..9cb9aec5 100644 --- a/src/tm_devices/driver_mixins/usb_drives_mixin.py +++ b/src/tm_devices/driver_mixins/usb_drives_mixin.py @@ -2,14 +2,15 @@ from abc import ABC, abstractmethod from typing import Tuple -from tm_devices.helpers import ReadOnlyCachedProperty +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 # pylint: disable=too-few-public-methods class USBDrivesMixin(ABC): """A mixin class which adds the usb_drives property.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def usb_drives(self) -> Tuple[str, ...]: """Return a list of all connected USB drives.""" diff --git a/src/tm_devices/drivers/api/rest_api/margin_testers/margin_tester.py b/src/tm_devices/drivers/api/rest_api/margin_testers/margin_tester.py index 20a8854a..1441c437 100644 --- a/src/tm_devices/drivers/api/rest_api/margin_testers/margin_tester.py +++ b/src/tm_devices/drivers/api/rest_api/margin_testers/margin_tester.py @@ -10,7 +10,10 @@ from tm_devices.drivers.api.rest_api.rest_api_device import RESTAPIDevice from tm_devices.drivers.device import family_base_class -from tm_devices.helpers import DeviceConfigEntry, DeviceTypes, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry, DeviceTypes + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -36,22 +39,22 @@ def __init__(self, config_entry: DeviceConfigEntry, verbose: bool) -> None: ################################################################################################ # Abstract Cached Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property @abstractmethod def adapter(self) -> str: """Return the device's connected adapter.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def fpga_version(self) -> Version: """Return the fpga version of the device.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def fw_version(self) -> Version: """Return the firmware version of the device.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def supported_technologies(self) -> Tuple[str, ...]: """Return the device's supported technologies.""" diff --git a/src/tm_devices/drivers/api/rest_api/margin_testers/tmt4.py b/src/tm_devices/drivers/api/rest_api/margin_testers/tmt4.py index baf1a861..80e83c9c 100644 --- a/src/tm_devices/drivers/api/rest_api/margin_testers/tmt4.py +++ b/src/tm_devices/drivers/api/rest_api/margin_testers/tmt4.py @@ -7,7 +7,10 @@ from packaging.version import Version from tm_devices.drivers.api.rest_api.margin_testers.margin_tester import MarginTester -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class TMT4(MarginTester): @@ -42,12 +45,12 @@ def __init__(self, config_entry: DeviceConfigEntry, verbose: bool) -> None: ################################################################################################ # Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def adapter(self) -> str: """Return the device's connected adapter.""" return self._about_info["adapter"] - @ReadOnlyCachedProperty + @cached_property def fpga_version(self) -> Version: """Return the fpga version of the device.""" # This key can return strings indicating a reboot is needed instead of Versions. @@ -56,7 +59,7 @@ def fpga_version(self) -> Version: except ValueError: return Version("0") - @ReadOnlyCachedProperty + @cached_property def fw_version(self) -> Version: """Return the firmware version of the device.""" # This key can (also) return strings indicating a reboot is needed instead of Versions. @@ -65,12 +68,12 @@ def fw_version(self) -> Version: except ValueError: return Version("0") - @ReadOnlyCachedProperty + @cached_property def manufacturer(self) -> str: """Return the manufacturer of the device.""" return self._about_info["manufacturer"] - @ReadOnlyCachedProperty + @cached_property def model(self) -> str: """Return the full model of the device.""" return self._about_info["model"] @@ -80,17 +83,17 @@ def port(self) -> Optional[int]: """Return the configured device port, defaults to 5000.""" return super().port or 5000 - @ReadOnlyCachedProperty + @cached_property def serial(self) -> str: """Return the serial number of the device.""" return self._about_info["serialNumber"] - @ReadOnlyCachedProperty + @cached_property def supported_technologies(self) -> Tuple[str, ...]: """Return the device's supported technologies.""" return tuple(self._about_info["supportedTechnologies"].split(",")) - @ReadOnlyCachedProperty + @cached_property def sw_version(self) -> Version: """Return the software version of the device.""" return Version(self._about_info["sw_version"]) diff --git a/src/tm_devices/drivers/device.py b/src/tm_devices/drivers/device.py index 190182ca..572a4db7 100644 --- a/src/tm_devices/drivers/device.py +++ b/src/tm_devices/drivers/device.py @@ -4,7 +4,7 @@ from abc import ABC, abstractmethod from contextlib import contextmanager, suppress -from functools import cached_property +from functools import cached_property as functools_cached_property from typing import ( Any, final, @@ -25,9 +25,11 @@ DeviceConfigEntry, get_timestamp_string, print_with_timestamp, - ReadOnlyCachedProperty, ) +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 + _T = TypeVar("_T") _FAMILY_BASE_CLASS_PROPERTY_NAME = "_product_family_base_class" @@ -100,22 +102,22 @@ def __str__(self) -> str: ################################################################################################ # Abstract Cached Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property @abstractmethod def manufacturer(self) -> str: """Return the manufacturer of the device.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def model(self) -> str: """Return the full model of the device.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def serial(self) -> str: """Return the serial number of the device.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def sw_version(self) -> Version: """Return the software version of the device.""" @@ -267,7 +269,7 @@ def port(self) -> Optional[int]: """Return the device port, or None if the device doesn't have a port.""" return self._config_entry.lan_port - @ReadOnlyCachedProperty + @cached_property def series(self) -> str: """Return the series of the device. @@ -284,7 +286,7 @@ def verbose(self) -> bool: ################################################################################################ # Cached Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def hostname(self) -> str: """Return the hostname of the device or an empty string if unable to fetch that.""" if self._config_entry.connection_type not in {ConnectionTypes.USB}: @@ -295,7 +297,7 @@ def hostname(self) -> str: pass return "" - @ReadOnlyCachedProperty + @cached_property def ip_address(self) -> str: """Return the IPv4 address of the device or an empty string if unable to fetch that.""" if self._config_entry.connection_type not in {ConnectionTypes.USB}: @@ -471,7 +473,7 @@ def reboot(self, quiet_period: int = 0) -> bool: """ # Reset the cached properties for prop in self._get_self_properties(): - if isinstance(getattr(self.__class__, prop), cached_property): + if isinstance(getattr(self.__class__, prop), functools_cached_property): # Try to delete the cached_property, if it raises an AttributeError, # that means that it has not previously been accessed and # there is no need to delete the cached_property. @@ -689,7 +691,7 @@ def _get_self_properties(self) -> Tuple[str, ...]: return tuple( p for p in dir(self.__class__) - if isinstance(getattr(self.__class__, p), (cached_property, property)) + if isinstance(getattr(self.__class__, p), (functools_cached_property, property)) ) @staticmethod diff --git a/src/tm_devices/drivers/pi/data_acquisition_systems/daq6510.py b/src/tm_devices/drivers/pi/data_acquisition_systems/daq6510.py index b2b1ab3b..0a17eac7 100644 --- a/src/tm_devices/drivers/pi/data_acquisition_systems/daq6510.py +++ b/src/tm_devices/drivers/pi/data_acquisition_systems/daq6510.py @@ -8,7 +8,10 @@ from tm_devices.drivers.pi.data_acquisition_systems.data_acquisition_system import ( DataAcquisitionSystem, ) -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class DAQ6510(DAQ6510Mixin, DataAcquisitionSystem): @@ -47,7 +50,7 @@ def ieee_cmds(self) -> LegacyTSPIEEE4882Commands: """Return an internal class containing methods for the standard IEEE 488.2 command set.""" return self._ieee_cmds # pyright: ignore[reportReturnType] - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return 1 diff --git a/src/tm_devices/drivers/pi/pi_device.py b/src/tm_devices/drivers/pi/pi_device.py index c1c113ef..94b6aa71 100644 --- a/src/tm_devices/drivers/pi/pi_device.py +++ b/src/tm_devices/drivers/pi/pi_device.py @@ -27,8 +27,10 @@ get_visa_backend, print_with_timestamp, PYVISA_PY_BACKEND, - ReadOnlyCachedProperty, ) + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 from tm_devices.helpers.constants_and_dataclasses import UNIT_TEST_TIMEOUT @@ -83,7 +85,7 @@ def __init__( def all_channel_names_list(self) -> Tuple[str, ...]: """Return a tuple containing all the channel names.""" - @ReadOnlyCachedProperty + @cached_property @abstractmethod def total_channels(self) -> int: """Return the total number of channels (all types).""" @@ -187,7 +189,7 @@ def visa_resource(self) -> visa.resources.MessageBasedResource: ################################################################################################ # Cached Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def sw_version(self) -> Version: """Return the software version of the device.""" id_string_parts = self.idn_string.split(",") @@ -204,27 +206,27 @@ def sw_version(self) -> Version: retval = get_version(sw_version) return retval - @ReadOnlyCachedProperty + @cached_property def idn_string(self) -> str: r"""Return the string returned from the ``*IDN?`` query when the device was created.""" return self.ieee_cmds.idn() - @ReadOnlyCachedProperty + @cached_property def manufacturer(self) -> str: """Return the manufacturer of the device.""" return self.idn_string.split(",")[0].strip() - @ReadOnlyCachedProperty + @cached_property def model(self) -> str: """Return the full model of the device.""" return self.idn_string.split(",")[1].strip() - @ReadOnlyCachedProperty + @cached_property def serial(self) -> str: """Return the serial number of the device.""" return self.idn_string.split(",")[2].strip() - @ReadOnlyCachedProperty + @cached_property def series(self) -> str: """Return the series of the device. @@ -233,7 +235,7 @@ def series(self) -> str: """ return get_model_series(self.model) - @ReadOnlyCachedProperty + @cached_property def visa_backend(self) -> str: """Return the VISA backend in use.""" return get_visa_backend(self._visa_resource.visalib.library_path.path) diff --git a/src/tm_devices/drivers/pi/power_supplies/psu2200/psu2200.py b/src/tm_devices/drivers/pi/power_supplies/psu2200/psu2200.py index ecdf8348..5662c1be 100644 --- a/src/tm_devices/drivers/pi/power_supplies/psu2200/psu2200.py +++ b/src/tm_devices/drivers/pi/power_supplies/psu2200/psu2200.py @@ -5,7 +5,10 @@ from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.power_supplies.power_supply import PowerSupplyUnit -from tm_devices.helpers import get_version, ReadOnlyCachedProperty +from tm_devices.helpers import get_version + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -24,7 +27,7 @@ def all_channel_names_list(self) -> Tuple[str, ...]: """Return a tuple containing all the channel names.""" return tuple(f"SOURCE{x+1}" for x in range(self.total_channels)) - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return max(1, int(self.model[2])) if self.model[2].isdecimal() else 1 @@ -32,7 +35,7 @@ def total_channels(self) -> int: ################################################################################################ # Public Methods ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def fpga_version(self) -> Version: """Return the fpga version of the device.""" id_string_parts = self.idn_string.split(",") diff --git a/src/tm_devices/drivers/pi/scopes/scope.py b/src/tm_devices/drivers/pi/scopes/scope.py index fb3cfa52..38c2a678 100644 --- a/src/tm_devices/drivers/pi/scopes/scope.py +++ b/src/tm_devices/drivers/pi/scopes/scope.py @@ -5,7 +5,10 @@ from typing import Any, List, Optional, Tuple, Union from tm_devices.drivers.pi.pi_device import PIDevice -from tm_devices.helpers import DeviceTypes, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceTypes + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class Scope(PIDevice, ABC): @@ -36,7 +39,7 @@ def all_channel_names_list(self) -> Tuple[str, ...]: """Return a tuple containing all the channel names.""" return tuple(f"CH{x+1}" for x in range(self.total_channels)) - @ReadOnlyCachedProperty + @cached_property def opt_string(self) -> str: r"""Return the string returned from the ``*OPT?`` query when the device was created.""" return self.ieee_cmds.opt() diff --git a/src/tm_devices/drivers/pi/scopes/tekscope/mso2.py b/src/tm_devices/drivers/pi/scopes/tekscope/mso2.py index 1f9aa6e4..e5244ace 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope/mso2.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope/mso2.py @@ -5,7 +5,10 @@ from tm_devices.commands import MSO2Mixin from tm_devices.drivers.pi.scopes.tekscope.tekscope import TekScope -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class MSO2(MSO2Mixin, TekScope): @@ -49,7 +52,7 @@ def all_channel_names_list(self) -> Tuple[str, ...]: retval = (*retval[:-1], "DCH1") return retval - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" retval = super().total_channels diff --git a/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py b/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py index 4a79ed10..3f957270 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope/tekscope.py @@ -37,7 +37,10 @@ from tm_devices.driver_mixins.usb_drives_mixin import USBDrivesMixin from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.scopes.scope import Scope -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty, SignalSourceFunctionsIAFG +from tm_devices.helpers import DeviceConfigEntry, SignalSourceFunctionsIAFG + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 from tm_devices.helpers.constants_and_dataclasses import UNIT_TEST_TIMEOUT @@ -120,7 +123,7 @@ def __init__( ################################################################################################ # Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def channel(self) -> "MappingProxyType[str, TekScopeChannel]": """Mapping of channel names to any detectable properties, attributes, and settings.""" # TODO: overwrite in MSO2 driver, would remove need for try-except @@ -185,12 +188,12 @@ def commands( """Return the device commands.""" return self._commands # pragma: no cover - @ReadOnlyCachedProperty + @cached_property def hostname(self) -> str: """Return the hostname of the device or an empty string if unable to fetch that.""" return self.query(":ETHERNET:NAME?", verbose=False, remove_quotes=True) - @ReadOnlyCachedProperty + @cached_property def license_list(self) -> Tuple[str, ...]: """Return the list of license AppIDs installed on the scope.""" license_list = self.query( @@ -209,7 +212,7 @@ def source_device_constants(self) -> TekScopeSourceDeviceConstants: """Return the device constants.""" return self._DEVICE_CONSTANTS - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" try: @@ -217,7 +220,7 @@ def total_channels(self) -> int: except ValueError: return 0 - @ReadOnlyCachedProperty + @cached_property def usb_drives(self) -> Tuple[str, ...]: """Return a list of all connected USB drives.""" # Find all USB drives connected to the device diff --git a/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py b/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py index 3d87ba5b..582bf270 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope/tekscopesw.py @@ -4,7 +4,10 @@ import pyvisa as visa from tm_devices.drivers.pi.scopes.tekscope.tekscope import TekScope -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class TekScopeSW(TekScope): @@ -47,7 +50,7 @@ def commands(self) -> Any: """Return the device commands.""" return self._commands - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return 0 diff --git a/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/mdo3.py b/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/mdo3.py index 48014554..264aeb2a 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/mdo3.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/mdo3.py @@ -3,7 +3,10 @@ from tm_devices.commands import MDO3Mixin from tm_devices.drivers.pi.scopes.tekscope_3k_4k.tekscope_3k_4k import TekScope3k4k -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class MDO3(MDO3Mixin, TekScope3k4k): @@ -30,7 +33,7 @@ def __init__( ################################################################################################ # Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return int(self.model[-1]) diff --git a/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/tekscope_3k_4k.py b/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/tekscope_3k_4k.py index 56748e0b..8aee5c2c 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/tekscope_3k_4k.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope_3k_4k/tekscope_3k_4k.py @@ -3,7 +3,9 @@ from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.scopes.scope import Scope -from tm_devices.helpers import ReadOnlyCachedProperty + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -18,12 +20,12 @@ class TekScope3k4k(Scope, ABC): # Properties ################################################################################################ - @ReadOnlyCachedProperty + @cached_property def hostname(self) -> str: """Return the hostname of the device or an empty string if unable to fetch that.""" return self.query(":ETHERNET:NAME?", verbose=False, remove_quotes=True) - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return int(self.model[6]) diff --git a/src/tm_devices/drivers/pi/scopes/tekscope_5k_7k_70k/tekscope_5k_7k_70k.py b/src/tm_devices/drivers/pi/scopes/tekscope_5k_7k_70k/tekscope_5k_7k_70k.py index 4d222545..7ae7de2e 100644 --- a/src/tm_devices/drivers/pi/scopes/tekscope_5k_7k_70k/tekscope_5k_7k_70k.py +++ b/src/tm_devices/drivers/pi/scopes/tekscope_5k_7k_70k/tekscope_5k_7k_70k.py @@ -5,7 +5,10 @@ from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.scopes.scope import Scope -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -49,7 +52,7 @@ def num_dig_bits_in_ch(self) -> int: # TODO: should be part of self.channel return self._num_dig_bits_in_ch - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return self._total_channels diff --git a/src/tm_devices/drivers/pi/signal_sources/afgs/afg.py b/src/tm_devices/drivers/pi/signal_sources/afgs/afg.py index 5d4ae797..ed7005b6 100644 --- a/src/tm_devices/drivers/pi/signal_sources/afgs/afg.py +++ b/src/tm_devices/drivers/pi/signal_sources/afgs/afg.py @@ -8,7 +8,10 @@ from tm_devices.driver_mixins.signal_generator_mixin import SourceDeviceConstants from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.signal_sources.signal_source import SignalSource -from tm_devices.helpers import DeviceTypes, ReadOnlyCachedProperty, SignalSourceFunctionsAFG +from tm_devices.helpers import DeviceTypes, SignalSourceFunctionsAFG + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @dataclass(frozen=True) @@ -32,7 +35,7 @@ def source_device_constants(self) -> AFGSourceDeviceConstants: """Return the device constants.""" return self._DEVICE_CONSTANTS # type: ignore[attr-defined] - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" if match := re.match(r"AFG\d+(\d)", self.model): diff --git a/src/tm_devices/drivers/pi/signal_sources/awgs/awg.py b/src/tm_devices/drivers/pi/signal_sources/awgs/awg.py index bbd89ded..5a008d5e 100644 --- a/src/tm_devices/drivers/pi/signal_sources/awgs/awg.py +++ b/src/tm_devices/drivers/pi/signal_sources/awgs/awg.py @@ -10,7 +10,10 @@ from tm_devices.driver_mixins.signal_generator_mixin import SourceDeviceConstants from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.signal_sources.signal_source import SignalSource -from tm_devices.helpers import DeviceTypes, ReadOnlyCachedProperty, SignalSourceFunctionsAWG +from tm_devices.helpers import DeviceTypes, SignalSourceFunctionsAWG + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @dataclass(frozen=True) @@ -38,7 +41,7 @@ def source_device_constants(self) -> AWGSourceDeviceConstants: """Return the device constants.""" return self._DEVICE_CONSTANTS # type: ignore[attr-defined] - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return int(self.query("AWGControl:CONFigure:CNUMber?", verbose=False)) diff --git a/src/tm_devices/drivers/pi/signal_sources/signal_source.py b/src/tm_devices/drivers/pi/signal_sources/signal_source.py index 4bda52ee..bd9f485d 100644 --- a/src/tm_devices/drivers/pi/signal_sources/signal_source.py +++ b/src/tm_devices/drivers/pi/signal_sources/signal_source.py @@ -7,7 +7,10 @@ from tm_devices.driver_mixins.signal_generator_mixin import SignalGeneratorMixin from tm_devices.drivers.pi.pi_device import PIDevice -from tm_devices.helpers import print_with_timestamp, ReadOnlyCachedProperty +from tm_devices.helpers import print_with_timestamp + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 class SignalSource(PIDevice, SignalGeneratorMixin, ABC): @@ -21,7 +24,7 @@ def all_channel_names_list(self) -> Tuple[str, ...]: """Return a tuple containing all the channel names.""" return tuple(f"SOURCE{x+1}" for x in range(self.total_channels)) - @ReadOnlyCachedProperty + @cached_property def opt_string(self) -> str: r"""Return the string returned from the ``*OPT?`` query when the device was created.""" return self.ieee_cmds.opt() diff --git a/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_interactive.py b/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_interactive.py index 6ade8ad0..08a8fd86 100644 --- a/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_interactive.py +++ b/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_interactive.py @@ -11,7 +11,9 @@ from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi._ieee488_2_commands import LegacyTSPIEEE4882Commands from tm_devices.drivers.pi.source_measure_units.source_measure_unit import SourceMeasureUnit -from tm_devices.helpers import ReadOnlyCachedProperty + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -44,7 +46,7 @@ def ieee_cmds(self) -> LegacyTSPIEEE4882Commands: """Return an internal class containing methods for the standard IEEE 488.2 command set.""" return self._ieee_cmds # pyright: ignore[reportReturnType] - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return 1 diff --git a/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_standard.py b/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_standard.py index 26fe9c63..2e02bf10 100644 --- a/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_standard.py +++ b/src/tm_devices/drivers/pi/source_measure_units/smu2400/smu2400_standard.py @@ -9,7 +9,9 @@ from tm_devices.drivers.pi.pi_device import PIDevice from tm_devices.drivers.pi.signal_sources.signal_source import SignalSource from tm_devices.drivers.pi.source_measure_units.source_measure_unit import SourceMeasureUnit -from tm_devices.helpers import ReadOnlyCachedProperty + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 if TYPE_CHECKING: import os @@ -38,7 +40,7 @@ def ieee_cmds(self) -> IEEE4882Commands: # pyright: ignore [reportIncompatibleM """Return an internal class containing methods for the standard IEEE 488.2 command set.""" return self._ieee_cmds - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return 1 diff --git a/src/tm_devices/drivers/pi/source_measure_units/smu2600/smu2600.py b/src/tm_devices/drivers/pi/source_measure_units/smu2600/smu2600.py index 191faffa..be60b3fb 100644 --- a/src/tm_devices/drivers/pi/source_measure_units/smu2600/smu2600.py +++ b/src/tm_devices/drivers/pi/source_measure_units/smu2600/smu2600.py @@ -21,7 +21,9 @@ ) from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.source_measure_units.source_measure_unit import SourceMeasureUnit -from tm_devices.helpers import ReadOnlyCachedProperty + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -40,7 +42,7 @@ def all_channel_names_list(self) -> Tuple[str, ...]: """Return a tuple containing all the channel names.""" return tuple(string.ascii_lowercase[: self.total_channels]) # pylint: disable=invalid-slice-index - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" # Grab the total channel count based on whether the last digit in the model is even/odd diff --git a/src/tm_devices/drivers/pi/source_measure_units/smu6000/smu6000.py b/src/tm_devices/drivers/pi/source_measure_units/smu6000/smu6000.py index 9e9d5e4a..a67aa91e 100644 --- a/src/tm_devices/drivers/pi/source_measure_units/smu6000/smu6000.py +++ b/src/tm_devices/drivers/pi/source_measure_units/smu6000/smu6000.py @@ -11,7 +11,9 @@ from tm_devices.drivers.pi.pi_device import PIDevice from tm_devices.drivers.pi.signal_sources.signal_source import SignalSource from tm_devices.drivers.pi.source_measure_units.source_measure_unit import SourceMeasureUnit -from tm_devices.helpers import ReadOnlyCachedProperty + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 if TYPE_CHECKING: import os @@ -40,7 +42,7 @@ def ieee_cmds(self) -> IEEE4882Commands: # pyright: ignore [reportIncompatibleM """Return an internal class containing methods for the standard IEEE 488.2 command set.""" return self._ieee_cmds - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return 1 diff --git a/src/tm_devices/drivers/pi/systems_switches/ss3706a.py b/src/tm_devices/drivers/pi/systems_switches/ss3706a.py index 8bff5c70..58061f63 100644 --- a/src/tm_devices/drivers/pi/systems_switches/ss3706a.py +++ b/src/tm_devices/drivers/pi/systems_switches/ss3706a.py @@ -8,7 +8,10 @@ from tm_devices.commands import SS3706AMixin from tm_devices.drivers.device import family_base_class from tm_devices.drivers.pi.systems_switches.systems_switch import SystemsSwitch -from tm_devices.helpers import DeviceConfigEntry, ReadOnlyCachedProperty +from tm_devices.helpers import DeviceConfigEntry + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 @family_base_class @@ -41,7 +44,7 @@ def all_channel_names_list(self) -> Tuple[str, ...]: """Return a tuple containing all the channel names.""" return tuple(f"{x+1}" for x in range(self.total_channels)) - @ReadOnlyCachedProperty + @cached_property def total_channels(self) -> int: """Return the total number of channels (all types).""" return 576 diff --git a/src/tm_devices/helpers/functions.py b/src/tm_devices/helpers/functions.py index b96dee21..20a03395 100644 --- a/src/tm_devices/helpers/functions.py +++ b/src/tm_devices/helpers/functions.py @@ -44,9 +44,7 @@ #################################################################################################### # Private Constants #################################################################################################### -# NOTE: This regex will show as having a bunch of errors in the PyCharm IDE due to an -# open bug affecting f-strings in regex: https://youtrack.jetbrains.com/issue/PY-52140 -_SUPPORTED_MODEL_REGEX_MAPPING = re.compile( +__SUPPORTED_MODEL_REGEX_STRING = ( # AFGs rf"(?P<{SupportedModels.AFG3K.value}>^AFG3\d\d\d$)" rf"|(?P<{SupportedModels.AFG3KB.value}>^AFG3\d\d\dB$)" @@ -155,6 +153,10 @@ # SSs rf"|(?P<{SupportedModels.SS3706A.value}>^3706A$)" ) +# NOTE: This regex would show as having a bunch of errors in the PyCharm IDE due to an +# open bug affecting f-strings in regex: https://youtrack.jetbrains.com/issue/PY-52140. +# For this reason, the regex string itself is stored in a separate, private constant. +_SUPPORTED_MODEL_REGEX_MAPPING = re.compile(__SUPPORTED_MODEL_REGEX_STRING) #################################################################################################### diff --git a/src/tm_devices/helpers/read_only_cached_property.py b/src/tm_devices/helpers/read_only_cached_property.py index 4f11103c..f860156b 100644 --- a/src/tm_devices/helpers/read_only_cached_property.py +++ b/src/tm_devices/helpers/read_only_cached_property.py @@ -12,6 +12,11 @@ class ReadOnlyCachedProperty(cached_property[_T]): # pyright: ignore[reportRedeclaration] """An implementation of cached_property that is read-only. + Notes: + In order for the PyCharm IDE to properly provide auto-complete hints, this class must be + imported in the following way: + ``from tm_devices.helpers import ReadOnlyCachedProperty as cached_property``. + Examples: >>> from tm_devices.helpers import ReadOnlyCachedProperty >>> class ClassWithReadOnlyCachedProperty: @@ -42,6 +47,11 @@ def __delete__(self, instance: object) -> None: class ReadOnlyCachedProperty(cached_property): # pyright: ignore[reportMissingTypeArgument] """An implementation of cached_property that is read-only. + Notes: + In order for the PyCharm IDE to properly provide auto-complete hints, this class must be + imported in the following way: + ``from tm_devices.helpers import ReadOnlyCachedProperty as cached_property``. + Examples: >>> from tm_devices.helpers import ReadOnlyCachedProperty >>> class ClassWithReadOnlyCachedProperty: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index c66eb963..919bb88a 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -35,12 +35,14 @@ get_visa_backend, ping_address, print_with_timestamp, - ReadOnlyCachedProperty, sanitize_enum, SupportedModels, VALID_DEVICE_CONNECTION_TYPES, ) +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 + MODEL_SERIES_LIST = SupportedModels.list_values() @@ -471,7 +473,7 @@ class ClassWithReadOnlyCachedProperty: counter = 0 previous_values: ClassVar[List[int]] = [] - @ReadOnlyCachedProperty + @cached_property def c(self) -> int: self.counter += 1 while True: @@ -487,9 +489,11 @@ def c(self) -> int: assert val_1 == val_2 with pytest.raises(AttributeError): + # noinspection PyPropertyAccess instance.c = -1234 assert instance.c == val_1 assert instance.counter == 1 + # noinspection PyPropertyAccess del instance.c assert instance.c != val_1 assert instance.counter == 2 diff --git a/tests/test_rest_api_device.py b/tests/test_rest_api_device.py index b11572c5..6f5034c7 100644 --- a/tests/test_rest_api_device.py +++ b/tests/test_rest_api_device.py @@ -11,7 +11,9 @@ from mock_server import INDEX_RESPONSE, PORT from tm_devices.drivers.api.rest_api.rest_api_device import RESTAPIDevice, SupportedRequestTypes from tm_devices.drivers.device import family_base_class -from tm_devices.helpers import ReadOnlyCachedProperty + +# noinspection PyPep8Naming +from tm_devices.helpers import ReadOnlyCachedProperty as cached_property # noqa: N813 from tm_devices.helpers.constants_and_dataclasses import DeviceConfigEntry from tm_devices.helpers.enums import ConnectionTypes, DeviceTypes @@ -47,22 +49,22 @@ def _open(self) -> bool: def _reboot(self) -> None: """Perform the actual rebooting code.""" - @ReadOnlyCachedProperty + @cached_property def manufacturer(self) -> str: """Return the manufacturer of the device.""" return "foo" - @ReadOnlyCachedProperty + @cached_property def model(self) -> str: """Return the full model of the device.""" return "bar" - @ReadOnlyCachedProperty + @cached_property def serial(self) -> str: """Return the serial number of the device.""" return "123" - @ReadOnlyCachedProperty + @cached_property def sw_version(self) -> Version: """Return the software version of the device.""" return Version("0")