From d31adb655af60c62de464389707f5e8b951f45b5 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 16 Oct 2026 19:04:42 +0100 Subject: [PATCH 1/3] pvvalue --- .github/workflows/lint_and_test.yml | 4 ++-- pyproject.toml | 6 ++++-- src/genie_python/block_names.py | 3 +-- src/genie_python/genie.py | 5 +---- src/genie_python/genie_blockserver.py | 2 +- src/genie_python/genie_cachannel_wrapper.py | 5 ++--- src/genie_python/genie_dae.py | 3 ++- src/genie_python/genie_p4p_wrapper.py | 5 ++--- src/genie_python/genie_pv_connection_protocol.py | 5 ++--- src/genie_python/genie_waitfor.py | 2 +- src/genie_python/typings/CaChannel/CaChannel.pyi | 6 ++---- 11 files changed, 20 insertions(+), 26 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index d044e00d..ce2d032a 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -7,13 +7,13 @@ jobs: uses: ISISComputingGroup/reusable-workflows/.github/workflows/linters.yml@main with: compare-branch: origin/main - python-ver: '3.12' + python-ver: '3.14' unit-tests: runs-on: ${{ matrix.os }} strategy: matrix: os: ["ubuntu-latest", "windows-latest"] - version: ['3.11', '3.12', '3.13'] + version: ['3.12', '3.13', '3.14'] fail-fast: false steps: - uses: actions/checkout@v7 diff --git a/pyproject.toml b/pyproject.toml index 175e9184..a2fd9bb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ name = "genie_python" dynamic = ["version"] description = "Instrument control & scripting for the ISIS Neutron & Muon source" readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.12" license = "BSD-3-Clause" license-files = ["LICENSE"] @@ -22,9 +22,9 @@ maintainers = [ classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ @@ -50,6 +50,8 @@ dependencies = [ "pyright", # For setting windows job-object flags "pywin32;platform_system=='Windows'", + # PVValue definition + "ibex-non-ca-helpers@git+https://github.com/ISISComputingGroup/ibex-non-ca-helpers@pvvalue", ] [project.optional-dependencies] diff --git a/src/genie_python/block_names.py b/src/genie_python/block_names.py index 5f7f6a50..cf7a3d96 100644 --- a/src/genie_python/block_names.py +++ b/src/genie_python/block_names.py @@ -10,8 +10,7 @@ DELAY_BEFORE_RETRYING_BLOCK_NAMES_PV_ON_FAIL = 30.0 -if TYPE_CHECKING: - from genie_python.genie import PVValue +from ibex_non_ca_helpers.typing import PVValue class BlockNamesManager: diff --git a/src/genie_python/genie.py b/src/genie_python/genie.py index d3c3e406..ec6f83f1 100644 --- a/src/genie_python/genie.py +++ b/src/genie_python/genie.py @@ -13,6 +13,7 @@ import numpy as np import numpy.typing as npt +from ibex_non_ca_helpers.typing import PVValue from genie_python.genie_api_setup import __api as _genie_api @@ -43,10 +44,6 @@ ) from genie_python.version import VERSION # noqa E402 -PVBaseValue = bool | int | float | str | bytes -PVValue = PVBaseValue | list[PVBaseValue] | npt.NDArray | None # pyright: ignore -# because we don't want to make PVValue generic - class _CgetReturn(TypedDict): name: str diff --git a/src/genie_python/genie_blockserver.py b/src/genie_python/genie_blockserver.py index 31fc3aab..63f32bd3 100644 --- a/src/genie_python/genie_blockserver.py +++ b/src/genie_python/genie_blockserver.py @@ -5,9 +5,9 @@ from typing import TYPE_CHECKING, Any, Callable, ParamSpec, TypeVar from genie_python.utilities import compress_and_hex, dehex_decompress_and_dejson +from ibex_non_ca_helpers.typing import PVValue if TYPE_CHECKING: - from genie_python.genie import PVValue from genie_python.genie_epics_api import API Param = ParamSpec("Param") diff --git a/src/genie_python/genie_cachannel_wrapper.py b/src/genie_python/genie_cachannel_wrapper.py index 1953adbf..a36ed89e 100644 --- a/src/genie_python/genie_cachannel_wrapper.py +++ b/src/genie_python/genie_cachannel_wrapper.py @@ -9,7 +9,7 @@ from builtins import object from collections.abc import Callable from threading import Event -from typing import TYPE_CHECKING, Optional, Tuple, TypeVar +from typing import Optional, Tuple, TypeVar from CaChannel import CaChannel, CaChannelException, ca @@ -31,8 +31,7 @@ dbf_type_to_text, ) -if TYPE_CHECKING: - from genie_python.genie import PVValue +from ibex_non_ca_helpers.typing import PVValue from .channel_access_exceptions import ( InvalidEnumStringException, diff --git a/src/genie_python/genie_dae.py b/src/genie_python/genie_dae.py index e2279431..e363c488 100644 --- a/src/genie_python/genie_dae.py +++ b/src/genie_python/genie_dae.py @@ -34,9 +34,10 @@ require_runstate, waveform_to_string, ) +from ibex_non_ca_helpers.typing import PVValue if TYPE_CHECKING: - from genie_python.genie import PVValue, _GetspectrumReturn + from genie_python.genie import _GetspectrumReturn from genie_python.genie_epics_api import API ## for beginrun etc. there exists both the PV specified here and also a PV with diff --git a/src/genie_python/genie_p4p_wrapper.py b/src/genie_python/genie_p4p_wrapper.py index 4b6d3b9d..68924241 100644 --- a/src/genie_python/genie_p4p_wrapper.py +++ b/src/genie_python/genie_p4p_wrapper.py @@ -7,7 +7,7 @@ import threading from builtins import object from collections.abc import Callable -from typing import TYPE_CHECKING, Optional, Tuple +from typing import Optional, Tuple from p4p import Value from p4p.client.thread import Context, Subscription @@ -15,8 +15,7 @@ from .channel_access_exceptions import WriteAccessException from .utilities import waveform_to_string -if TYPE_CHECKING: - from genie_python.genie import PVValue +from ibex_non_ca_helpers.typing import PVValue TIMEOUT = 15 # Default timeout for PV set/get EXIST_TIMEOUT = 3 # Separate smaller timeout for pv_exists() and searchw() operations diff --git a/src/genie_python/genie_pv_connection_protocol.py b/src/genie_python/genie_pv_connection_protocol.py index b28a6a03..9b144dfe 100644 --- a/src/genie_python/genie_pv_connection_protocol.py +++ b/src/genie_python/genie_pv_connection_protocol.py @@ -1,8 +1,7 @@ from collections.abc import Callable -from typing import TYPE_CHECKING, Optional, Protocol, Tuple, runtime_checkable +from typing import Optional, Protocol, Tuple, runtime_checkable -if TYPE_CHECKING: - from genie_python.genie import PVValue +from ibex_non_ca_helpers import PVValue @runtime_checkable diff --git a/src/genie_python/genie_waitfor.py b/src/genie_python/genie_waitfor.py index 76c1bfde..9c3e8f85 100644 --- a/src/genie_python/genie_waitfor.py +++ b/src/genie_python/genie_waitfor.py @@ -13,7 +13,7 @@ from genie_python.utilities import check_break, get_time_delta if TYPE_CHECKING: - from genie_python.genie import PVValue + from ibex_non_ca_helpers import PVValue from genie_python.genie_epics_api import API NUMERIC_TYPE = (float, int) diff --git a/src/genie_python/typings/CaChannel/CaChannel.pyi b/src/genie_python/typings/CaChannel/CaChannel.pyi index ee43e55e..e4f75805 100644 --- a/src/genie_python/typings/CaChannel/CaChannel.pyi +++ b/src/genie_python/typings/CaChannel/CaChannel.pyi @@ -2,9 +2,10 @@ This type stub file was generated by pyright. """ -from typing import Any, Callable, Tuple, TypeAlias, TypeVar +from typing import Any, Callable, Tuple, TypeVar from numpy import NDArray +from ibex_non_ca_helpers.typing import PVValue __all__ = ["CaChannelException", "CaChannel"] T = TypeVar("T") @@ -14,9 +15,6 @@ class CaChannelException(Exception): def __int__(self) -> int: ... def __str__(self) -> str: ... -PVBaseValue: TypeAlias = bool | int | float | str -PVValue: TypeAlias = PVBaseValue | list[PVBaseValue] | NDArray | None - class CaChannel: """CaChannel: A Python class with identical API as of caPython/CaChannel. From 35cca5848a627913887ea87b3640309eafe0b51d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 19 Oct 2026 13:20:33 +0100 Subject: [PATCH 2/3] ruff --- doc/conf.py | 1 - ruff.toml | 11 ++ src/genie_python/__init__.py | 2 +- src/genie_python/block_names.py | 13 +- src/genie_python/channel_access_exceptions.py | 10 +- src/genie_python/genie.py | 137 +++++++------ src/genie_python/genie_advanced.py | 32 ++- src/genie_python/genie_alerts.py | 42 ++-- src/genie_python/genie_api_setup.py | 25 ++- src/genie_python/genie_blockserver.py | 24 ++- src/genie_python/genie_cachannel_wrapper.py | 33 ++-- src/genie_python/genie_change_cache.py | 45 ++--- src/genie_python/genie_dae.py | 184 ++++++++---------- src/genie_python/genie_epics_api.py | 122 +++++------- src/genie_python/genie_experimental_data.py | 8 +- src/genie_python/genie_logging.py | 61 +++--- src/genie_python/genie_p4p_wrapper.py | 27 ++- src/genie_python/genie_plot.py | 27 ++- .../genie_pre_post_cmd_manager.py | 3 +- .../genie_pv_connection_protocol.py | 6 +- src/genie_python/genie_script_checker.py | 84 ++++---- src/genie_python/genie_script_generator.py | 24 +-- src/genie_python/genie_simulate.py | 32 ++- src/genie_python/genie_simulate_impl.py | 171 ++++++++-------- src/genie_python/genie_startup.py | 2 - src/genie_python/genie_toggle_settings.py | 6 +- src/genie_python/genie_wait_for_move.py | 31 +-- src/genie_python/genie_waitfor.py | 116 +++++------ .../ibex_websocket_backend.py | 9 +- src/genie_python/mysql_abstraction_layer.py | 38 ++-- .../testing_utils/script_checker.py | 4 +- .../typings/CaChannel/CaChannel.pyi | 43 +--- src/genie_python/typings/CaChannel/ca.pyi | 12 +- src/genie_python/utilities.py | 42 ++-- tests/py3_test_genie_experimental_data.py | 127 ++++++++---- tests/test_block_names.py | 6 +- tests/test_genie.py | 23 +-- tests/test_genie_alerts.py | 1 - tests/test_genie_api_setup.py | 36 ++-- tests/test_genie_blockserver_tests.py | 1 - tests/test_genie_change_cache.py | 1 - tests/test_genie_dae.py | 43 ++-- tests/test_genie_epics_api.py | 75 +++---- tests/test_genie_wait_for_move.py | 3 +- tests/test_genie_waitfor.py | 49 +++-- tests/test_matplotlib_backend.py | 4 +- tests/test_mysql_abstraction_layer.py | 3 +- tests/test_script_checker.py | 23 ++- tests/test_simulation.py | 1 - tests/test_utilities.py | 11 +- tests/test_utils_with_unicode_literals.py | 5 +- 51 files changed, 863 insertions(+), 976 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c41e7975..8a213182 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # genie_python documentation build configuration file, created by # sphinx-quickstart on Tue Aug 19 08:23:25 2014. diff --git a/ruff.toml b/ruff.toml index 44555f13..38eab18b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -24,6 +24,9 @@ ignore = [ "D407", # Missing dashed underline after section ("{name}") "N999", # Ignore this because the repo itself would need to be renamed "ANN401", # explicit ANY type is allowed + "BLE001", # Common pattern throughout this package + "B017", # Common pattern throughout this package + "S110", # Common pattern throughout this package ] [lint.per-file-ignores] "tests/*" = [ @@ -34,6 +37,14 @@ ignore = [ "E501", "ANN", ] +"*.pyi" = [ # auto-generated type-stubs, don't lint these + "ANN", + "N", + "D", + "E", + "F", + "PYI" +] [lint.pydocstyle] # Use Google-style docstrings. diff --git a/src/genie_python/__init__.py b/src/genie_python/__init__.py index c4521327..c728e443 100644 --- a/src/genie_python/__init__.py +++ b/src/genie_python/__init__.py @@ -1 +1 @@ -from .genie_epics_api import BLOCK_NAMES +from .genie_epics_api import BLOCK_NAMES as BLOCK_NAMES diff --git a/src/genie_python/block_names.py b/src/genie_python/block_names.py index cf7a3d96..fb77f5bb 100644 --- a/src/genie_python/block_names.py +++ b/src/genie_python/block_names.py @@ -1,7 +1,8 @@ import zlib from keyword import iskeyword from threading import RLock, Timer -from typing import TYPE_CHECKING, Optional + +from ibex_non_ca_helpers.typing import PVValue from .channel_access_exceptions import UnableToConnectToPVException from .genie_blockserver import BLOCK_SERVER_PREFIX, PV_BLOCK_NAMES @@ -10,8 +11,6 @@ DELAY_BEFORE_RETRYING_BLOCK_NAMES_PV_ON_FAIL = 30.0 -from ibex_non_ca_helpers.typing import PVValue - class BlockNamesManager: """ @@ -44,7 +43,7 @@ def update_prefix(self, pv_prefix: str) -> None: """ with self.pv_name_lock: - new_name = "{}{}{}".format(pv_prefix, BLOCK_SERVER_PREFIX, PV_BLOCK_NAMES) + new_name = f"{pv_prefix}{BLOCK_SERVER_PREFIX}{PV_BLOCK_NAMES}" if new_name != self._pv_name: self._pv_name = new_name if self._timer is None: @@ -83,7 +82,7 @@ def _add_monitor(self) -> None: self._timer.daemon = True self._timer.start() - def _update_block_names(self, value: "PVValue", _: Optional[str], _1: Optional[str]) -> None: + def _update_block_names(self, value: "PVValue", _: str | None, _1: str | None) -> None: """ Update the block names from a pv Args: @@ -104,7 +103,7 @@ def _update_block_names(self, value: "PVValue", _: Optional[str], _1: Optional[s for name in block_names: attribute_name = name if iskeyword(attribute_name): - attribute_name = "{}__".format(attribute_name) + attribute_name = f"{attribute_name}__" setattr(self._block_names, attribute_name, name) except (zlib.error, ValueError, TypeError): # if we can not decode the blocks then just pass @@ -128,7 +127,7 @@ def __getattr__(self, attr: str) -> str: # letter, e.g. ipython console calls __wrapper__ to check it is not a wrapper if attr.startswith("_"): raise AttributeError() - print("WARNING: Block name {} not found, it may not exist".format(attr)) + print(f"WARNING: Block name {attr} not found, it may not exist") if attr.endswith("__") and iskeyword(attr[:-2]): return str(attr[:-2]) return attr diff --git a/src/genie_python/channel_access_exceptions.py b/src/genie_python/channel_access_exceptions.py index 585942d0..830f5473 100644 --- a/src/genie_python/channel_access_exceptions.py +++ b/src/genie_python/channel_access_exceptions.py @@ -10,9 +10,7 @@ class UnableToConnectToPVException(IOError): # noqa N818 Historic name """ def __init__(self, pv_name: str, err: str) -> None: - super(UnableToConnectToPVException, self).__init__( - f"Unable to connect to PV {pv_name}: {err}" - ) + super().__init__(f"Unable to connect to PV {pv_name}: {err}") class InvalidEnumStringException(KeyError): # noqa N818 Historic name @@ -21,7 +19,7 @@ class InvalidEnumStringException(KeyError): # noqa N818 Historic name """ def __init__(self, pv_name: str, valid_states: str) -> None: - super(InvalidEnumStringException, self).__init__( + super().__init__( f"Invalid string value entered for {pv_name}. Valid strings are {valid_states}" ) @@ -32,7 +30,7 @@ class ReadAccessException(IOError): # noqa N818 Historic name """ def __init__(self, pv_name: str) -> None: - super(ReadAccessException, self).__init__(f"Read access denied for PV {pv_name}") + super().__init__(f"Read access denied for PV {pv_name}") class WriteAccessException(IOError): # noqa N818 Historic name @@ -41,4 +39,4 @@ class WriteAccessException(IOError): # noqa N818 Historic name """ def __init__(self, pv_name: str) -> None: - super(WriteAccessException, self).__init__(f"Write access denied for PV {pv_name}") + super().__init__(f"Write access denied for PV {pv_name}") diff --git a/src/genie_python/genie.py b/src/genie_python/genie.py index ec6f83f1..0106f6d5 100644 --- a/src/genie_python/genie.py +++ b/src/genie_python/genie.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function - import datetime import importlib import importlib.util @@ -7,9 +5,9 @@ import re import sys import types -from builtins import FileNotFoundError, str -from io import open -from typing import Any, Callable, TypedDict +from builtins import FileNotFoundError +from collections.abc import Callable +from typing import Any, TypedDict import numpy as np import numpy.typing as npt @@ -20,7 +18,7 @@ os.environ["FROM_IBEX"] = str(False) # for user import this functionality so they can do g.adv and g.sim -import genie_python.genie_advanced as adv # noqa F401 +import genie_python.genie_advanced as adv import genie_python.genie_alerts as alerts # noqa F401 import genie_python.genie_simulate as sim # noqa F401 import genie_python.genie_toggle_settings as toggle # noqa F401 @@ -30,19 +28,19 @@ get_user_script_dir, helparglist, log_command_and_handle_exception, - my_pv_prefix, # noqa F401 + my_pv_prefix, set_user_script_dir, usercommand, ) -from genie_python.genie_script_checker import ScriptChecker # noqa E402 -from genie_python.genie_toggle_settings import ToggleSettings # noqa E402 -from genie_python.utilities import ( # noqa E402 +from genie_python.genie_script_checker import ScriptChecker +from genie_python.genie_toggle_settings import ToggleSettings +from genie_python.utilities import ( EnvironmentDetails, check_lowlimit_against_highlimit, get_correct_filepath_existing, get_correct_path, ) -from genie_python.version import VERSION # noqa E402 +from genie_python.version import VERSION class _CgetReturn(TypedDict): @@ -120,9 +118,10 @@ class _GetbeamlineparsReturn(TypedDict): if not (MIN_SUPPORTED_PYTHON_VERSION <= sys.version_info[0:3] <= MAX_SUPPORTED_PYTHON_VERSION): message = ( "WARNING: genie_python only supports " - "python versions {0[0]}.{0[1]}.{0[2]} to {1[0]}.{1[1]}.{1[2]}, you are running {2}".format( - MIN_SUPPORTED_PYTHON_VERSION, MAX_SUPPORTED_PYTHON_VERSION, sys.version - ) + f"python versions " + f"{MIN_SUPPORTED_PYTHON_VERSION[0]}.{MIN_SUPPORTED_PYTHON_VERSION[1]}.{MIN_SUPPORTED_PYTHON_VERSION[2]} to " # ruff: ignore[E501] + f"{MAX_SUPPORTED_PYTHON_VERSION[0]}.{MAX_SUPPORTED_PYTHON_VERSION[1]}.{MAX_SUPPORTED_PYTHON_VERSION[2]}, " # ruff: ignore[E501] + f"you are running {sys.version}" ) print(message, file=sys.stderr) @@ -241,7 +240,7 @@ def cset( # See if single block name was entered, i.e. cset("block1", runcontrol=True) if len(args) > 0: if len(args) > 2: - raise Exception( + raise ValueError( "Too many arguments, please type: help(g.cset) for more information on the syntax" ) blocks = [str(args[0])] @@ -250,26 +249,27 @@ def cset( # Check for specifying blocks via the cset(block=value) syntax blocks, values = zip(*kwargs.items()) else: - raise Exception( + raise ValueError( "Incorrect syntax, please type: help(g.cset) for more information on the syntax" ) for block in blocks: if not _genie_api.block_exists(block): - raise Exception('No block with the name "{}" exists'.format(block)) + raise ValueError(f'No block with the name "{block}" exists') if wait and runcontrol is not None: - raise Exception("Cannot enable or disable runcontrol at the same time as setting a wait") + raise ValueError("Cannot enable or disable runcontrol at the same time as setting a wait") if wait and values[0] is None: - raise Exception("Cannot wait as no setpoint specified. Please type: help(g.cset) for help") + raise ValueError("Cannot wait as no setpoint specified. Please type: help(g.cset) for help") # Warn if highlimit and lowlimit are round the incorrect way check_lowlimit_against_highlimit(lowlimit, highlimit) - if len(blocks) > 1: + if len(blocks) > 1 and not all( + argument is None for argument in [runcontrol, lowlimit, highlimit, wait] + ): # Setting multiple blocks, so other settings not allowed - if not all(argument is None for argument in [runcontrol, lowlimit, highlimit, wait]): - raise Exception("Runcontrol and wait can only be changed for one block at a time") + raise ValueError("Runcontrol and wait can only be changed for one block at a time") for block, value in list(zip(blocks, values)): # If there are multiple blocks then runcontrol etc. @@ -320,8 +320,8 @@ def cget(block: str) -> _CgetReturn: def _log_alarmed_block(block_name: str, alarm_state: PVValue) -> None: - _genie_api.logger.log_info_msg("BLOCK {} IN {} ALARM".format(block_name, alarm_state)) - print("Block {} is in alarm: {}".format(block_name, alarm_state), file=sys.stdout) + _genie_api.logger.log_info_msg(f"BLOCK {block_name} IN {alarm_state} ALARM") + print(f"Block {block_name} is in alarm: {alarm_state}", file=sys.stdout) def _warn_if_block_alarm(block: str) -> None: @@ -375,7 +375,7 @@ def cshow(block: str | None = None) -> None: >>> cshow("block1") """ blocks_to_get = [block] if block is not None else _genie_api.get_block_names() - for block in blocks_to_get: + for block in blocks_to_get: # ruff: ignore[PLR1704] _print_from_cget(_genie_api.get_block_data(block, True)) @@ -465,18 +465,18 @@ def waitfor( if block is None: # Search through the params to see if there is a block there blks = _genie_api.get_block_names() - for k in pars: + for k, v in pars.items(): if k in blks: if block is not None: - raise Exception("Can set waitfor for only one block at a time") + raise ValueError("Can set waitfor for only one block at a time") block = k - value = pars[k] + value = v else: - raise ValueError("Block named '{}' did not exist.".format(k)) + raise ValueError(f"Block named '{k}' did not exist.") # Check that wait_for object exists if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor - try calling set_instrument first") + raise ValueError("Cannot execute waitfor - try calling set_instrument first") # Warn if highlimit and lowlimit are round correct way check_lowlimit_against_highlimit(lowlimit, highlimit) # Start_waiting checks the block exists @@ -549,7 +549,7 @@ def waitfor_block( """ if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor_block - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_block - try calling set_instrument first") # Warn if highlimit and lowlimit are round correct way check_lowlimit_against_highlimit(lowlimit, highlimit) _genie_api.waitfor.start_waiting( @@ -638,7 +638,7 @@ def waitfor_frames(frames: int | None = None, quiet: bool = False) -> None: raise ValueError("Cannot execute waitfor_frames - frames parameter cannot be negative") if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor_frames - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_frames - try calling set_instrument first") _genie_api.waitfor.start_waiting(frames=frames, quiet=quiet) @@ -668,7 +668,7 @@ def waitfor_raw_frames(raw_frames: int | None = None, quiet: bool = False) -> No ) if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor_raw_frames - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_raw_frames - try calling set_instrument first") _genie_api.waitfor.start_waiting(raw_frames=raw_frames, quiet=quiet) @@ -689,7 +689,7 @@ def waitfor_uamps(uamps: float, quiet: bool = False) -> None: """ if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor_uamps - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_uamps - try calling set_instrument first") _genie_api.waitfor.start_waiting(uamps=uamps, quiet=quiet) @@ -717,7 +717,7 @@ def waitfor_mevents(mevents: float | None = None, quiet: bool = False) -> None: raise ValueError("Cannot execute waitfor_mevents - mevents parameter cannot be negative") if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor_mevents - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_mevents - try calling set_instrument first") _genie_api.waitfor.start_waiting(mevents=mevents, quiet=quiet) @@ -748,7 +748,7 @@ def waitfor_runstate( # Check that wait_for object exists if _genie_api.waitfor is None: # pyright: ignore # pyright doesn't recognise that waitfor can be None - raise Exception("Cannot execute waitfor_runstate - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_runstate - try calling set_instrument first") _genie_api.waitfor.wait_for_runstate(state, maxwaitsecs, onexit, quiet) @@ -784,28 +784,22 @@ def waitfor_move(*blocks: str, **kwargs: int | None) -> None: """ # Sort out the parameters # Standard parameters - if "start_timeout" in kwargs: - start_timeout = kwargs["start_timeout"] - else: - start_timeout = 2 - if "move_timeout" in kwargs: - move_timeout = kwargs["move_timeout"] - else: - move_timeout = None + start_timeout = kwargs.get("start_timeout", 2) + move_timeout = kwargs.get("move_timeout", None) # Check that wait_for_move object exists if _genie_api.wait_for_move is None: - raise Exception("Cannot execute waitfor_move - try calling set_instrument first") + raise ValueError("Cannot execute waitfor_move - try calling set_instrument first") if len(blocks) > 0: # Specified blocks waitfor_move - move_blocks = list() + move_blocks = [] # Check blocks exist for b in blocks: if _genie_api.block_exists(b): move_blocks.append(b) else: - print("Block %s does not exist, so ignoring it" % b) + print(f"Block {b} does not exist, so ignoring it") _genie_api.wait_for_move.wait_specific(move_blocks, start_timeout, move_timeout) else: # Standard waitfor_move @@ -817,7 +811,7 @@ def waitfor_move(*blocks: str, **kwargs: int | None) -> None: @log_command_and_handle_exception def get_pv( name: str, to_string: bool = False, is_local: bool = False, use_numpy: bool = False -) -> Any: # noqa: ANN401 +) -> Any: """ Get the value for the specified PV. @@ -1464,7 +1458,7 @@ def _get_correct_globals() -> dict[str, int]: """ import inspect - globs = dict() + globs = {} for i in inspect.stack(): if "cshow" in i[0].f_globals: @@ -1487,7 +1481,7 @@ def load_script(name: str, check_script: bool = True, warnings_as_error: bool = globs = _get_correct_globals() - _genie_api.logger.log_info_msg("Trying to resolve full filename for %s" % name) + _genie_api.logger.log_info_msg(f"Trying to resolve full filename for {name}") try: try: @@ -1496,9 +1490,9 @@ def load_script(name: str, check_script: bool = True, warnings_as_error: bool = # Try with default script directory prepended full_name = get_correct_filepath_existing(os.path.join(get_user_script_dir(), name)) except Exception: - raise Exception("Script file was not found (%s)" % get_correct_path(name)) + raise OSError(f"Script file was not found ({get_correct_path(name)})") - _genie_api.logger.log_info_msg("Trying to load script from: %s" % full_name) + _genie_api.logger.log_info_msg(f"Trying to load script from: {full_name}") directory, filename = os.path.split(os.path.abspath(full_name)) @@ -1518,7 +1512,7 @@ def load_script(name: str, check_script: bool = True, warnings_as_error: bool = combined = "script not loaded as errors found in script: " for e in errs: combined += "\n\t" + e - raise Exception(combined) + raise ValueError(combined) mod = __load_module(filename[0:-3], directory) # Safe to load @@ -1526,17 +1520,15 @@ def load_script(name: str, check_script: bool = True, warnings_as_error: bool = funcs = [] file_path = os.path.join(directory, filename) with open(file_path) as f: - for line in f.readlines(): + for line in f: m = re.match(r"^def\s+(.+)\(", line) if m is not None: funcs.append(m.group(1)) scripts = [] for att in dir(mod): - if isinstance(mod.__dict__.get(att), types.FunctionType): - # Check function comes from script file not an import - if att in funcs: - scripts.append(att) + if isinstance(mod.__dict__.get(att), types.FunctionType) and att in funcs: + scripts.append(att) if len(scripts) > 0: # This is where the script file is actually loaded @@ -1546,24 +1538,25 @@ def load_script(name: str, check_script: bool = True, warnings_as_error: bool = # dont_inherit=True so that __future__ # statements in this file are not propagated to user scripts code = compile(file_contents, file_path, "exec", dont_inherit=True) - exec(code, globs) + exec(code, globs) # ruff: ignore[S102] msg = "Loaded the following script(s): " for script in scripts: msg += script + ", " print(msg[0:-2]) - print("From: %s" % file_path) + print(f"From: {file_path}") print( - "File last modified: %s" - % datetime.datetime.fromtimestamp(os.path.getmtime(file_path)).strftime( - "%Y-%m-%d %H:%M:%S" + "File last modified: {}".format( + datetime.datetime.fromtimestamp(os.path.getmtime(file_path)).strftime( + "%Y-%m-%d %H:%M:%S" + ) ) ) else: - raise Exception( - "No scripts found in {} - please ensure all your code is" - " contained within functions.".format(file_path) + raise ValueError( + f"No scripts found in {file_path} - please ensure all your code is" + " contained within functions." ) except Exception as e: if directory in sys.path: @@ -1722,21 +1715,21 @@ def get_table_file(path: str) -> str: wiring = get_table_file(wiring) except Exception: errors.append( - "Could not find wiring table. Did you type the file name correctly? %s" % wiring + f"Could not find wiring table. Did you type the file name correctly? {wiring}" ) if spectra is not None: try: spectra = get_table_file(spectra) except Exception: errors.append( - "Could not find spectra table. Did you type the file name correctly? %s" % spectra + f"Could not find spectra table. Did you type the file name correctly? {spectra}" ) if detector is not None: try: detector = get_table_file(detector) except Exception: errors.append( - "Could not find detector table. Did you type the file name correctly? %s" % detector + f"Could not find detector table. Did you type the file name correctly? {detector}" ) assert _genie_api.dae is not None @@ -2548,11 +2541,11 @@ def load(name: str) -> None: name (string): The script the user is trying to load. """ print( - 'This function does not load a script; you probably wanted g.load_script("{0}").' + f"This function does not load a script; you probably wanted " + f'g.load_script("{get_correct_path(name)}"). ' "If you wanted numpy load please call it directly with import numpy as numpy; " - 'numpy.load("{0}")'.format(get_correct_path(name)) + f'numpy.load("{get_correct_path(name)}")' ) - return @usercommand diff --git a/src/genie_python/genie_advanced.py b/src/genie_python/genie_advanced.py index f436761c..84a1cc84 100644 --- a/src/genie_python/genie_advanced.py +++ b/src/genie_python/genie_advanced.py @@ -5,9 +5,10 @@ """ import contextlib +from collections.abc import Iterator from datetime import UTC, datetime, timedelta from time import sleep -from typing import Any, Iterator, Protocol, TypedDict +from typing import Any, Protocol, TypedDict import numpy as np import numpy.typing as npt @@ -75,8 +76,8 @@ def motor_in_set_mode(pv_name: str) -> Iterator[None]: __api.set_pv_value(pv_name + ".SET", 1, True) offset_freeze_switch = __api.get_pv_value(pv_name + ".FOFF") __api.set_pv_value(pv_name + ".FOFF", "Frozen", True) - except IOError as ex: - raise ValueError("Can not set motor set and frozen offset mode: {}".format(ex)) + except OSError as ex: + raise ValueError(f"Can not set motor set and frozen offset mode: {ex}") try: yield @@ -84,13 +85,13 @@ def motor_in_set_mode(pv_name: str) -> Iterator[None]: try: __api.set_pv_value(pv_name + ".SET", 0, True) __api.set_pv_value(pv_name + ".FOFF", offset_freeze_switch, True) - except IOError as ex: - raise ValueError("Can not reset motor set and frozen offset mode: {}".format(ex)) + except OSError as ex: + raise ValueError(f"Can not reset motor set and frozen offset mode: {ex}") @usercommand @helparglist("name str, value flt") -def redefine_motor_position(name: str, value: float | int) -> None: +def redefine_motor_position(name: str, value: float) -> None: """ Change the motor Move Abs value. @@ -111,8 +112,8 @@ def redefine_motor_position(name: str, value: float | int) -> None: try: __api.set_pv_value(pv_name + ".VAL", value, True) - except IOError as ex: - raise ValueError("Can not set new motor position: {}".format(ex)) + except OSError as ex: + raise ValueError(f"Can not set new motor position: {ex}") @usercommand @@ -150,9 +151,7 @@ def pv_exists(pv: str, is_local: bool = False) -> bool: @usercommand @helparglist("pv str, value[, maxwait]") @log_command_and_handle_exception -def wait_for_pv( - pv: str, value: bool | int | float | str | None, maxwait: int | None = None -) -> None: +def wait_for_pv(pv: str, value: bool | float | str | None, maxwait: int | None = None) -> None: """ Wait until a PV has reached a given value. @@ -166,9 +165,8 @@ def wait_for_pv( curr_value = __api.get_pv_value(pv) if curr_value == value: break - if maxwait is not None: - if timedelta(seconds=maxwait) < datetime.now(UTC) - start_time: - break + if maxwait is not None and timedelta(seconds=maxwait) < datetime.now(UTC) - start_time: + break sleep(DELAY_IN_WAIT_FOR_SLEEP_LOOP) check_break(2) @@ -392,12 +390,10 @@ def get_exp_data( """ try: if __api.exp_data is None: - raise EnvironmentError("Could not connect to instrument database") + raise OSError("Could not connect to instrument database") return __api.exp_data.get_exp_data(rb, user, role, verbose) except AttributeError as e: - raise NotImplementedError( - "get_exp_data is not implemented for this genie type. {}".format(e) - ) + raise NotImplementedError(f"get_exp_data is not implemented for this genie type. {e}") @usercommand diff --git a/src/genie_python/genie_alerts.py b/src/genie_python/genie_alerts.py index ddd64b7f..063fb5a3 100644 --- a/src/genie_python/genie_alerts.py +++ b/src/genie_python/genie_alerts.py @@ -48,7 +48,7 @@ def set_range( """ if not __api.block_exists(block): - raise Exception('No block with the name "{}" exists'.format(block)) + raise ValueError(f'No block with the name "{block}" exists') __api.set_pv_value(_ALERT_LOW.format(block), lowlimit, wait=False, is_local=True) __api.set_pv_value(_ALERT_HIGH.format(block), highlimit, wait=False, is_local=True) @@ -73,7 +73,7 @@ def enable(block: str, set_enabled: bool = True) -> None: """ if not __api.block_exists(block): - raise Exception('No block with the name "{}" exists'.format(block)) + raise ValueError(f'No block with the name "{block}" exists') __api.set_pv_value(_ALERT_ENABLE.format(block), set_enabled, wait=False, is_local=True) @@ -108,7 +108,7 @@ def set_sms(numbers: list[str] | str) -> None: else: __api.set_pv_value(_ALERT_MOBILES, numbers, wait=False, is_local=True) except Exception as e: - print("Unable to set alert SMS numbers: {}".format(e)) + print(f"Unable to set alert SMS numbers: {e}") ## no log decorator so email addresses not sent to log file @@ -128,7 +128,7 @@ def set_email(emails: list[str] | str) -> None: else: __api.set_pv_value(_ALERT_EMAILS, emails, wait=False, is_local=True) except Exception as e: - print("Unable to set alert email addresses: {}".format(e)) + print(f"Unable to set alert email addresses: {e}") def _print_block(block: str, only_if_enabled: bool = False) -> None: @@ -137,27 +137,23 @@ def _print_block(block: str, only_if_enabled: bool = False) -> None: ) if only_if_enabled and not enabled: return - print("Block: {}".format(block)) - print(" Enabled: {}".format(enabled)) + print(f"Block: {block}") + print(f" Enabled: {enabled}") print( - " Low: {}".format( - __api.get_pv_value(_ALERT_LOW.format(block), to_string=False, is_local=True) - ) + f" Low: " + f"{__api.get_pv_value(_ALERT_LOW.format(block), to_string=False, is_local=True)}" ) print( - " High: {}".format( - __api.get_pv_value(_ALERT_HIGH.format(block), to_string=False, is_local=True) - ) + f" High: " + f"{__api.get_pv_value(_ALERT_HIGH.format(block), to_string=False, is_local=True)}" ) print( - " Delay In: {}".format( - __api.get_pv_value(_ALERT_DELAY_IN.format(block), to_string=False, is_local=True) - ) + f" Delay In: " + f"{__api.get_pv_value(_ALERT_DELAY_IN.format(block), to_string=False, is_local=True)}" ) print( - " Delay Out: {}".format( - __api.get_pv_value(_ALERT_DELAY_OUT.format(block), to_string=False, is_local=True) - ) + f" Delay Out: " + f"{__api.get_pv_value(_ALERT_DELAY_OUT.format(block), to_string=False, is_local=True)}" ) @@ -172,22 +168,22 @@ def status(block: str | None = None, all: bool = False) -> None: block (string): The block to print information about all (bool): If True information about all the blocks is printed """ - print("Emails: {}".format(__api.get_pv_value(_ALERT_EMAILS, to_string=False, is_local=True))) - print("Mobiles: {}".format(__api.get_pv_value(_ALERT_MOBILES, to_string=True, is_local=True))) + print(f"Emails: {__api.get_pv_value(_ALERT_EMAILS, to_string=False, is_local=True)}") + print(f"Mobiles: {__api.get_pv_value(_ALERT_MOBILES, to_string=True, is_local=True)}") if block is not None: if not __api.block_exists(block): - raise Exception('No block with the name "{}" exists'.format(block)) + raise ValueError(f'No block with the name "{block}" exists') _print_block(block) else: blocks = __api.get_block_names() - for block in blocks: + for block in blocks: # ruff: ignore[PLR1704] _print_block(block, not all) # used as part of tests, returns a dictionary of details def _dump(block: str) -> dict[str, str]: if not __api.block_exists(block): - raise Exception('No block with the name "{}" exists'.format(block)) + raise ValueError(f'No block with the name "{block}" exists') res = {} res["emails"] = str(__api.get_pv_value(_ALERT_EMAILS, to_string=True, is_local=True)).split(";") res["mobiles"] = str(__api.get_pv_value(_ALERT_MOBILES, to_string=True, is_local=True)).split( diff --git a/src/genie_python/genie_api_setup.py b/src/genie_python/genie_api_setup.py index 9ec57ba5..cacd7b6f 100644 --- a/src/genie_python/genie_api_setup.py +++ b/src/genie_python/genie_api_setup.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import ctypes import functools import glob @@ -9,7 +7,8 @@ import sys import time import traceback -from typing import Any, Callable, ParamSpec, TypeVar +from collections.abc import Callable +from typing import Any, ParamSpec, TypeVar import IPython from decorator import getfullargspec @@ -38,7 +37,7 @@ try: # If __api does not exist or is None then we need to create it. if __api is None: # noqa: F821 __api currently gets added to globals which is bad. - raise Exception("API does not exist") + raise ValueError("API does not exist") except Exception: # This should only get called the first time genie is imported my_pv_prefix = None @@ -72,12 +71,12 @@ def set_user_script_dir(*directory: str | list[str]) -> None: directory_name = get_correct_filepath_existing(directory_name) break except OSError: - directory_name, base_name = os.path.split(directory_name.strip(r"\\/")) + directory_name, base_name = os.path.split(directory_name.strip(r"\\/")) # ruff: ignore[B005] dirs_to_create.append(base_name) # Got to a single directory which does not exist if base_name == "": - raise OSError("Script dir does not exist and can not be created: {}".format(requested_dir)) + raise OSError(f"Script dir does not exist and can not be created: {requested_dir}") for dir_to_create in reversed(dirs_to_create): directory_name = os.path.join(directory_name, dir_to_create) @@ -93,7 +92,7 @@ def get_user_script_dir() -> str: """ Returns: the user script directory """ - global USER_SCRIPT_DIR + global USER_SCRIPT_DIR # ruff: ignore[PLW0602] return USER_SCRIPT_DIR @@ -145,16 +144,16 @@ def _get_completion_paths(self, line_buffer: str) -> tuple[list[str], str, bool, # get file path search_path = line_buffer.split(LOAD_SCRIPT_COMMAND)[-1] - had_quotes = search_path.startswith("'") or search_path.startswith('"') + had_quotes = search_path.startswith(("'", '"')) if had_quotes: search_path = search_path[1:] is_absolute = os.path.isabs(search_path) if not is_absolute: - search_path = "{}{}".format(get_user_script_dir(), search_path) + search_path = f"{get_user_script_dir()}{search_path}" # search and return results - paths = glob.glob("{}*".format(search_path)) + paths = glob.glob(f"{search_path}*") return paths, search_path, had_quotes, is_absolute @@ -180,7 +179,7 @@ def standalone_complete(self, completer: IPCompleter, text: str | None = None) - if LOAD_SCRIPT_COMMAND not in line_buffer: return [] else: - paths, search_path, had_quotes, was_absolute = self._get_completion_paths(line_buffer) + paths, _search_path, had_quotes, was_absolute = self._get_completion_paths(line_buffer) # add quote if it is missing if had_quotes: @@ -257,7 +256,7 @@ def pydev_complete( # assemble the auto replace completion.append( - "{}{}".format(line_buffer_back_to_last_splitting_character, completion_path) + f"{line_buffer_back_to_last_splitting_character}{completion_path}" ) return match, completion @@ -446,6 +445,6 @@ def _handle_exception(exception: Exception | None = None, message: str | None = elif message is not None: _print_error_message(message) if _exceptions_raised: - raise Exception(message) + raise exception else: _print_error_message("UNSPECIFIED") diff --git a/src/genie_python/genie_blockserver.py b/src/genie_python/genie_blockserver.py index 63f32bd3..075f0ccf 100644 --- a/src/genie_python/genie_blockserver.py +++ b/src/genie_python/genie_blockserver.py @@ -1,12 +1,11 @@ -from __future__ import absolute_import, print_function - import time -from builtins import object -from typing import TYPE_CHECKING, Any, Callable, ParamSpec, TypeVar +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar -from genie_python.utilities import compress_and_hex, dehex_decompress_and_dejson from ibex_non_ca_helpers.typing import PVValue +from genie_python.utilities import compress_and_hex, dehex_decompress_and_dejson + if TYPE_CHECKING: from genie_python.genie_epics_api import API @@ -25,18 +24,17 @@ def wrapper(*args: Param.args, **kwargs: Param.kwargs) -> RetType: return func(*args, **kwargs) except Exception as e: print( - "Exception thrown from {}: {}, will retry in 15 seconds".format( - func.__name__, e.__class__.__name__ - ) + f"Exception thrown from {func.__name__}: " + f"{e.__class__.__name__}, will retry in 15 seconds" ) time.sleep(15) return wrapper -class BlockServer(object): +class BlockServer: def __init__(self, api: "API") -> None: - self.api: "API" = api + self.api: API = api def _get_pv_value(self, pv: str, as_string: bool = False) -> "PVValue": """Just a convenient wrapper for calling the api's get_pv_value method""" @@ -47,21 +45,21 @@ def _set_pv_value(self, pv: str, value: "PVValue | bytes", wait: bool = False) - return self.api.set_pv_value(self.api.prefix_pv_name(pv), value, wait) @_blockserver_retry - def get_sample_par_names(self) -> Any: # noqa: ANN401 + def get_sample_par_names(self) -> Any: """Get the current sample parameter names as a list.""" # Get the names from the blockserver raw = self._get_pv_value(BLOCK_SERVER_PREFIX + "SAMPLE_PARS", True) return dehex_decompress_and_dejson(raw) @_blockserver_retry - def get_beamline_par_names(self) -> Any: # noqa: ANN401 + def get_beamline_par_names(self) -> Any: """Get the current beamline parameter names as a list.""" # Get the names from the blockserver raw = self._get_pv_value(BLOCK_SERVER_PREFIX + "BEAMLINE_PARS", True) return dehex_decompress_and_dejson(raw) @_blockserver_retry - def get_runcontrol_settings(self) -> Any: # noqa: ANN401 + def get_runcontrol_settings(self) -> Any: """Get the current run-control settings.""" raw = self._get_pv_value(BLOCK_SERVER_PREFIX + "GET_RC_PARS", True) return dehex_decompress_and_dejson(raw) diff --git a/src/genie_python/genie_cachannel_wrapper.py b/src/genie_python/genie_cachannel_wrapper.py index a36ed89e..7e7a7337 100644 --- a/src/genie_python/genie_cachannel_wrapper.py +++ b/src/genie_python/genie_cachannel_wrapper.py @@ -2,14 +2,11 @@ Wrapping of channel access in genie_python """ -from __future__ import absolute_import, print_function - import os import threading -from builtins import object from collections.abc import Callable from threading import Event -from typing import Optional, Tuple, TypeVar +from typing import TypeVar from CaChannel import CaChannel, CaChannelException, ca @@ -48,12 +45,12 @@ T = TypeVar("T") -class CaChannelWrapper(object): +class CaChannelWrapper: """ Wrap CA Channel access to give utilities methods for access in one place """ - error_log_func: Optional[Callable[[str], None]] = None + error_log_func: Callable[[str], None] | None = None # noinspection PyPep8Naming @staticmethod @@ -69,11 +66,11 @@ def logError(message: str): # noqa N802 except Exception: pass else: - print("CAERROR: {}".format(message)) + print(f"CAERROR: {message}") # noinspection PyPep8Naming @staticmethod - def printfHandler(message: str, user_args: Tuple[T, ...]) -> None: # noqa N802 + def printfHandler(message: str, user_args: tuple[T, ...]) -> None: # noqa N802 """ Callback used for CA printing messages. @@ -84,11 +81,11 @@ def printfHandler(message: str, user_args: Tuple[T, ...]) -> None: # noqa N802 Returns: None. """ - CaChannelWrapper.logError("CAMessage: {}".format(message)) + CaChannelWrapper.logError(f"CAMessage: {message}") # noinspection PyPep8Naming @staticmethod - def CAExceptionHandler(epics_args: dict[str, str], user_args: Tuple[None]) -> None: # noqa N802 + def CAExceptionHandler(epics_args: dict[str, str], user_args: tuple[None]) -> None: # noqa N802 """ Callback used for CA exception messages. @@ -140,7 +137,7 @@ def installHandlers(chan: CaChannel) -> None: # noqa N802 # noinspection PyPep8Naming @staticmethod - def putCB(epics_args: Tuple[str, int, int, int], user_args: Tuple[Event, ...]) -> None: # noqa N802 + def putCB(epics_args: tuple[str, int, int, int], user_args: tuple[Event, ...]) -> None: # noqa N802 """ Callback used for setting PV values. @@ -215,12 +212,12 @@ def _check_for_disp(name: str) -> None: ): # Do not check for DISP if it's already in the name of the PV to check if "." in name: # If given a field on a PV, check the PV itself if DISP is set name = name.split(".")[0] - _disp_name = "{}.DISP".format(name) + _disp_name = f"{name}.DISP" if ( CaChannelWrapper.pv_exists(_disp_name, 0) and CaChannelWrapper.get_pv_value(_disp_name) != "0" ): - raise WriteAccessException("{} (DISP is set)".format(name)) + raise WriteAccessException(f"{name} (DISP is set)") @staticmethod def get_chan(name: str, timeout: float = EXIST_TIMEOUT) -> CaChannel: @@ -317,7 +314,7 @@ def get_pv_value( return output @staticmethod - def get_pv_timestamp(name: str, timeout: float = TIMEOUT) -> Tuple[int, int]: + def get_pv_timestamp(name: str, timeout: float = TIMEOUT) -> tuple[int, int]: """ Get the timestamp of when the PV was last processed. @@ -434,7 +431,7 @@ def check_for_enum_value(value: "PVValue|bytes", chan: CaChannel, name: str) -> @staticmethod def add_monitor( name: str, - call_back_function: "Callable[[PVValue, Optional[str], Optional[str]], None]", + call_back_function: "Callable[[PVValue, str | None, str | None], None]", link_alarm_on_disconnect: bool = True, to_string: bool = False, use_numpy: bool | None = None, @@ -484,7 +481,7 @@ def _process_call_back(epics_args: dict[str, str], _: dict[str, str]) -> None: epics_args.get("pv_status", AlarmCondition.No), ) - def _connection_callback(epics_args: Tuple[T, ...], _: dict[str, str]) -> None: + def _connection_callback(epics_args: tuple[T, ...], _: dict[str, str]) -> None: if epics_args[1] == ca.CA_OP_CONN_DOWN: call_back_function(chan.last_value, AlarmSeverity.Invalid, AlarmCondition.Link) @@ -508,7 +505,7 @@ def poll() -> None: """ # pick first channel and perform flush on it. try: - for key, value in CACHE.map.items(): + for value in CACHE.map.values(): value.poll() break except AttributeError: @@ -517,7 +514,7 @@ def poll() -> None: @staticmethod def _wait_for_pend_event( - chan: CaChannel, event: Event, timeout: Optional[float] = None, interval: float = 0.1 + chan: CaChannel, event: Event, timeout: float | None = None, interval: float = 0.1 ) -> None: """ Wait for a pending event to occur in short intervals to allow for keyboard interrupt; diff --git a/src/genie_python/genie_change_cache.py b/src/genie_python/genie_change_cache.py index 3ba0a83a..59c7ebf1 100644 --- a/src/genie_python/genie_change_cache.py +++ b/src/genie_python/genie_change_cache.py @@ -1,8 +1,9 @@ -from builtins import object, str +import xml.etree.ElementTree as ET +from typing import Any -class ChangeCache(object): - def __init__(self): +class ChangeCache: + def __init__(self) -> None: self.wiring = None self.detector = None self.spectra = None @@ -31,12 +32,12 @@ def __init__(self): self.periods_delay = None self.periods_settings = [] - def set_monitor(self, spec, low, high): + def set_monitor(self, spec: int, low: float, high: float) -> None: self.mon_spect = spec self.mon_from = low self.mon_to = high - def clear_vetos(self): + def clear_vetos(self) -> None: self.smp_veto = 0 self.ts2_veto = 0 self.hz50_veto = 0 @@ -45,12 +46,12 @@ def clear_vetos(self): self.ext2_veto = 0 self.ext3_veto = 0 - def set_fermi(self, enable, delay=1.0, width=1.0): + def set_fermi(self, enable: bool, delay: float = 1.0, width: float = 1.0) -> None: self.fermi_veto = 1 if enable else 0 self.fermi_delay = delay self.fermi_width = width - def change_dae_settings(self, root): + def change_dae_settings(self, root: ET.Element) -> bool: changed = self._change_xml(root, "String", "Wiring Table", self.wiring) changed |= self._change_xml(root, "String", "Detector Table", self.detector) changed |= self._change_xml(root, "String", "Spectra Table", self.spectra) @@ -68,7 +69,7 @@ def change_dae_settings(self, root): changed |= self._change_vetos(root) return changed - def _change_vetos(self, root): + def _change_vetos(self, root: ET.Element) -> bool: changed = self._change_xml(root, "EW", "SMP (Chopper) Veto", self.smp_veto) changed |= self._change_xml(root, "EW", " TS2 Pulse Veto", self.ts2_veto) changed |= self._change_xml(root, "EW", " ISIS 50Hz Veto", self.hz50_veto) @@ -78,30 +79,30 @@ def _change_vetos(self, root): changed |= self._change_xml(root, "EW", "Veto 3", self.ext3_veto) return changed - def change_tcb_calculation_method(self, root): + def change_tcb_calculation_method(self, root: ET.Element) -> bool: changed = self._change_xml(root, "U16", "Calculation Method", self.tcb_calculation_method) return changed - def change_tcb_settings(self, root): + def change_tcb_settings(self, root: ET.Element) -> bool: changed = self._change_xml(root, "String", "Time Channel File", self.tcb_file) changed |= self.change_tcb_calculation_method(root) changed |= self._change_tcb_table(root) return changed - def _change_tcb_table(self, root): + def _change_tcb_table(self, root: ET.Element) -> bool: changed = False for row in self.tcb_tables: regime = str(row[0]) trange = str(row[1]) - changed |= self._change_xml(root, "DBL", "TR%s From %s" % (regime, trange), row[2]) - changed |= self._change_xml(root, "DBL", "TR%s To %s" % (regime, trange), row[3]) - changed |= self._change_xml(root, "DBL", "TR%s Steps %s" % (regime, trange), row[4]) - changed |= self._change_xml(root, "U16", "TR%s In Mode %s" % (regime, trange), row[5]) + changed |= self._change_xml(root, "DBL", f"TR{regime} From {trange}", row[2]) + changed |= self._change_xml(root, "DBL", f"TR{regime} To {trange}", row[3]) + changed |= self._change_xml(root, "DBL", f"TR{regime} Steps {trange}", row[4]) + changed |= self._change_xml(root, "U16", f"TR{regime} In Mode {trange}", row[5]) changed |= self.change_tcb_calculation_method(root) return changed - def change_period_settings(self, root): + def change_period_settings(self, root: ET.Element) -> bool: changed = self._change_xml(root, "EW", "Period Type", self.periods_type) changed |= self._change_xml( root, "I32", "Number Of Software Periods", self.periods_soft_num @@ -113,7 +114,7 @@ def change_period_settings(self, root): changed |= self._change_period_table(root) return changed - def _change_period_table(self, root): + def _change_period_table(self, root: ET.Element) -> bool: changed = False for row in self.periods_settings: period = row[0] @@ -121,13 +122,13 @@ def _change_period_table(self, root): frames = row[2] output = row[3] label = row[4] - changed |= self._change_xml(root, "EW", "Type %s" % period, ptype) - changed |= self._change_xml(root, "I32", "Frames %s" % period, frames) - changed |= self._change_xml(root, "U16", "Output %s" % period, output) - changed |= self._change_xml(root, "String", "Label %s" % period, label) + changed |= self._change_xml(root, "EW", f"Type {period}", ptype) + changed |= self._change_xml(root, "I32", f"Frames {period}", frames) + changed |= self._change_xml(root, "U16", f"Output {period}", output) + changed |= self._change_xml(root, "String", f"Label {period}", label) return changed - def _change_xml(self, xml, node, name, value): + def _change_xml(self, xml: ET.Element, node: str, name: str, value: Any) -> bool: """ Helper func to change the xml. Will not be set if the input is None. diff --git a/src/genie_python/genie_dae.py b/src/genie_python/genie_dae.py index e363c488..276729ff 100644 --- a/src/genie_python/genie_dae.py +++ b/src/genie_python/genie_dae.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import, print_function - import json import os import re import xml.etree.ElementTree as ET import zlib from binascii import hexlify -from builtins import str from collections import namedtuple +from collections.abc import Generator from contextlib import contextmanager from datetime import datetime, timedelta -from io import open from stat import S_IREAD, S_IWUSR from time import sleep, strftime -from typing import TYPE_CHECKING, Generator, cast +from typing import TYPE_CHECKING, cast import numpy as np import numpy.typing as npt @@ -25,6 +22,8 @@ # Note: caffi dynamically added to dependencies by CaChannel if not using built backend. from caffi.ca import AlarmCondition, AlarmSeverity # type: ignore[reportMissingImports] +from ibex_non_ca_helpers.typing import PVValue + from genie_python.genie_cachannel_wrapper import CaChannelWrapper from genie_python.genie_change_cache import ChangeCache from genie_python.utilities import ( @@ -34,7 +33,6 @@ require_runstate, waveform_to_string, ) -from ibex_non_ca_helpers.typing import PVValue if TYPE_CHECKING: from genie_python.genie import _GetspectrumReturn @@ -150,7 +148,7 @@ FIFO_VETO = "fifo" -class Dae(object): +class Dae: """ Communications with the DAE pvs. """ @@ -245,7 +243,7 @@ def _check_for_runstate_error(self, pv: str, header: str = "") -> None: """ status = self._get_pv_value(pv + ".STAT", to_string=True) if status != "NO_ALARM": - raise Exception( + raise OSError( "{} {}".format( header.strip(), self._get_pv_value(self._get_dae_pv_name("errormessage"), to_string=True), @@ -287,7 +285,7 @@ def set_verbose(self, verbose: bool) -> None: else: print("Setting DAE messages to non-verbose mode") else: - raise Exception("Value must be boolean") + raise TypeError("Value must be boolean") @require_runstate(["SETUP"]) def begin_run( @@ -316,7 +314,7 @@ def begin_run( prepost - run pre and post commands [optional] """ if self.in_change: - raise Exception("Cannot start in CHANGE mode, type change_finish()") + raise ValueError("Cannot start in CHANGE mode, type change_finish()") # Set sample parameters sample_pars = { @@ -332,7 +330,7 @@ def begin_run( # Check PV exists val = self._get_pv_value(self._get_dae_pv_name("beginrun")) if val is None: - raise Exception("begin_run: could not connect to DAE") + raise OSError("begin_run: could not connect to DAE") if period is not None: # Set the period before starting the run @@ -351,8 +349,8 @@ def begin_run( "The following details will currently be used to determine" "ownership of the data file" ) - print("* Proposal Number: {}".format(self.get_rb_number())) - print("* Experiment Team: {}".format(self.get_users())) + print(f"* Proposal Number: {self.get_rb_number()}") + print(f"* Experiment Team: {self.get_users()}") print("If this is incorrect, you can change it any time before the run is ENDed\n") except Exception as e: print(f"WARNING: Unable to read RB/Users from service: {e}") @@ -385,7 +383,7 @@ def callback_function( """ message = waveform_to_string(message) if message: - print("ISISICP error: {}".format(message)) + print(f"ISISICP error: {message}") _cancel_monitor_fn = CaChannelWrapper.add_monitor( self._get_dae_pv_name("errormessage"), callback_function @@ -434,11 +432,9 @@ def abort_run(self, prepost: bool = True) -> None: prepost - run pre and post commands [optional] """ print( - ( - "** Aborting Run {} at {} " - "(the run will not be saved, call g.recover() to undo this)".format( - self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ") - ) + "** Aborting Run {} at {} " + "(the run will not be saved, call g.recover() to undo this)".format( + self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ") ) ) self._set_pv_value(self._get_dae_pv_name("abortrun", base=not prepost), 1, wait=True) @@ -477,7 +473,7 @@ def end_run( run_number = self.get_run_number() if not quiet: - print(("** Ending Run {} at {}".format(run_number, strftime("%H:%M:%S %d/%m/%y ")))) + print("** Ending Run {} at {}".format(run_number, strftime("%H:%M:%S %d/%m/%y "))) self.api.logger.log_info_msg(f"END: run number: {run_number}") try: @@ -532,7 +528,7 @@ def update_store_run(self) -> None: This is more efficient than doing the commands separately. """ print( - ("** Saving Run {} at {}".format(self.get_run_number(), strftime("%H:%M:%S %d/%m/%y "))) + "** Saving Run {} at {}".format(self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ")) ) self._set_pv_value(self._get_dae_pv_name("saverun"), 1, wait=True) @@ -618,11 +614,7 @@ def pause_run(self, immediate: bool = False, prepost: bool = True) -> None: return print( - ( - "** Pausing Run {} at {}".format( - self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ") - ) - ) + "** Pausing Run {} at {}".format(self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ")) ) if immediate: @@ -648,11 +640,7 @@ def resume_run(self, prepost: bool = True) -> None: prepost - run pre and post commands [optional] """ print( - ( - "** Resuming Run {} at {}".format( - self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ") - ) - ) + "** Resuming Run {} at {}".format(self.get_run_number(), strftime("%H:%M:%S %d/%m/%y ")) ) self._set_pv_value(self._get_dae_pv_name("resumerun", base=not prepost), 1, wait=True) @@ -681,8 +669,8 @@ def get_run_state(self) -> str: """ try: return self._get_pv_value(self._get_dae_pv_name("runstate"), to_string=True) - except IOError: - raise IOError("get_run_state: could not get run state") + except OSError: + raise OSError("get_run_state: could not get run state") def get_run_number(self) -> str: """ @@ -744,7 +732,7 @@ def set_period(self, period: int) -> None: self._set_pv_value(self._get_dae_pv_name("period_sp"), period, wait=True) if self.api.get_pv_alarm(self._get_dae_pv_name("period_sp")) == "INVALID": - raise IOError( + raise OSError( f"You are trying to set an invalid period number {period}! " f"The number must be between 1 and {self.get_num_periods()}." ) @@ -1293,7 +1281,7 @@ def change_sync(self, source: str) -> None: elif source == "isis (ts1 only)": value = 6 else: - raise Exception("Invalid timing source entered, try help(change_sync)!") + raise ValueError("Invalid timing source entered, try help(change_sync)!") self.change_cache.dae_sync = value if did_change: self.change_finish() @@ -1315,13 +1303,13 @@ def change_tcb_file(self, tcb_file: str | None = None, default: bool = False) -> did_change = True if tcb_file is not None: tcb_file = get_correct_path(tcb_file) - print(("Reading TCB boundaries from {}".format(tcb_file))) + print(f"Reading TCB boundaries from {tcb_file}") elif default: tcb_file = "c:\\labview modules\\dae\\tcb.dat" else: - raise Exception("No tcb file specified") + raise ValueError("No tcb file specified") if not os.path.exists(tcb_file): - raise Exception("Tcb file could not be found") + raise OSError("Tcb file could not be found") self.change_cache.tcb_file = tcb_file self.change_cache.tcb_calculation_method = 1 if did_change: @@ -1346,19 +1334,19 @@ def _create_tcb_return_string(self, low: float, high: float, step: float, log: b low_changed, high_changed, step_changed = (c is not None for c in [low, high, step]) if low_changed and high_changed: - out += "range {} to {} ".format(low, high) + out += f"range {low} to {high} " elif low_changed: - out += "low limit to {} ".format(low) + out += f"low limit to {low} " elif high_changed: - out += "high limit to {} ".format(high) + out += f"high limit to {high} " if step_changed: - out += "step {} ".format(step) + out += f"step {step} " if not any([low_changed, high_changed, step_changed]): - out += "to {}".format(binning) + out += f"to {binning}" else: - out += "({})".format(binning) + out += f"({binning})" return out @@ -1382,7 +1370,7 @@ def change_tcb( log: whether to use LOG binning [optional] regime: the time regime to set (1 to 6)[optional] """ - print((self._create_tcb_return_string(low, high, step, log))) + print(self._create_tcb_return_string(low, high, step, log)) did_change = False if not self.in_change: self.change_start() @@ -1431,17 +1419,17 @@ def change_vetos(self, **params: bool) -> None: ] # Change keys to be case insensitive - params = dict((k.lower(), v) for k, v in params.items()) + params = {k.lower(): v for k, v in params.items()} # Check for invalid veto names and invalid (non-boolean) values not_bool = [] for k, v in params.items(): if k not in valid_vetoes: - raise Exception("Invalid veto name: {}".format(k)) + raise ValueError(f"Invalid veto name: {k}") if not isinstance(v, bool): not_bool.append(k) if len(not_bool) > 0: - raise Exception( + raise TypeError( "Vetoes must be set to True or False, " "the following vetoes were incorrect: {}".format(" ".join(not_bool)) ) @@ -1457,30 +1445,22 @@ def change_vetos(self, **params: bool) -> None: did_change = True # Clearall must be done first. - if CLEAR_VETO in params: - if isinstance(params[CLEAR_VETO], bool) and params[CLEAR_VETO]: - self.change_cache.clear_vetos() - if SMP_VETO in params: - if isinstance(params[SMP_VETO], bool): - self.change_cache.smp_veto = int(params[SMP_VETO]) - if TS2_VETO in params: - if isinstance(params[TS2_VETO], bool): - self.change_cache.ts2_veto = int(params[TS2_VETO]) - if HZ50_VETO in params: - if isinstance(params[HZ50_VETO], bool): - self.change_cache.hz50_veto = int(params[HZ50_VETO]) - if EXT0_VETO in params: - if isinstance(params[EXT0_VETO], bool): - self.change_cache.ext0_veto = int(params[EXT0_VETO]) - if EXT1_VETO in params: - if isinstance(params[EXT1_VETO], bool): - self.change_cache.ext1_veto = int(params[EXT1_VETO]) - if EXT2_VETO in params: - if isinstance(params[EXT2_VETO], bool): - self.change_cache.ext2_veto = int(params[EXT2_VETO]) - if EXT3_VETO in params: - if isinstance(params[EXT3_VETO], bool): - self.change_cache.ext3_veto = int(params[EXT3_VETO]) + if CLEAR_VETO in params and isinstance(params[CLEAR_VETO], bool) and params[CLEAR_VETO]: + self.change_cache.clear_vetos() + if SMP_VETO in params and isinstance(params[SMP_VETO], bool): + self.change_cache.smp_veto = int(params[SMP_VETO]) + if TS2_VETO in params and isinstance(params[TS2_VETO], bool): + self.change_cache.ts2_veto = int(params[TS2_VETO]) + if HZ50_VETO in params and isinstance(params[HZ50_VETO], bool): + self.change_cache.hz50_veto = int(params[HZ50_VETO]) + if EXT0_VETO in params and isinstance(params[EXT0_VETO], bool): + self.change_cache.ext0_veto = int(params[EXT0_VETO]) + if EXT1_VETO in params and isinstance(params[EXT1_VETO], bool): + self.change_cache.ext1_veto = int(params[EXT1_VETO]) + if EXT2_VETO in params and isinstance(params[EXT2_VETO], bool): + self.change_cache.ext2_veto = int(params[EXT2_VETO]) + if EXT3_VETO in params and isinstance(params[EXT3_VETO], bool): + self.change_cache.ext3_veto = int(params[EXT3_VETO]) if did_change: self.change_finish() @@ -1511,7 +1491,7 @@ def _change_runtime_vetos(self, params: dict) -> None: ) del params[FIFO_VETO] else: - raise Exception("FIFO veto must be set to True or False") + raise TypeError("FIFO veto must be set to True or False") return params def set_fermi_veto( @@ -1529,20 +1509,18 @@ def set_fermi_veto( Exception: if invalid typed value supplied. """ if not isinstance(enable, bool): - raise Exception("Fermi veto: enable must be a boolean value") + raise TypeError("Fermi veto: enable must be a boolean value") if not isinstance(delay, float) and not isinstance(delay, int): - raise Exception("Fermi veto: delay must be a numeric value") + raise TypeError("Fermi veto: delay must be a numeric value") if not isinstance(width, float) and not isinstance(width, int): - raise Exception("Fermi veto: width must be a numeric value") + raise TypeError("Fermi veto: width must be a numeric value") did_change = False if not self.in_change: self.change_start() did_change = True if enable: self.change_cache.set_fermi(1, delay, width) - print( - ("SET_FERMI_VETO: requested status is ON, delay: {} width: {}".format(delay, width)) - ) + print(f"SET_FERMI_VETO: requested status is ON, delay: {delay} width: {width}") else: self.change_cache.set_fermi(0) print("SET_FERMI_VETO: requested status is OFF") @@ -1560,7 +1538,7 @@ def set_num_soft_periods(self, number: int) -> None: Exception: if wrongly typed value supplied """ if not isinstance(number, float) and not isinstance(number, int): - raise Exception("Number of soft periods must be a numeric value") + raise TypeError("Number of soft periods must be a numeric value") did_change = False if not self.in_change: self.change_start() @@ -1644,7 +1622,7 @@ def configure_hard_periods( if period_file is not None: period_file = get_correct_path(period_file) if not os.path.exists(period_file): - raise Exception("Period file could not be found") + raise OSError("Period file could not be found") self.change_cache.periods_src = 1 self.change_cache.periods_file = period_file else: @@ -1654,7 +1632,7 @@ def configure_hard_periods( elif mode.strip().lower() == "ext": self.change_cache.periods_type = 2 else: - raise Exception('Period mode invalid, it should be "int" or "ext"') + raise ValueError('Period mode invalid, it should be "int" or "ext"') if did_change: self.change_finish() @@ -1697,12 +1675,12 @@ def configure_internal_periods( if isinstance(sequences, int): self.change_cache.periods_seq = sequences else: - raise Exception("Number of period sequences must be an integer") + raise TypeError("Number of period sequences must be an integer") if output_delay is not None: if isinstance(output_delay, int): self.change_cache.periods_delay = output_delay else: - raise Exception("Output delay of periods must be an integer (microseconds)") + raise TypeError("Output delay of periods must be an integer (microseconds)") self.define_hard_period(period, daq, dwell, unused, frames, output, label) if did_change: self.change_finish() @@ -1764,7 +1742,7 @@ def define_hard_period( (period, p_type, p_frames, p_output, p_label) ) else: - raise Exception("Period number must be an integer from 1 to 8") + raise ValueError("Period number must be an integer from 1 to 8") if did_change: self.change_finish() @@ -1796,8 +1774,8 @@ def _change_dae_settings(self) -> None: errors = " " for item in tables_to_check: if item.correctly_written is False: - errors = "{}{} : {}, ".format(errors, item.table_type, item.cache_value) - raise ValueError("{} table(s) failed to write.".format(errors)) + errors = f"{errors}{item.table_type} : {item.cache_value}, " + raise ValueError(f"{errors} table(s) failed to write.") def _check_table_file_paths_correct(self, tables_to_check: list[str]) -> None: """Checks the wiring, detector and spectra tables in @@ -1881,7 +1859,7 @@ def _change_period_settings(self) -> None: ) if self.api.get_pv_alarm(self._get_dae_pv_name("periodsettings_sp")) == "INVALID": - raise IOError( + raise OSError( "The DAE could not set the number of periods! " "This may be because you are trying to " "set a number that is too large for the DAE memory. Try a smaller number!" @@ -1939,10 +1917,7 @@ def in_transition(self) -> bool: bool: is the DAE in transition """ transition = self._get_pv_value(self._get_dae_pv_name("statetrans")) - if transition == "Yes": - return True - else: - return False + return transition == "Yes" def get_wiring_tables(self) -> list[str]: """ @@ -2004,7 +1979,7 @@ def get_tcb_settings(self, trange: int, regime: int = 1) -> dict[str, int]: dict: the low, high and step for the supplied range and regime """ root = self._get_tcb_xml() - search_text = r"TR{} (\w+) {}".format(regime, trange) + search_text = rf"TR{regime} (\w+) {trange}" regex = re.compile(search_text) out = {} @@ -2021,7 +1996,7 @@ def get_table_path(self, table_type: str) -> str: dae_xml = self._get_dae_settings_xml() for top in dae_xml.iter("String"): n = top.find("Name") - if n.text == "{} Table".format(table_type): + if n.text == f"{table_type} Table": val = top.find("Val") return val.text @@ -2066,7 +2041,7 @@ def _isis_dae_triggered_state_was_reached( :return: True if state was reached; False otherwise """ self.api.logger.log_info_msg( - "Trying to reach state '{}' using trigger pv '{}'".format(state, trigger_pv) + f"Trying to reach state '{state}' using trigger pv '{trigger_pv}'" ) state_attained = False current_state = "Not set" @@ -2079,9 +2054,8 @@ def _isis_dae_triggered_state_was_reached( break else: self.api.logger.log_error_msg( - "Failed to get to state '{}' using trigger pv '{}' was in state '{}'".format( - state, trigger_pv, current_state - ) + f"Failed to get to state '{state}' using trigger pv " + f"'{trigger_pv}' was in state '{current_state}'" ) return state_attained @@ -2092,7 +2066,7 @@ def temporarily_kill_icp(self) -> Generator[None, None, None]: """ try: if not self._isis_dae_triggered_state_was_reached("CS:PS:ISISDAE_01:STOP", "Shutdown"): - raise IOError("Could not stop ISISDAE!") + raise OSError("Could not stop ISISDAE!") for p in psutil.process_iter(): try: if p.name().lower() == "isisicp.exe": @@ -2102,7 +2076,7 @@ def temporarily_kill_icp(self) -> Generator[None, None, None]: yield finally: if not self._isis_dae_triggered_state_was_reached("CS:PS:ISISDAE_01:START", "Running"): - raise IOError("Could not restart ISISDAE!") + raise OSError("Could not restart ISISDAE!") if self._get_pv_value(self._prefix_pv_name("CS:PS:ISISDAE_01:AUTORESTART")) != "On": self._set_pv_value(self._prefix_pv_name("CS:PS:ISISDAE_01:TOGGLE"), 1) @@ -2119,7 +2093,7 @@ def set_simulation_mode(self, mode: bool) -> None: existent_config_files = [p for p in DAE_CONFIG_FILE_PATHS if os.path.exists(p)] if not len(existent_config_files) > 0: - raise IOError("Could not find ICP configuration file") + raise OSError("Could not find ICP configuration file") with self.temporarily_kill_icp(): for path in existent_config_files: @@ -2183,9 +2157,7 @@ def integrate_spectrum( else: if t_min < time[0]: raise ValueError( - "Argument from_time, {}, is less than lowest bin time, {}.".format( - t_min, time[0] - ) + f"Argument from_time, {t_min}, is less than lowest bin time, {time[0]}." ) first_bin_included = time.searchsorted(t_min, side="left") @@ -2196,9 +2168,7 @@ def integrate_spectrum( else: if t_max > time[-1]: raise ValueError( - "Argument to_time, {}, is greater than highest bin time, {}.".format( - t_max, time[-1] - ) + f"Argument to_time, {t_max}, is greater than highest bin time, {time[-1]}." ) last_complete_bin = time.searchsorted(t_max, side="left") diff --git a/src/genie_python/genie_epics_api.py b/src/genie_python/genie_epics_api.py index 3fdb6971..ea3079b1 100644 --- a/src/genie_python/genie_epics_api.py +++ b/src/genie_python/genie_epics_api.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import, print_function - import contextlib import os import re @@ -7,10 +5,9 @@ import typing import urllib.parse import urllib.request -from builtins import str from collections import OrderedDict -from io import open -from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Callable +from typing import TYPE_CHECKING, Any from genie_python.block_names import BlockNames, BlockNamesManager from genie_python.channel_access_exceptions import UnableToConnectToPVException @@ -49,7 +46,7 @@ BLOCK_NAMES_MANAGER = BlockNamesManager(BLOCK_NAMES) -class API(object): +class API: def __init__( self, pv_prefix: str, @@ -98,7 +95,7 @@ def __init__( hcom = ctypes.cdll.LoadLibrary(comdll) hcom.eltc(ctypes.c_int(0)) except Exception as e: - print("Unable to disable CA console errors from {}: {}".format(comdll, e)) + print(f"Unable to disable CA console errors from {comdll}: {e}") def get_instrument(self) -> str: """ @@ -178,7 +175,7 @@ def _get_machine_details_from_identifier( if instrument_details is not None: machine = instrument_details["hostName"] elif machine_identifier.startswith(instrument_pv_prefix): - machine = "NDX{0}".format(instrument) + machine = f"NDX{instrument}" elif machine_identifier.startswith(test_machine_pv_prefix): machine = instrument else: @@ -224,9 +221,7 @@ def set_instrument( try: self.exp_data = GetExperimentData(machine) except Exception as e: - error_message = ( - "Could not connect to database, RB numbers will not be accessible: {}".format(e) - ) + error_message = f"Could not connect to database, RB numbers will not be accessible: {e}" self.logger.log_error_msg(error_message) print(error_message) self.exp_data = None @@ -255,8 +250,7 @@ def _get_pv_prefix(self, instrument: str, is_instrument: bool) -> str: string: the PV prefix """ clean_instrument = instrument - if clean_instrument.endswith(":"): - clean_instrument = clean_instrument[:-1] + clean_instrument = clean_instrument.removesuffix(":") if len(clean_instrument) > 8: clean_instrument = clean_instrument[0:6] + crc8(clean_instrument) @@ -264,13 +258,11 @@ def _get_pv_prefix(self, instrument: str, is_instrument: bool) -> str: if is_instrument: pv_prefix_prefix = "IN" - print("THIS IS %s!" % self.instrument_name.upper()) + print(f"THIS IS {self.instrument_name.upper()}!") else: pv_prefix_prefix = "TE" - print("THIS IS %s! (test machine)" % self.instrument_name.upper()) - return "{prefix}:{instrument}:".format( - prefix=pv_prefix_prefix, instrument=self.instrument_name - ) + print(f"THIS IS {self.instrument_name.upper()}! (test machine)") + return f"{pv_prefix_prefix}:{self.instrument_name}:" def prefix_pv_name(self, name: str) -> str: """ @@ -312,7 +304,7 @@ def init_instrument( import importlib # Load the instrument init file - self.localmod = importlib.import_module("init_{}".format(instrument)) + self.localmod = importlib.import_module(f"init_{instrument}") _file = self.localmod.__file__ assert _file is not None @@ -324,9 +316,10 @@ def init_instrument( assert isinstance(file_loc, str) # execfile - this puts any imports in the init file into the globals namespace # Note: Anything loose in the module like print statements will be run twice - exec(compile(open(file_loc).read(), file_loc, "exec"), globs) + with open(file_loc) as f: + exec(compile(f.read(), file_loc, "exec"), globs) # ruff: ignore[S102] # Call the init command - init_func = getattr(self.localmod, "init") + init_func = self.localmod.init init_func(machine_name) def set_pv_value( @@ -356,10 +349,9 @@ def set_pv_value( f" is_local={is_local} attempts={attempts} ignoring because value is None" ) return - if is_local: - if not name.startswith(self.inst_prefix): - name = self.prefix_pv_name(name) - self.logger.log_info_msg("set_pv_value %s %s" % (name, str(value))) + if is_local and not name.startswith(self.inst_prefix): + name = self.prefix_pv_name(name) + self.logger.log_info_msg(f"set_pv_value {name} {value!s}") while True: try: @@ -368,8 +360,8 @@ def set_pv_value( except Exception as e: attempts -= 1 if attempts < 1: - self.logger.log_error_msg("set_pv_value exception {!r}".format(e)) - raise e + self.logger.log_error_msg(f"set_pv_value exception {e!r}") + raise @typing.overload def get_pv_value( @@ -415,9 +407,8 @@ def get_pv_value( use_numpy (None|boolean): True use numpy to return arrays, False return a list; None for use the default """ - if is_local: - if not name.startswith(self.inst_prefix): - name = self.prefix_pv_name(name) + if is_local and not name.startswith(self.inst_prefix): + name = self.prefix_pv_name(name) if not self.pv_exists(name): raise UnableToConnectToPVException(name, "does not exist") @@ -425,10 +416,10 @@ def get_pv_value( while True: try: return Wrapper.get_pv_value(name, to_string, use_numpy=use_numpy) - except Exception as e: + except Exception: attempts -= 1 if attempts < 1: - raise e + raise def pv_exists(self, name: str, fail_fast: bool = False, is_local: bool = False) -> bool: """ @@ -444,9 +435,8 @@ def pv_exists(self, name: str, fail_fast: bool = False, is_local: bool = False) Returns: bool: True if the block exists """ - if is_local: - if not name.startswith(self.inst_prefix): - name = self.prefix_pv_name(name) + if is_local and not name.startswith(self.inst_prefix): + name = self.prefix_pv_name(name) if fail_fast: return Wrapper.pv_exists(name, 0) else: @@ -511,7 +501,7 @@ def get_block_names(self) -> list[str]: Note: does not include the prefix """ - return [name for name in BLOCK_NAMES.__dict__.keys()] + return [name for name in BLOCK_NAMES.__dict__] def block_exists(self, name: str, fail_fast: bool = False) -> bool: """ @@ -530,8 +520,8 @@ def set_block_value( name: str, value: "PVValue" = None, runcontrol: bool | None = None, - lowlimit: int | float | None = None, - highlimit: int | float | None = None, + lowlimit: float | None = None, + highlimit: float | None = None, wait: bool | None = False, ) -> None: """ @@ -540,7 +530,7 @@ def set_block_value( # Run pre-command if wait is not None and runcontrol is not None: # Cannot set both at the same time - raise Exception( + raise ValueError( "Cannot enable or disable runcontrol at the same time as setting a wait" ) @@ -553,15 +543,15 @@ def set_block_value( if lowlimit is not None and highlimit is not None: if lowlimit > highlimit: print( - "Low limit ({}) higher than high limit ({}), " - "swapping them around for you".format(lowlimit, highlimit) + f"Low limit ({lowlimit}) higher than high limit ({highlimit}), " + "swapping them around for you" ) lowlimit, highlimit = highlimit, lowlimit if isinstance(value, (int, float)) and wait and not lowlimit < value < highlimit: # Can only warn as may move through this range whilst changing print( - "Warning the range {} to {} does not cover setpoint of {}, " - "may wait forever".format(lowlimit, highlimit, value) + f"Warning the range {lowlimit} to {highlimit} does not " + f"cover setpoint of {value}, may wait forever" ) if value is not None: @@ -626,10 +616,9 @@ def get_block_units(self, block_name: str) -> str | None: existing_block.upper() for existing_block in self.get_block_names() ): # If block doesn't exist, not found even in some form on the block server - raise Exception( - "No block with the name '{}' exists\nCurrent blocks are {}".format( - block_name, self.get_block_names() - ) + raise ValueError( + f"No block with the name '{block_name}' exists\n" + f"Current blocks are {self.get_block_names()}" ) field_type = Wrapper.dbf_type_to_string(Wrapper.get_chan(pv_name).field_type()) @@ -660,7 +649,7 @@ def _get_pars( ans[m.groups()[0]] = val else: self.logger.log_error_msg( - "Unexpected PV found whilst retrieving parameters: {0}".format(n) + f"Unexpected PV found whilst retrieving parameters: {n}" ) return ans @@ -692,12 +681,12 @@ def set_sample_par(self, name: str, value: "PVValue") -> None: if names is not None and isinstance(names, list): for n in names: if isinstance(n, str): - m = re.match(".+:SAMPLE:%s" % name.upper(), n) + m = re.match(f".+:SAMPLE:{name.upper()}", n) if m is not None: # Found it! self.set_pv_value(self.prefix_pv_name(n), value) return - raise Exception("Sample parameter %s does not exist" % name) + raise ValueError(f"Sample parameter {name} does not exist") def get_beamline_pars(self) -> "_GetbeamlineparsReturn": """ @@ -726,11 +715,11 @@ def set_beamline_par(self, name: str, value: "PVValue") -> None: if names is not None: for n in names: - m = re.match(".+:BL:%s" % name.upper(), n) + m = re.match(f".+:BL:{name.upper()}", n) if m is not None: self.set_pv_value(self.prefix_pv_name(n), value) return - raise Exception("Beamline parameter %s does not exist" % name) + raise ValueError(f"Beamline parameter {name} does not exist") def get_runcontrol_settings(self, block_name: str) -> tuple["PVValue", "PVValue", "PVValue"]: """ @@ -751,9 +740,7 @@ def get_runcontrol_settings(self, block_name: str) -> tuple["PVValue", "PVValue" except UnableToConnectToPVException: return "UNKNOWN", "UNKNOWN", "UNKNOWN" - def check_alarms( - self, blocks: typing.Tuple[str, ...] - ) -> tuple[list[str], list[str], list[str]]: + def check_alarms(self, blocks: tuple[str, ...]) -> tuple[list[str], list[str], list[str]]: """ Checks whether the specified blocks are in alarm. @@ -787,7 +774,7 @@ def check_limit_violations(self, blocks: typing.Iterable[str]) -> list[str]: def _get_fields_from_blocks( self, blocks: list[str], field_name: str, field_description: str ) -> list[tuple[str, "PVValue"]]: - field_values = list() + field_values = [] for block in blocks: if self.block_exists(block): block_name = self.correct_blockname(block, False) @@ -795,11 +782,11 @@ def _get_fields_from_blocks( try: field_value = self.get_pv_value(full_block_pv + "." + field_name, attempts=1) field_values.append((block_name, field_value)) - except IOError: + except OSError: # Could not get value - print("Could not get {} for block: {}".format(field_description, block)) + print(f"Could not get {field_description} for block: {block}") else: - print("Block {} does not exist, so ignoring it".format(block)) + print(f"Block {block} does not exist, so ignoring it") return field_values @@ -873,7 +860,7 @@ def send_sms(self, phone_num: str, message: str) -> None: try: self._alert_http_request(mobiles=phone_num, message=message) except Exception as e: - raise Exception("Could not send SMS: {}".format(e)) + raise OSError(f"Could not send SMS: {e}") def send_email(self, address: str, message: str) -> None: """ @@ -886,7 +873,7 @@ def send_email(self, address: str, message: str) -> None: try: self._alert_http_request(emails=address, message=message) except Exception as e: - raise Exception("Could not send email: {}".format(e)) + raise OSError(f"Could not send email: {e}") def send_alert(self, message: str, inst: str | None) -> None: """ @@ -901,7 +888,7 @@ def send_alert(self, message: str, inst: str | None) -> None: try: self._alert_http_request(inst=inst, message=message) except Exception as e: - raise Exception("Could not send alert: {}".format(e)) + raise OSError(f"Could not send alert: {e}") def get_alarm_from_block(self, block: str) -> str: """ @@ -931,9 +918,7 @@ def get_pv_alarm(self, pv_name: str) -> str: alarm status could not be determined """ try: - alarm_val = self.get_pv_value( - "{}.SEVR".format(remove_field_from_pv(pv_name)), to_string=True - ) + alarm_val = self.get_pv_value(f"{remove_field_from_pv(pv_name)}.SEVR", to_string=True) return alarm_val except Exception: @@ -971,10 +956,9 @@ def get_block_data(self, block: str, fail_fast: bool = False) -> "_CgetReturn": ans["connected"] = False else: # Can't find block at all - raise Exception( - "No block with the name '{}' exists\nCurrent blocks are {}".format( - block, self.get_block_names() - ) + raise ValueError( + f"No block with the name '{block}' exists\n" + f"Current blocks are {self.get_block_names()}" ) ans["name"] = block diff --git a/src/genie_python/genie_experimental_data.py b/src/genie_python/genie_experimental_data.py index e3c2ba2d..1a4313fa 100644 --- a/src/genie_python/genie_experimental_data.py +++ b/src/genie_python/genie_experimental_data.py @@ -35,7 +35,7 @@ def get_sql(self) -> SQLAbstraction: """ return self._sql - def _parameter_is_valid(self, value: bool | int | float | str, column: str, table: str) -> bool: + def _parameter_is_valid(self, value: bool | float | str, column: str, table: str) -> bool: """ Checks whether the given value exists in the table. @@ -52,7 +52,7 @@ def _parameter_is_valid(self, value: bool | int | float | str, column: str, tabl command=f"SELECT * FROM `{table}` WHERE {column} = %s", bound_variables=(value,) ) - return True if result else False + return bool(result) class _GetExpDataReturn(TypedDict): rb_number: int | str @@ -117,7 +117,7 @@ def _validate_parameters(self, rb: int | str, user: str, role: str) -> None: raise NotFoundError(f'Role "{role}" was not found. Existing roles: {roles}') def _query_database_for_data( - self, args: bool | int | float | str, sql: str + self, args: bool | float | str, sql: str ) -> list[_GetExpDataReturn]: exp_data = [] for exp_id, user, role, start_date, duration in self.get_sql().query_returning_cursor( @@ -183,4 +183,4 @@ class NotFoundError(IOError): """ def __init__(self, message: str) -> None: - super(NotFoundError, self).__init__(message) + super().__init__(message) diff --git a/src/genie_python/genie_logging.py b/src/genie_python/genie_logging.py index 720f6195..dbb4940e 100644 --- a/src/genie_python/genie_logging.py +++ b/src/genie_python/genie_logging.py @@ -3,11 +3,19 @@ import getpass import logging import logging.config +import os +import socket +from collections.abc import Generator +from contextlib import contextmanager +from time import localtime, strftime +from typing import Any import graypy +from genie_python.version import VERSION + -def send(self, s): +def send(self: graypy.GELFUDPHandler, s: Any) -> None: """ Needed otherwise graypy spits out logs if the DNS alias cannot be resolved. See https://github.com/ISISComputingGroup/IBEX/issues/7059 @@ -25,30 +33,23 @@ def send(self, s): graypy.GELFUDPHandler.send = send -import os -import socket -from contextlib import contextmanager -from time import localtime, strftime - -from genie_python.version import VERSION - class InstrumentFilter(logging.Filter): """For Graylog fields (https://graypy.readthedocs.io/en/latest/readme.html#adding-custom-logging-fields)""" - def __init__(self): + def __init__(self) -> None: self.instrument = "" self.sim_mode = False self.genie_version = VERSION self.reset() - def set_inst_name(self, inst_name): + def set_inst_name(self, inst_name: str) -> None: self.instrument = inst_name - def set_sim_mode(self, sim_mode): + def set_sim_mode(self, sim_mode: bool) -> None: self.sim_mode = sim_mode - def filter(self, record): + def filter(self, record: logging.LogRecord) -> bool: record.instrument = self.instrument record.command_called = self.command_called record.function_args = self.function_args @@ -62,7 +63,7 @@ def filter(self, record): self.reset() return True - def reset(self): + def reset(self) -> None: """ Reset filter fields back to blank values. """ @@ -81,29 +82,29 @@ def reset(self): class LoggingConfigurer: @staticmethod - def get_file_name(): + def get_file_name() -> str: curr_time = localtime() current_date_time = strftime("%Y-%m-%d-%a", curr_time) return f"genie-{current_date_time}.log" @staticmethod - def get_log_file_dir(): + def get_log_file_dir() -> str: if socket.gethostname() in vhd_build_machines: return os.path.join("C:", os.sep, "genie_python_logs") else: if os.name == "nt": return os.path.join("C:", os.sep, "Instrument", "Var", "logs", "genie_python") else: - return os.path.join("/tmp/{}/genie_python".format(getpass.getuser())) + return os.path.join(f"/tmp/{getpass.getuser()}/genie_python") @staticmethod - def get_log_file_path(): + def get_log_file_path() -> str: logs_dir = LoggingConfigurer.get_log_file_dir() filename = LoggingConfigurer.get_file_name() return os.path.join(logs_dir, filename) @staticmethod - def get_logging_config(): + def get_logging_config() -> dict[str, Any]: return { "version": 1, "disable_existing_loggers": False, @@ -142,7 +143,7 @@ def get_logging_config(): @contextmanager -def genie_logger(): +def genie_logger() -> Generator[logging.Logger, None, None]: try: logging.config.dictConfig(LoggingConfigurer.get_logging_config()) yield logging.getLogger("genie_python_graylogger") @@ -151,7 +152,7 @@ def genie_logger(): class GenieLogger: - def __init__(self, sim_mode=False): + def __init__(self, sim_mode: bool = False) -> None: self.sim_mode = sim_mode self.logs_dir = LoggingConfigurer.get_log_file_dir() if not os.path.exists(self.logs_dir): @@ -161,11 +162,17 @@ def __init__(self, sim_mode=False): with genie_logger() as logger: logger.addFilter(filter) - def log_info_msg(self, message): + def log_info_msg(self, message: str) -> None: with genie_logger() as logger: logger.info(self._get_message_with_mode(message)) - def log_command(self, function_name, arguments, command_exception, time_taken=None): + def log_command( + self, + function_name: str, + arguments: Any, + command_exception: str, + time_taken: float | None = None, + ) -> None: filter.command_called = function_name filter.function_args = arguments filter.exception_text = command_exception @@ -174,27 +181,27 @@ def log_command(self, function_name, arguments, command_exception, time_taken=No with genie_logger() as logger: logger.debug(self._get_message_with_mode(f"{function_name} {arguments}")) - def log_command_error_msg(self, function_name, error_msg): + def log_command_error_msg(self, function_name: str, error_msg: str) -> None: error_msg = f"An exception has occurred as a result of the command:\n{error_msg}" filter.command_called = function_name filter.exception_text = error_msg with genie_logger() as logger: logger.error(self._get_message_with_mode(error_msg)) - def log_error_msg(self, error_msg): + def log_error_msg(self, error_msg: str) -> None: filter.exception_text = error_msg with genie_logger() as logger: logger.error(self._get_message_with_mode(error_msg)) - def log_ca_msg(self, error_msg): + def log_ca_msg(self, error_msg: str) -> None: """Log the CA error (from CaChannel)""" filter.exception_text = error_msg with genie_logger() as logger: logger.error(self._get_message_with_mode(error_msg)) - def set_sim_mode(self, sim_mode): + def set_sim_mode(self, sim_mode: bool) -> None: self.sim_mode = sim_mode filter.set_sim_mode(sim_mode) - def _get_message_with_mode(self, msg): + def _get_message_with_mode(self, msg: str) -> str: return f"(SIMULATION) {msg}" if self.sim_mode is True else msg diff --git a/src/genie_python/genie_p4p_wrapper.py b/src/genie_python/genie_p4p_wrapper.py index 68924241..7ac31479 100644 --- a/src/genie_python/genie_p4p_wrapper.py +++ b/src/genie_python/genie_p4p_wrapper.py @@ -2,30 +2,25 @@ Wrapping of p4p in genie_python """ -from __future__ import absolute_import, print_function - import threading -from builtins import object from collections.abc import Callable -from typing import Optional, Tuple +from ibex_non_ca_helpers.typing import PVValue from p4p import Value from p4p.client.thread import Context, Subscription from .channel_access_exceptions import WriteAccessException from .utilities import waveform_to_string -from ibex_non_ca_helpers.typing import PVValue - TIMEOUT = 15 # Default timeout for PV set/get EXIST_TIMEOUT = 3 # Separate smaller timeout for pv_exists() and searchw() operations CACHE = threading.local() CACHE_LOCK = threading.local() -class P4PWrapper(object): - context: Optional[Context] = None - error_log_function: Optional[Callable[[str], None]] = None +class P4PWrapper: + context: Context | None = None + error_log_function: Callable[[str], None] | None = None # noinspection PyPep8Naming @staticmethod @@ -41,7 +36,7 @@ def _log_error(message: str) -> None: except Exception: pass else: - print("PVERROR: {}".format(message)) + print(f"PVERROR: {message}") @staticmethod def set_pv_value( @@ -75,7 +70,7 @@ def get_pv_value( name: str, to_string: bool = False, timeout: float = TIMEOUT, - use_numpy: Optional[bool] = None, + use_numpy: bool | None = None, ) -> "PVValue": context = P4PWrapper.get_context() output = context.get(name, timeout=timeout) @@ -98,7 +93,7 @@ def get_pv_value( return val @staticmethod - def get_pv_timestamp(name: str, timeout: float = TIMEOUT) -> Tuple[int, int]: + def get_pv_timestamp(name: str, timeout: float = TIMEOUT) -> tuple[int, int]: context = P4PWrapper.get_context() output = context.get(name, timeout=timeout) if isinstance(output, Exception): @@ -123,10 +118,10 @@ def pv_exists(name: str, timeout: float) -> bool: @staticmethod def add_monitor( name: str, - call_back_function: "Callable[[PVValue, Optional[str], Optional[str]], None]", + call_back_function: "Callable[[PVValue, str | None, str | None], None]", link_alarm_on_disconnect: bool = True, to_string: bool = False, - use_numpy: Optional[bool] = None, + use_numpy: bool | None = None, ) -> Subscription: def _process_call_back(response: Value | Exception) -> None: if isinstance(response, Exception): @@ -193,9 +188,9 @@ def _check_for_disp(name: str) -> None: ): # Do not check for DISP if it's already in the name of the PV to check if "." in name: # If given a field on a PV, check the PV itself if DISP is set name = name.split(".")[0] - _disp_name = "{}.DISP".format(name) + _disp_name = f"{name}.DISP" if P4PWrapper.pv_exists(_disp_name, 0) and P4PWrapper.get_pv_value(_disp_name) != "0": - raise WriteAccessException("{} (DISP is set)".format(name)) + raise WriteAccessException(f"{name} (DISP is set)") @staticmethod def close_context() -> None: diff --git a/src/genie_python/genie_plot.py b/src/genie_python/genie_plot.py index 5148b3cd..7c1afb13 100644 --- a/src/genie_python/genie_plot.py +++ b/src/genie_python/genie_plot.py @@ -1,10 +1,9 @@ -from __future__ import absolute_import, print_function - -from builtins import object, str +from collections.abc import Callable from functools import wraps +from typing import Any, Self -def _plotting_func(func): +def _plotting_func[T, R](func: Callable[T, R]) -> Callable[T, R]: """ Decorator for functions that interact with MPL. @@ -13,7 +12,7 @@ def _plotting_func(func): """ @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: Any) -> R: import matplotlib.pyplot as pyplt was_interactive = pyplt.isinteractive() @@ -26,9 +25,9 @@ def wrapper(*args, **kwargs): return wrapper -class SpectraPlot(object): +class SpectraPlot: @_plotting_func - def __init__(self, api, spectrum, period, dist): + def __init__(self, api: Any, spectrum: int, period: int, dist: bool) -> None: import matplotlib.pyplot as pyplt self.api = api @@ -38,20 +37,20 @@ def __init__(self, api, spectrum, period, dist): self.ax.autoscale_view(True, True, True) self.ax.set_xlabel("Time") self.ax.set_ylabel("Counts") - self.ax.set_title("Spectrum {}".format(spectrum)) + self.ax.set_title(f"Spectrum {spectrum}") self.add_spectrum(spectrum, period, dist) @_plotting_func - def add_spectrum(self, spectrum, period=1, dist=True): + def add_spectrum(self, spectrum: int, period: int = 1, dist: bool = True) -> Self: self.spectra.append((spectrum, period, dist)) data = self.api.dae.get_spectrum(spectrum, period, dist) - name = "Spect {}".format(spectrum) + name = f"Spect {spectrum}" self.ax.plot(data["time"], data["signal"], label=name) self.__update_legend() return self @_plotting_func - def refresh(self): + def refresh(self) -> None: for i in range(len(self.ax.lines)): data = self.api.dae.get_spectrum( self.spectra[0][0], self.spectra[0][1], self.spectra[0][2] @@ -61,16 +60,16 @@ def refresh(self): self.ax.autoscale_view(True, True, True) @_plotting_func - def delete_plot(self, plotnum): + def delete_plot(self, plotnum: int) -> None: del self.ax.lines[plotnum] del self.spectra[plotnum] self.__update_legend() - def __update_legend(self): + def __update_legend(self) -> None: handles, labels = self.ax.get_legend_handles_labels() self.ax.legend(handles, labels) - def __repr__(self): + def __repr__(self) -> str: if len(self.spectra) > 0: return "Spectra plot ({})".format(", ".join([str(x[0]) for x in self.spectra])) else: diff --git a/src/genie_python/genie_pre_post_cmd_manager.py b/src/genie_python/genie_pre_post_cmd_manager.py index 0b7bc52d..5f528968 100644 --- a/src/genie_python/genie_pre_post_cmd_manager.py +++ b/src/genie_python/genie_pre_post_cmd_manager.py @@ -1,8 +1,7 @@ -from builtins import object from typing import Any -class PrePostCmdManager(object): +class PrePostCmdManager: """ A class to manager the precmd and postcmd commands such as used in begin, end, abort, resume, pause. diff --git a/src/genie_python/genie_pv_connection_protocol.py b/src/genie_python/genie_pv_connection_protocol.py index 9b144dfe..9cf75153 100644 --- a/src/genie_python/genie_pv_connection_protocol.py +++ b/src/genie_python/genie_pv_connection_protocol.py @@ -1,5 +1,5 @@ from collections.abc import Callable -from typing import Optional, Protocol, Tuple, runtime_checkable +from typing import Protocol, runtime_checkable from ibex_non_ca_helpers import PVValue @@ -20,7 +20,7 @@ def get_pv_value( ) -> "PVValue": ... @staticmethod - def get_pv_timestamp(name: str, timeout: float) -> Tuple[int, int]: ... + def get_pv_timestamp(name: str, timeout: float) -> tuple[int, int]: ... @staticmethod def pv_exists(name: str, timeout: float) -> bool: ... @@ -28,7 +28,7 @@ def pv_exists(name: str, timeout: float) -> bool: ... @staticmethod def add_monitor( name: str, - call_back_function: "Callable[[PVValue, Optional[str], Optional[str]], None]", + call_back_function: "Callable[[PVValue, str | None, str | None], None]", link_alarm_on_disconnect: bool = True, to_string: bool = False, use_numpy: bool | None = None, diff --git a/src/genie_python/genie_script_checker.py b/src/genie_python/genie_script_checker.py index a558c5ab..092a8a1a 100644 --- a/src/genie_python/genie_script_checker.py +++ b/src/genie_python/genie_script_checker.py @@ -1,6 +1,5 @@ -from __future__ import absolute_import, print_function - import ast +import builtins import json import os import re @@ -9,16 +8,16 @@ import sys import sysconfig import unicodedata -from builtins import object -from io import StringIO, open -from typing import Iterable +from collections.abc import Iterable +from io import StringIO +from types import TracebackType from astroid import MANAGER, nodes from pylint.lint import Run from pylint.reporters.text import TextReporter -class ScriptChecker(object): +class ScriptChecker: """ Check Scripts for common errors """ @@ -47,11 +46,11 @@ def _check_g_inst_name(self, line: str, line_no: int) -> str: """ g_error = re.search(self._find_regex("g"), line) if g_error: - return "W: {line_no}: 'g' assignment in line {line_no}".format(line_no=line_no) + return f"W: {line_no}: 'g' assignment in line {line_no}" inst_error = re.search(self._find_regex("inst"), line) if inst_error: - return "W: {line_no}: 'inst' assignment in line {line_no}".format(line_no=line_no) + return f"W: {line_no}: 'inst' assignment in line {line_no}" return "" @@ -63,9 +62,7 @@ def check_script_lines(self, lines: Iterable[str]) -> list[str]: Returns: error in the script; empty list if none """ reassignment_warnings = [] - line_no = 0 - for line in lines: - line_no += 1 + for line_no, line in enumerate(lines, 1): warning = self._check_g_inst_name(line, line_no) if len(warning) != 0: reassignment_warnings.append(warning) @@ -90,7 +87,7 @@ def _can_cache_module(self, module_name: str, module: nodes.Module) -> bool: # Allow modules defined in the site packages directories to be cached as # they are unlikely to change at runtime - if module.file is not None and any( + if module.file is not None and any( # ruff: ignore[SIM103] module.file.startswith(site_package_dir) for site_package_dir in site.getsitepackages() ): return True @@ -163,10 +160,15 @@ def __init__( def __enter__(self) -> None: self._filename = os.path.join(self.config_path, self.config_name) - with open(self._filename, "w") as f: + with builtins.open(self._filename, "w") as f: f.write(json.dumps(self.json_write)) - def __exit__(self, exc_type: None, exc_value: None, exc_traceback: None) -> None: + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + exc_traceback: TracebackType | None, + ) -> None: os.unlink(self._filename) def pyright_script_checker( @@ -204,7 +206,9 @@ def pyright_script_checker( script_path, ] - pr_result = subprocess.run(args=cmd, capture_output=True, text=True, encoding="utf-8") + pr_result = subprocess.run( + args=cmd, capture_output=True, text=True, encoding="utf-8", check=False + ) json_out = unicodedata.normalize("NFKD", pr_result.stdout) try: @@ -221,16 +225,20 @@ def pyright_script_checker( # add message to error array else add to warning array for diagnostic in json_data["generalDiagnostics"]: start = diagnostic["range"]["start"] - if not diagnostic["rule"] == "reportUndefinedVariable": ### CHANGE + if diagnostic["rule"] != "reportUndefinedVariable": ### CHANGE if diagnostic["severity"] == "error": errors += [ - f"E: {start['line'] + 1}: " - f"{diagnostic['message']} [{diagnostic['rule']}]" + ( + f"E: {start['line'] + 1}: " + f"{diagnostic['message']} [{diagnostic['rule']}]" + ) ] else: warnings += [ - f"W: {start['line'] + 1}: " - f"{diagnostic['message']} [{diagnostic['rule']}]" + ( + f"W: {start['line'] + 1}: " + f"{diagnostic['message']} [{diagnostic['rule']}]" + ) ] return warnings, errors @@ -298,7 +306,7 @@ def check_script( elif not no_pylint: pylint_path = os.path.join(dir_path, ".pylintrc") - with open(script_name) as f: + with builtins.open(script_name) as f: reassignment_warnings = self.check_script_lines(f) warnings.extend(reassignment_warnings) @@ -307,9 +315,9 @@ def check_script( ) init_hook = ( "import sys;" - 'sys.path.append("{}");' + f'sys.path.append("{inst_file_path}");' 'sys.path.append("U:\\scripts");' - 'sys.path.append("C:\\Instrument\\scripts");'.format(inst_file_path) + 'sys.path.append("C:\\Instrument\\scripts");' ) init_hook = init_hook.replace("\\", "\\\\") inst_scripts_file_path = os.path.join(inst_file_path, "inst") @@ -323,7 +331,7 @@ def check_script( return [ "Error while getting attributes of instrument scripts. Please check " + os.path.join(inst_scripts_file_path, e_filename) - + ": {}".format(e) + + f": {e}" ] # C = Convention related checks, @@ -334,10 +342,10 @@ def check_script( # --msg-template={msg_id}:{line:3d},{column}: {obj}: {msg} for more specific message Run( [ - "--rcfile={}".format(pylint_path), - "--init-hook={}".format(init_hook), + f"--rcfile={pylint_path}", + f"--init-hook={init_hook}", "--msg-template={C}:{line:3d}: {msg} ({symbol})", - "--generated-members={}".format(functions), + f"--generated-members={functions}", "--score=n", script_name, ], @@ -370,7 +378,7 @@ def check_script( error_line_numbers.append(int(selected_number)) lines_containing_errors = [] - with open(script_name) as f: + with builtins.open(script_name) as f: content = f.read() lines_list = content.splitlines() @@ -432,7 +440,7 @@ def get_inst_attributes(self, instrument_scripts_paths: str) -> str | list[str]: attributes = "" for filename in os.listdir(instrument_scripts_paths): if filename.endswith(".py") and not filename.startswith("__"): - with open(os.path.join(instrument_scripts_paths, filename)) as f: + with builtins.open(os.path.join(instrument_scripts_paths, filename)) as f: src = f.read() tree = ast.parse(src, filename) attributes += self.get_all_attributes(tree) @@ -460,11 +468,11 @@ def get_names_of_functions_classes_global_variables(self, body: list[ast.stmt]) for item in body: # getting functions in global scope if isinstance(item, ast.FunctionDef) and not item.name.startswith("_"): - attributes += "inst.{function_name},".format(function_name=item.name) + attributes += f"inst.{item.name}," # getting class and its attributes elif isinstance(item, ast.ClassDef): - class_name = "inst.{class_name}".format(class_name=item.name) - attributes += "{class_name},".format(class_name=class_name) + class_name = f"inst.{item.name}" + attributes += f"{class_name}," attributes += self.get_class_member_names(item.body, class_name) elif isinstance(item, ast.Assign): for target in item.targets: @@ -474,13 +482,13 @@ def get_names_of_functions_classes_global_variables(self, body: list[ast.stmt]) def parse_assignment_target(self, target: ast.AST, descendants: str = "") -> str: if isinstance(target, ast.Name): - return "inst.{}{},".format(target.id, descendants) + return f"inst.{target.id}{descendants}," if sys.version_info[0] >= 3 and isinstance(target, ast.Starred): return self.parse_assignment_target(target.value, descendants=descendants) if isinstance(target, ast.Attribute): - descendants = ".{}{}".format(target.attr, descendants) + descendants = f".{target.attr}{descendants}" return self.parse_assignment_target(target.value, descendants=descendants) if isinstance(target, (ast.List, ast.Tuple)): @@ -503,16 +511,12 @@ def get_class_member_names(self, body: list[ast.stmt], class_name: str) -> str: for function_body in body: if isinstance(function_body, ast.FunctionDef): if "__" not in function_body.name: - attributes += "{class_name}.{function_name},".format( - class_name=class_name, function_name=function_body.name - ) + attributes += f"{class_name}.{function_body.name}," else: # variables defined inside __init__ for variables in function_body.body: if isinstance(variables, ast.Assign) and isinstance( variables.targets[0], ast.Attribute ): - attributes += "{class_name}.{variable_name},".format( - class_name=class_name, variable_name=variables.targets[0].attr - ) + attributes += f"{class_name}.{variables.targets[0].attr}," return attributes diff --git a/src/genie_python/genie_script_generator.py b/src/genie_python/genie_script_generator.py index 54917aaa..5561e299 100644 --- a/src/genie_python/genie_script_generator.py +++ b/src/genie_python/genie_script_generator.py @@ -1,8 +1,8 @@ from abc import ABCMeta, abstractmethod -from builtins import object from collections import OrderedDict +from collections.abc import Callable from functools import wraps -from typing import Any, Callable, Type +from typing import Any class GlobalParamValidationError(Exception): @@ -10,10 +10,8 @@ class GlobalParamValidationError(Exception): An error to throw when a global parameter is invalid """ - pass - -class CopyPreviousRow(object): +class CopyPreviousRow: """ Class to use when an action should copy its previous row's value when a new action is added. Examples: @@ -23,11 +21,11 @@ class CopyPreviousRow(object): >>> def run(self, temperature=CopyPreviousRow(1.0), field=1.0, mevents=10) """ - def __init__(self, default_value: bool | int | float | str) -> None: + def __init__(self, default_value: bool | float | str) -> None: self.value = default_value -class ScriptDefinition(object, metaclass=ABCMeta): +class ScriptDefinition(metaclass=ABCMeta): global_params: dict[str, Any] | None @abstractmethod @@ -51,7 +49,6 @@ def run(self, **kwargs) -> str | None: # noqa:ANN003 - abstract, can be overrid >>> @cast_parameters_to(temperature=float, field=float, uamps=mytype) >>> def run(self, temperature=0.0, field=0.0, uamps=0.0): """ - pass @abstractmethod def parameters_valid(self, **kwargs) -> str | None: # noqa:ANN003 - abstract, can be overridden to any type in subclass @@ -74,7 +71,6 @@ def parameters_valid(self, **kwargs) -> str | None: # noqa:ANN003 - abstract, c >>> @cast_parameters_to(temperature=float, field=float, uamps=mytype) >>> def parameters_valid(self, temperature=0.0, field=0.0, uamps=0.0): """ - pass def estimate_time(self, **kwargs) -> float | None: # noqa:ANN003 - abstract, can be overridden to any type in subclass """ @@ -131,7 +127,7 @@ def get_file(self) -> str: def cast_parameters_to( - *args_casts: Type | Callable, **keyword_casts: Type | Callable + *args_casts: type | Callable, **keyword_casts: type | Callable ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: """ A decorator to cast args and kwargs using the passed casters. @@ -188,8 +184,8 @@ def wrapper(self: ScriptDefinition, *args: Any, **kwargs: Any) -> Any: if str(e) != "": casting_failures += str(e) + "\n" else: - casting_failures += "Cannot convert {} from string to {}\n".format( - str(v), str(keyword_casts[k].__name__) + casting_failures += ( + f"Cannot convert {v!s} from string to {keyword_casts[k].__name__!s}\n" ) # Cast args cast_args = [] @@ -200,8 +196,8 @@ def wrapper(self: ScriptDefinition, *args: Any, **kwargs: Any) -> Any: if str(e) != "": casting_failures += str(e) + "\n" else: - casting_failures += "Cannot convert {} from string to {}\n".format( - args[i], str(args_casts[i].__name__) + casting_failures += ( + f"Cannot convert {args[i]} from string to {args_casts[i].__name__!s}\n" ) if casting_failures != "": return casting_failures diff --git a/src/genie_python/genie_simulate.py b/src/genie_python/genie_simulate.py index eacfff2f..b9eb0c6c 100644 --- a/src/genie_python/genie_simulate.py +++ b/src/genie_python/genie_simulate.py @@ -1,13 +1,12 @@ -from __future__ import absolute_import, print_function - -from builtins import object from collections import OrderedDict +from types import TracebackType +from typing import Any import genie_python.genie_api_setup import genie_python.genie_simulate_impl as genie_sim -def in_sim_mode(): +def in_sim_mode() -> bool: """ Get whether genie_python is in simulation mode. @@ -18,7 +17,7 @@ def in_sim_mode(): return isinstance(_genie_api, genie_sim.API) -class Simulate(object): +class Simulate: """A context manager that will put genie_python into simulation mode and out again. For example: >>> with g.sim.Simulate(): @@ -28,17 +27,25 @@ class Simulate(object): Will run only print the results of the g. commands and not change the current experiment. """ - def __init__(self, populate_with_current_blocks=True, initial_block_values={}): + def __init__( + self, + populate_with_current_blocks: bool = True, + initial_block_values: dict[str, Any] | None = None, + ) -> None: """ Create the context manager. Args: - populate_with_current_blocks(bool): if True the simulated environment will be populated with the current blocks - initial_block_values(dict): If not empty, blocks will be set initially to these values; can be plain values or cget results + populate_with_current_blocks: if True the simulated environment will be + populated with the current blocks + initial_block_values: If not empty, blocks will be set initially to these + values; can be plain values or cget results """ from genie_python import genie from genie_python.genie import _genie_api + if initial_block_values is None: + initial_block_values = {} self.genie = genie self.previous_api = _genie_api self.new_api = genie_sim.API(None, None, populate_with_current_blocks) @@ -53,7 +60,7 @@ def __init__(self, populate_with_current_blocks=True, initial_block_values={}): else: self.new_api.set_block_value(ky, vl) - def __enter__(self): + def __enter__(self) -> None: print("Entering genie_python simulation mode") print( "For more information on simulation mode see https://github.com/ISISComputingGroup/ibex_user_manual/wiki/Simulating-Scripts" @@ -62,7 +69,12 @@ def __enter__(self): self.genie._genie_api = self.new_api self.previous_api.logger.set_sim_mode(in_sim_mode()) - def __exit__(self, *args): + def __exit__( + self, + _type: type[BaseException] | None, + _value: BaseException | None, + _traceback: TracebackType | None, + ) -> bool | None: print("Exiting genie_python simulation mode") genie_python.genie_api_setup._exceptions_raised = False self.genie._genie_api = self.previous_api diff --git a/src/genie_python/genie_simulate_impl.py b/src/genie_python/genie_simulate_impl.py index aa70ecfb..a5289f8d 100644 --- a/src/genie_python/genie_simulate_impl.py +++ b/src/genie_python/genie_simulate_impl.py @@ -1,14 +1,12 @@ -from __future__ import absolute_import, print_function - import inspect import os import socket import typing import xml.etree.ElementTree as ET -from builtins import object, str from collections import OrderedDict +from collections.abc import Callable from datetime import timedelta -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING import numpy as np import numpy.typing as npt @@ -27,7 +25,7 @@ ) -class Waitfor(object): +class Waitfor: def __init__(self) -> None: pass @@ -54,9 +52,7 @@ def start_waiting( frame = inspect.currentframe() args, _, _, values = inspect.getargvalues(frame) wait_str = [ - "{}={}".format(arg, values[arg]) - for arg in args - if values[arg] is not None and arg != "self" + f"{arg}={values[arg]}" for arg in args if values[arg] is not None and arg != "self" ] print("Waiting for {}".format(", ".join(wait_str))) @@ -64,12 +60,12 @@ def wait_for_runstate( self, state: str, maxwaitsecs: int = 3600, onexit: bool = False, quiet: bool = False ) -> None: if onexit: - print("Waiting to exit state {}".format(state)) + print(f"Waiting to exit state {state}") else: - print("Waiting for state {}".format(state)) + print(f"Waiting for state {state}") -class WaitForMoveController(object): +class WaitForMoveController: def __init__(self) -> None: pass @@ -88,7 +84,7 @@ def wait_for_start(self, timeout: float | None, check_for_move: bool) -> None: pass -class ChangeCache(object): +class ChangeCache: def __init__(self) -> None: self.wiring = None self.detector = None @@ -204,10 +200,10 @@ def _change_tcb_table(self, root: ET.Element, changed: bool) -> bool: for row in self.tcb_tables: regime = str(row[0]) trange = str(row[1]) - self._change_xml(root, "DBL", "TR%s From %s" % (regime, trange), row[2]) - self._change_xml(root, "DBL", "TR%s To %s" % (regime, trange), row[3]) - self._change_xml(root, "DBL", "TR%s Steps %s" % (regime, trange), row[4]) - self._change_xml(root, "U16", "TR%s In Mode %s" % (regime, trange), row[5]) + self._change_xml(root, "DBL", f"TR{regime} From {trange}", row[2]) + self._change_xml(root, "DBL", f"TR{regime} To {trange}", row[3]) + self._change_xml(root, "DBL", f"TR{regime} Steps {trange}", row[4]) + self._change_xml(root, "U16", f"TR{regime} In Mode {trange}", row[5]) changed = True return changed @@ -244,33 +240,30 @@ def _change_period_table(self, root: ET.Element, changed: bool) -> bool: output = row[3] label = row[4] if ptype is not None: - self._change_xml(root, "EW", "Type %s" % period, ptype) + self._change_xml(root, "EW", f"Type {period}", ptype) changed = True if frames is not None: - self._change_xml(root, "I32", "Frames %s" % period, frames) + self._change_xml(root, "I32", f"Frames {period}", frames) changed = True if output is not None: - self._change_xml(root, "U16", "Output %s" % period, output) + self._change_xml(root, "U16", f"Output {period}", output) changed = True if label is not None: - self._change_xml(root, "String", "Label %s" % period, label) + self._change_xml(root, "String", f"Label {period}", label) changed = True return changed - def _change_xml( - self, xml: ET.Element, node: str, name: str, value: str | int | float | None - ) -> None: + def _change_xml(self, xml: ET.Element, node: str, name: str, value: str | float | None) -> None: for top in xml.iter(node): n = top.find("Name") - if n is not None: - if n.text == name: - v = top.find("Val") - if v is not None: - v.text = str(value) - break + if n is not None and n.text == name: + v = top.find("Val") + if v is not None: + v.text = str(value) + break -class Dae(object): +class Dae: def __init__(self) -> None: self.run_state = "SETUP" self.run_number = "123456" @@ -298,7 +291,7 @@ def __init__(self) -> None: self.detector_tables = [""] self.tcb_tables = [] self.period_files = [] - self.spectrum: "_GetspectrumReturn" = { + self.spectrum: _GetspectrumReturn = { "time": [1.0], "signal": [1.0], "sum": None, @@ -420,7 +413,7 @@ def update_store_run(self) -> None: if self.run_state == "RUNNING" or self.run_state == "PAUSED": pass else: - raise Exception("Can only be called when RUNNING or PAUSED") + raise ValueError("Can only be called when RUNNING or PAUSED") def update_run(self) -> None: """ @@ -429,7 +422,7 @@ def update_run(self) -> None: if self.run_state == "RUNNING" or self.run_state == "PAUSED": pass else: - raise Exception("Can only be called when RUNNING or PAUSED") + raise ValueError("Can only be called when RUNNING or PAUSED") @require_runstate(["RUNNING", "VETOING", "WAITING", "PAUSED"]) def store_run(self) -> None: @@ -439,7 +432,7 @@ def recover_run(self) -> None: if self.run_state == "SETUP": pass else: - raise Exception("Can only be called when SETUP") + raise ValueError("Can only be called when SETUP") def post_recover_check(self, verbose: bool = False) -> None: pass @@ -481,7 +474,7 @@ def set_period(self, period: int) -> None: if period <= self.num_periods: self.period_current = period else: - raise Exception("Cannot set period as it is higher than the number of periods") + raise ValueError("Cannot set period as it is higher than the number of periods") def get_uamps(self, period: bool = False) -> float: """Returns the current number of micro-amp hours. @@ -512,7 +505,7 @@ def set_title(self, title: str) -> None: Args: title: String: the new title """ - print("Setting title to {}".format(title)) + print(f"Setting title to {title}") self.title_current = title def get_display_title(self) -> bool: @@ -529,7 +522,7 @@ def set_display_title(self, display_title: bool) -> None: Args: display_title: boolean: the new display title status """ - print("Setting title display status to {}".format(display_title)) + print(f"Setting title display status to {display_title}") self.display_title = display_title def get_rb_number(self) -> str: @@ -600,9 +593,9 @@ def change_start(self) -> None: """ # Check in setup if self.get_run_state() != "SETUP": - raise Exception("Must be in SETUP before starting change!") + raise ValueError("Must be in SETUP before starting change!") if self.in_change: - raise Exception("Already in change - previous cached values will be used") + raise ValueError("Already in change - previous cached values will be used") else: self.in_change = True self.change_cache = ChangeCache() @@ -669,7 +662,6 @@ def change_monitor(self, spec: int, low: float, high: float) -> None: if not self.in_change: self.change_start() did_change = True - pass if did_change: self.change_finish() @@ -736,7 +728,7 @@ def configure_hard_periods( self.change_cache.periods_type = 1 if period_file is not None: if not os.path.exists(period_file): - raise Exception("Period file could not be found") + raise OSError("Period file could not be found") self.change_cache.periods_src = 1 self.change_cache.periods_file = period_file else: @@ -746,7 +738,7 @@ def configure_hard_periods( elif mode.strip().lower() == "ext": self.change_cache.periods_type = 2 else: - raise Exception('Period mode invalid, it should be "int" or "ext"') + raise ValueError('Period mode invalid, it should be "int" or "ext"') if did_change: self.change_finish() @@ -788,12 +780,12 @@ def configure_internal_periods( if isinstance(sequences, int): self.change_cache.periods_seq = sequences else: - raise Exception("Number of period sequences must be an integer") + raise TypeError("Number of period sequences must be an integer") if output_delay is not None: if isinstance(output_delay, int): self.change_cache.periods_delay = output_delay else: - raise Exception("Output delay of periods must be an integer (microseconds)") + raise TypeError("Output delay of periods must be an integer (microseconds)") self.define_hard_period(period, daq, dwell, unused, frames, output, label) if did_change: self.change_finish() @@ -852,7 +844,7 @@ def define_hard_period( (period, p_type, p_frames, p_output, p_label) ) else: - raise Exception("Period number must be an integer from 1 to 8") + raise ValueError("Period number must be an integer from 1 to 8") if did_change: self.change_finish() @@ -904,7 +896,7 @@ def change_sync(self, source: str) -> None: elif source == "isis (first ts1)": value = 5 else: - raise Exception("Invalid timing source entered, try help(change_sync)!") + raise ValueError("Invalid timing source entered, try help(change_sync)!") self.change_cache.dae_sync = value if did_change: self.change_finish() @@ -925,9 +917,9 @@ def change_tcb_file(self, tcb_file: str | None = None, default: bool = False) -> elif default: tcb_file = "c:\\labview modules\\dae\\tcb.dat" else: - raise Exception("No tcb file specified") + raise ValueError("No tcb file specified") if not os.path.exists(tcb_file): - raise Exception("Tcb file could not be found") + raise OSError("Tcb file could not be found") self.change_cache.tcb_file = tcb_file if did_change: self.change_finish() @@ -987,30 +979,22 @@ def change_vetos(self, **params: bool) -> None: if not self.in_change: self.change_start() did_change = True - if "clearall" in params: - if isinstance(params["clearall"], bool): - self.change_cache.clear_vetos() - if "smp" in params: - if isinstance(params["smp"], bool): - self.change_cache.smp_veto = int(params["smp"]) - if "ts2" in params: - if isinstance(params["ts2"], bool): - self.change_cache.ts2_veto = int(params["ts2"]) - if "hz50" in params: - if isinstance(params["hz50"], bool): - self.change_cache.hz50_veto = int(params["hz50"]) - if "ext0" in params: - if isinstance(params["ext0"], bool): - self.change_cache.ext0_veto = int(params["ext0"]) - if "ext1" in params: - if isinstance(params["ext1"], bool): - self.change_cache.ext1_veto = int(params["ext1"]) - if "ext2" in params: - if isinstance(params["ext2"], bool): - self.change_cache.ext2_veto = int(params["ext2"]) - if "ext3" in params: - if isinstance(params["ext3"], bool): - self.change_cache.ext3_veto = int(params["ext3"]) + if "clearall" in params and isinstance(params["clearall"], bool): + self.change_cache.clear_vetos() + if "smp" in params and isinstance(params["smp"], bool): + self.change_cache.smp_veto = int(params["smp"]) + if "ts2" in params and isinstance(params["ts2"], bool): + self.change_cache.ts2_veto = int(params["ts2"]) + if "hz50" in params and isinstance(params["hz50"], bool): + self.change_cache.hz50_veto = int(params["hz50"]) + if "ext0" in params and isinstance(params["ext0"], bool): + self.change_cache.ext0_veto = int(params["ext0"]) + if "ext1" in params and isinstance(params["ext1"], bool): + self.change_cache.ext1_veto = int(params["ext1"]) + if "ext2" in params and isinstance(params["ext2"], bool): + self.change_cache.ext2_veto = int(params["ext2"]) + if "ext3" in params and isinstance(params["ext3"], bool): + self.change_cache.ext3_veto = int(params["ext3"]) if did_change: self.change_finish() @@ -1023,11 +1007,11 @@ def set_fermi_veto(self, enable: bool | None, delay: float = 1.0, width: float = width - the veto width """ if not isinstance(enable, bool): - raise Exception("Fermi veto: enable must be a boolean value") + raise TypeError("Fermi veto: enable must be a boolean value") if not isinstance(delay, float) and not isinstance(delay, int): - raise Exception("Fermi veto: delay must be a numeric value") + raise TypeError("Fermi veto: delay must be a numeric value") if not isinstance(width, float) and not isinstance(width, int): - raise Exception("Fermi veto: width must be a numeric value") + raise TypeError("Fermi veto: width must be a numeric value") did_change = False if not self.in_change: self.change_start() @@ -1048,7 +1032,7 @@ def set_num_soft_periods(self, number: int) -> None: number - the number of periods to create """ if not isinstance(number, float) and not isinstance(number, int): - raise Exception("Number of soft periods must be a numeric value") + raise TypeError("Number of soft periods must be a numeric value") did_change = False if not self.in_change: self.change_start() @@ -1100,7 +1084,7 @@ def set_autosave_freq(self, freq: int) -> None: self.autosave_freq = freq -class API(object): +class API: def __init__( self, pv_prefix: str | None = None, globs: dict | None = None, strict_block: bool = True ) -> None: @@ -1115,7 +1099,7 @@ def __init__( """ self.inst_prefix = None self.pre_post_cmd_manager = PrePostCmdManager() - self.block_dict = dict() + self.block_dict = {} self.num_periods = 1 self.run_number = "123456" self.waitfor = Waitfor() @@ -1148,10 +1132,7 @@ def set_pv_value( ) -> None: if is_local: name = self.prefix_pv_name(name) - print( - "set_pv_value called (name=%s value=%s wait=%s is_local=%s)" - % (name, value, wait, is_local) - ) + print(f"set_pv_value called (name={name} value={value} wait={wait} is_local={is_local})") def get_pv_value( self, @@ -1164,8 +1145,8 @@ def get_pv_value( if is_local: name = self.prefix_pv_name(name) print( - "get_pv_value called (name=%s value=%s attempts=%s is_local=%s use_numpy=%s)" - % (name, to_string, attempts, is_local, use_numpy) + f"get_pv_value called (name={name} value={to_string} " + f"attempts={attempts} is_local={is_local} use_numpy={use_numpy})" ) def pv_exists(self, name: str, is_local: bool = False) -> bool: @@ -1180,11 +1161,11 @@ def reload_current_config(self) -> None: def correct_blockname(self, name: str, add_prefix: bool = True) -> str: return name - def get_block_names(self) -> list(): + def get_block_names(self) -> []: return list(self.block_dict.keys()) def block_exists(self, name: str) -> bool: - return name in self.block_dict.keys() if self.strict_block else True + return name in self.block_dict if self.strict_block else True def set_block_value( self, @@ -1208,7 +1189,7 @@ def set_block_value( wait (boolean): pause execution until setpoint is reached (one block only) """ - print("Setting {} to value {}".format(name, value)) + print(f"Setting {name} to value {value}") if name not in self.block_dict: self.block_dict[name] = { "value": value, @@ -1273,7 +1254,7 @@ def get_beamline_pars(self) -> "_GetbeamlineparsReturn": def set_beamline_par(self, name: str, value: "PVValue") -> None: self.beamline_pars[name] = value - def get_runcontrol_settings(self, name: str) -> tuple(): + def get_runcontrol_settings(self, name: str) -> (): return ( self.block_dict[name]["runcontrol"], self.block_dict[name]["lowlimit"], @@ -1281,13 +1262,13 @@ def get_runcontrol_settings(self, name: str) -> tuple(): ) def check_alarms(self, blocks: tuple[str, ...]) -> tuple[list[str], list[str], list[str]]: - minor = list() - major = list() - invalid = list() + minor = [] + major = [] + invalid = [] return (minor, major, invalid) def check_limit_violations(self, blocks: tuple[str, ...]) -> list: - return list() + return [] def get_current_block_values(self) -> dict: """ @@ -1306,13 +1287,13 @@ def get_current_block_values(self) -> dict: return block_values def send_sms(self, phone_num: str, message: str) -> None: - print(('SMS "{}" sent to {}'.format(message, phone_num))) + print(f'SMS "{message}" sent to {phone_num}') def send_email(self, address: str, message: str) -> None: - print(('Email "{}" sent to {}'.format(message, address))) + print(f'Email "{message}" sent to {address}') def send_alert(self, message: str, inst: str | None) -> None: - print(('Slack message "{}" sent to {}'.format(message, inst))) + print(f'Slack message "{message}" sent to {inst}') def get_alarm_from_block(self, block: str) -> str: return "NO_ALARM" diff --git a/src/genie_python/genie_startup.py b/src/genie_python/genie_startup.py index abec68f4..573ae5d1 100644 --- a/src/genie_python/genie_startup.py +++ b/src/genie_python/genie_startup.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import ctypes import os import sys diff --git a/src/genie_python/genie_toggle_settings.py b/src/genie_python/genie_toggle_settings.py index 9ef9372e..cf25d175 100644 --- a/src/genie_python/genie_toggle_settings.py +++ b/src/genie_python/genie_toggle_settings.py @@ -4,8 +4,6 @@ This module is used for storing and updating user preferences and settings. """ -from __future__ import absolute_import, print_function - import genie_python.genie_api_setup # for _exceptions_raised from genie_python.genie_api_setup import helparglist, log_command_and_handle_exception, usercommand @@ -33,7 +31,7 @@ def exceptions_raised(toggle_on: bool) -> None: """ genie_python.genie_api_setup._exceptions_raised = toggle_on # noinspection PyProtectedMember - print("Raise exceptions set to {}.".format(genie_python.genie_api_setup._exceptions_raised)) + print(f"Raise exceptions set to {genie_python.genie_api_setup._exceptions_raised}.") @usercommand @@ -52,4 +50,4 @@ def cset_verbose(verbose: bool) -> None: >>> cset_verbose(True) """ ToggleSettings.cset_verbose = verbose - print("Default cset verbosity set to {}.".format(ToggleSettings.cset_verbose)) + print(f"Default cset verbosity set to {ToggleSettings.cset_verbose}.") diff --git a/src/genie_python/genie_wait_for_move.py b/src/genie_python/genie_wait_for_move.py index 5548017d..86c4ee6f 100644 --- a/src/genie_python/genie_wait_for_move.py +++ b/src/genie_python/genie_wait_for_move.py @@ -10,7 +10,8 @@ # “alldone” indicates when any motion has completed and “moving” gives a count of moving motors. import time -from typing import TYPE_CHECKING, Callable, Iterable +from collections.abc import Callable, Iterable +from typing import TYPE_CHECKING from genie_python.utilities import check_break @@ -18,13 +19,13 @@ from genie_python.genie_epics_api import API -class WaitForMoveController(object): +class WaitForMoveController: def __init__(self, api: "API", motion_pv: str) -> None: self._api = api self._motion_pv = motion_pv self._polling_delay = 0.02 self._wait_succeeded = False - self._missing_blocks = list() + self._missing_blocks = [] def wait(self, start_timeout: float | None = None, move_timeout: float | None = None) -> None: """Wait for motor motion to complete. @@ -127,10 +128,10 @@ def _check_specific_motion(self, blocks: Iterable[str]) -> bool: # DMOV = 0 when moving try: moving = self._api.get_pv_value(block_pv + ":DMOV", attempts=1) == 0 - except IOError: + except OSError: # Could not find block so don't try it again - self._api.logger.log_info_msg("WAITFOR_MOVE DISCONNECTED BLOCK: {}".format(block)) - print("Could not connect to block {} so ignoring it".format(block)) + self._api.logger.log_info_msg(f"WAITFOR_MOVE DISCONNECTED BLOCK: {block}") + print(f"Could not connect to block {block} so ignoring it") self._missing_blocks.append(block) moving = False if moving: @@ -143,24 +144,24 @@ def _flag_error_conditions(self, blocks: Iterable[str]) -> None: filtered_blocks = self._filter_out_missing_blocks(blocks) # Check alarms - minor, major, invalid = self._api.check_alarms(filtered_blocks) + minor, major, _invalid = self._api.check_alarms(filtered_blocks) for i in major: - self._api.logger.log_info_msg("WAITFOR_MOVE BLOCK %s IN MAJOR ALARM" % i) - print("Block %s is in alarm: MAJOR" % i) + self._api.logger.log_info_msg(f"WAITFOR_MOVE BLOCK {i} IN MAJOR ALARM") + print(f"Block {i} is in alarm: MAJOR") for i in minor: - self._api.logger.log_info_msg("WAITFOR_MOVE BLOCK %s IN MINOR ALARM" % i) - print("Block %s is in alarm state: MINOR" % i) + self._api.logger.log_info_msg(f"WAITFOR_MOVE BLOCK {i} IN MINOR ALARM") + print(f"Block {i} is in alarm state: MINOR") # Check soft limit violations violations = self._api.check_limit_violations(filtered_blocks) for i in violations: - self._api.logger.log_info_msg("WAITFOR_MOVE BLOCK %s HAS SOFT LIMIT VIOLATIONS" % i) - print("Block %s has soft limit violations" % i) + self._api.logger.log_info_msg(f"WAITFOR_MOVE BLOCK {i} HAS SOFT LIMIT VIOLATIONS") + print(f"Block {i} has soft limit violations") # Print missing blocks for i in self._missing_blocks: - self._api.logger.log_info_msg("WAITFOR_MOVE BLOCK %s COULD NOT BE FOUND" % i) - print("Block %s could not be found" % i) + self._api.logger.log_info_msg(f"WAITFOR_MOVE BLOCK {i} COULD NOT BE FOUND") + print(f"Block {i} could not be found") def _filter_out_missing_blocks(self, blocks: Iterable[str]) -> list[str]: filtered_blocks = [] diff --git a/src/genie_python/genie_waitfor.py b/src/genie_python/genie_waitfor.py index 9c3e8f85..1182858d 100644 --- a/src/genie_python/genie_waitfor.py +++ b/src/genie_python/genie_waitfor.py @@ -2,18 +2,17 @@ Classes allowing you to wait for states """ -from __future__ import absolute_import, print_function - from abc import ABCMeta, abstractmethod -from builtins import object, str +from collections.abc import Callable from datetime import datetime, timedelta from time import sleep, strptime -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING from genie_python.utilities import check_break, get_time_delta if TYPE_CHECKING: from ibex_non_ca_helpers import PVValue + from genie_python.genie_epics_api import API NUMERIC_TYPE = (float, int) @@ -31,7 +30,7 @@ # When the exception disappears pv reconnected is printed once -class WaitForControllerState(object, metaclass=ABCMeta): +class WaitForControllerState(metaclass=ABCMeta): """ The abstract class for the exception state of the WaitForController when it is in the loop in the start_waiting method. @@ -99,7 +98,6 @@ def handle_exception_notification(self, exception: Exception) -> "WaitForControl :param exception: The exception to handle :return: The state the context is now in (could be the same or a transition) """ - pass @abstractmethod def exception_cleared(self) -> "WaitForControllerState|None": @@ -107,7 +105,6 @@ def exception_cleared(self) -> "WaitForControllerState|None": When there is no exception handle it. :return: The state the context is now in (could be the same or a transition) """ - pass @abstractmethod def enter(self) -> None: @@ -115,7 +112,6 @@ def enter(self) -> None: When this state is first entered handle it. :return: None """ - pass class WaitForControllerExceptionState(WaitForControllerState): @@ -160,8 +156,9 @@ def print_exception(self, exception: Exception | None) -> None: :param exception: The exception to print :return: None """ - message = "{}: Exception in waitfor loop: {}: {}".format( - str(datetime.now()), exception.__class__.__name__, exception + message = ( + f"{datetime.now()!s}: " + f"Exception in waitfor loop: {exception.__class__.__name__}: {exception}" ) print(message) self._api.logger.log_info_msg(message) @@ -204,7 +201,7 @@ def print_exception_cleared(self) -> None: Print to the console that the pv is connected. :return: None """ - message = "{}: Exception cleared".format(str(datetime.now())) + message = f"{datetime.now()!s}: Exception cleared" print(message) self._api.logger.log_info_msg(message) @@ -216,7 +213,7 @@ def enter(self) -> None: self.print_exception_cleared() -class WaitForControllerExceptionContext(object): +class WaitForControllerExceptionContext: """ The exception context of the WaitForController when it is in the loop in the start_waiting method @@ -240,7 +237,7 @@ def process_exception(self, exception: Exception | None) -> None: self._state = self._state.process_exception(exception) -class WaitForController(object): +class WaitForController: """ Controller for waiting for states """ @@ -257,8 +254,8 @@ def start_waiting( self, block: str | None = None, value: "PVValue | None" = None, - lowlimit: float | int | None = None, - highlimit: float | int | None = None, + lowlimit: float | None = None, + highlimit: float | None = None, maxwait: float | None = None, wait_all: bool = False, seconds: float | None = None, @@ -307,15 +304,15 @@ def start_waiting( self.api.logger.log_info_msg("WAITFOR STARTED") if maxwait is not None: if not isinstance(maxwait, NUMERIC_TYPE): - raise Exception("The value entered for maxwait was invalid, it should be numeric.") + raise ValueError("The value entered for maxwait was invalid, it should be numeric.") else: - timeout_msg = "[timeout={}]".format(timedelta(seconds=maxwait).total_seconds()) + timeout_msg = f"[timeout={timedelta(seconds=maxwait).total_seconds()}]" if seconds is not None and not isinstance(seconds, NUMERIC_TYPE): - raise Exception("Invalid value entered for seconds") + raise ValueError("Invalid value entered for seconds") if minutes is not None and not isinstance(minutes, NUMERIC_TYPE): - raise Exception("Invalid value entered for minutes") + raise ValueError("Invalid value entered for minutes") if hours is not None and not isinstance(hours, NUMERIC_TYPE): - raise Exception("Invalid value entered for hours") + raise ValueError("Invalid value entered for hours") if time is not None: try: ans = strptime(time, "%H:%M:%S") @@ -323,50 +320,48 @@ def start_waiting( minutes = ans.tm_min hours = ans.tm_hour except Exception: - raise Exception( + raise ValueError( "Time string entered was invalid. It should be of the form HH:MM:SS" ) if frames is not None: if not isinstance(frames, int): - raise Exception("Invalid value entered for frames") + raise ValueError("Invalid value entered for frames") else: - self._print_if_not_quiet( - "Waiting for {} frames {}".format(frames, timeout_msg), quiet - ) + self._print_if_not_quiet(f"Waiting for {frames} frames {timeout_msg}", quiet) if raw_frames is not None: if not isinstance(raw_frames, int): - raise Exception("Invalid value entered for raw_frames") + raise ValueError("Invalid value entered for raw_frames") else: self._print_if_not_quiet( - "Waiting for {} raw frames {}".format(raw_frames, timeout_msg), quiet + f"Waiting for {raw_frames} raw frames {timeout_msg}", quiet ) if uamps is not None: if not (isinstance(uamps, NUMERIC_TYPE)): - raise Exception("Invalid value entered for uamps") + raise ValueError("Invalid value entered for uamps") else: - self._print_if_not_quiet( - "Waiting for {} uamps {}".format(uamps, timeout_msg), quiet - ) + self._print_if_not_quiet(f"Waiting for {uamps} uamps {timeout_msg}", quiet) if mevents is not None: if not (isinstance(mevents, NUMERIC_TYPE)): - raise Exception("Invalid value entered for mevents") + raise ValueError("Invalid value entered for mevents") else: self._print_if_not_quiet( - "Waiting for {} million events {}".format(mevents, timeout_msg), quiet + f"Waiting for {mevents} million events {timeout_msg}", quiet ) if block is not None: if not self.api.block_exists(block): - raise NameError('No block with the name "{}" exists'.format(block)) + raise NameError(f'No block with the name "{block}" exists') block = self.api.correct_blockname(block, add_prefix=False) if value is not None and (not isinstance(value, NUMERIC_TYPE + (str,))): - raise Exception( + raise ValueError( "The value entered for the block was invalid, it should be numeric or a string." ) if lowlimit is not None and (not isinstance(lowlimit, NUMERIC_TYPE)): - raise Exception("The value entered for lowlimit was invalid, it should be numeric.") + raise ValueError( + "The value entered for lowlimit was invalid, it should be numeric." + ) if highlimit is not None and (not isinstance(highlimit, NUMERIC_TYPE)): - raise Exception( + raise ValueError( "The value entered for highlimit was invalid, it should be numeric." ) @@ -379,12 +374,11 @@ def start_waiting( context = WaitForControllerExceptionContext(self.api) while True: - if maxwait is not None: - if datetime.now() - start_time >= timedelta(seconds=maxwait): - print("Waitfor timed out after {} seconds".format(maxwait)) - print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S"))) - self.api.logger.log_info_msg("WAITFOR TIMED OUT") - return + if maxwait is not None and datetime.now() - start_time >= timedelta(seconds=maxwait): + print(f"Waitfor timed out after {maxwait} seconds") + print("End time: {}".format(datetime.now().time().strftime("%H:%M:%S"))) + self.api.logger.log_info_msg("WAITFOR TIMED OUT") + return if early_exit(): print("Early exit handler evaluated to true in waitfor - stopping wait") @@ -479,9 +473,9 @@ def wait_for_runstate( def _init_wait_time( self, - seconds: float | int | None, - minutes: float | int | None, - hours: float | int | None, + seconds: float | None, + minutes: float | None, + hours: float | None, quiet: bool = False, timeout_msg: str = "", ) -> None: @@ -489,7 +483,7 @@ def _init_wait_time( if self.time_delta is not None: self.start_time = datetime.now() self._print_if_not_quiet( - "Waiting for {} seconds {}".format(self.time_delta.total_seconds(), timeout_msg), + f"Waiting for {self.time_delta.total_seconds()} seconds {timeout_msg}", quiet, ) else: @@ -497,19 +491,16 @@ def _init_wait_time( def _waiting_for_time(self) -> bool: if (self.start_time is None) or (self.time_delta is None): - raise Exception("start_time and time_delta must not be None") + raise ValueError("start_time and time_delta must not be None") else: - if datetime.now() - self.start_time >= self.time_delta: - return False - else: - return True + return not datetime.now() - self.start_time >= self.time_delta def _init_wait_block( self, block: str | None, value: "PVValue", - lowlimit: float | int | None, - highlimit: float | int | None, + lowlimit: float | None, + highlimit: float | None, quiet: bool = False, timeout_msg: str = "", ) -> None: @@ -518,21 +509,18 @@ def _init_wait_block( return self.low, self.high = self._get_block_limits(value, lowlimit, highlimit) if self.low is None and self.high is None: - raise Exception("No limit(s) set for {0}".format(block)) + raise ValueError(f"No limit(s) set for {block}") if self.low == self.high: - self._print_if_not_quiet( - "Waiting for {0}={1}{2}".format(str(block), str(self.low), timeout_msg), quiet - ) + self._print_if_not_quiet(f"Waiting for {block!s}={self.low!s}{timeout_msg}", quiet) else: self._print_if_not_quiet( - "Waiting for {0} (lowlimit={1}, highlimit={2}){3}".format( - str(block), str(self.low), str(self.high), timeout_msg - ), + f"Waiting for {block!s} (lowlimit={self.low!s}, " + f"highlimit={self.high!s}){timeout_msg}", quiet, ) def _get_block_limits( - self, value: "PVValue", lowlimit: float | int | None, highlimit: float | int | None + self, value: "PVValue", lowlimit: float | None, highlimit: float | None ) -> tuple["PVValue | None", "PVValue | None"]: low = None high = None @@ -546,8 +534,8 @@ def _get_block_limits( if isinstance(low, NUMERIC_TYPE) and isinstance(high, NUMERIC_TYPE) and low > high: low, high = high, low print( - "WARNING: The highlimit and lowlimit have been \ - swapped to lowlimit({}) and highlimit({})".format(low, high) + f"WARNING: The highlimit and lowlimit have been \ + swapped to lowlimit({low}) and highlimit({high})" ) return low, high diff --git a/src/genie_python/matplotlib_backend/ibex_websocket_backend.py b/src/genie_python/matplotlib_backend/ibex_websocket_backend.py index 58b81ed4..4b177986 100644 --- a/src/genie_python/matplotlib_backend/ibex_websocket_backend.py +++ b/src/genie_python/matplotlib_backend/ibex_websocket_backend.py @@ -10,9 +10,10 @@ import logging import sys import threading +from collections.abc import Callable, Mapping from functools import wraps from time import sleep -from typing import Any, Callable, Mapping, ParamSpec, TypeVar, cast +from typing import Any, ParamSpec, TypeVar, cast import tornado import tornado.websocket @@ -403,13 +404,13 @@ def trigger_manager_draw(cls, manager: FigureManager) -> None: @classmethod def draw_if_interactive(cls) -> None: with IBEX_BACKEND_LOCK: - super(_BackendIbexWebAgg, cls).draw_if_interactive() # pyright: ignore + super().draw_if_interactive() # pyright: ignore @classmethod def new_figure_manager(cls, num: int, *args: Any, **kwargs: Any) -> _FigureManager: with IBEX_BACKEND_LOCK: for x in list(figure_numbers): - if x not in Gcf.figs.keys(): + if x not in Gcf.figs: figure_numbers.remove(x) figure_numbers.append(num) if len(figure_numbers) > max_number_of_figures: @@ -419,4 +420,4 @@ def new_figure_manager(cls, num: int, *args: Any, **kwargs: Any) -> _FigureManag f"the oldest figure, which was {figure_numbers[0]}." ) figure_numbers.pop(0) - return super(_BackendIbexWebAgg, cls).new_figure_manager(num, *args, **kwargs) # pyright: ignore + return super().new_figure_manager(num, *args, **kwargs) # pyright: ignore diff --git a/src/genie_python/mysql_abstraction_layer.py b/src/genie_python/mysql_abstraction_layer.py index dfb81a5a..df7b9308 100644 --- a/src/genie_python/mysql_abstraction_layer.py +++ b/src/genie_python/mysql_abstraction_layer.py @@ -18,8 +18,8 @@ # http://opensource.org/licenses/eclipse-1.0.php from abc import abstractmethod -from collections.abc import Iterable -from typing import Generator, List, Optional, TypeAlias +from collections.abc import Generator, Iterable +from typing import TypeAlias import mysql.connector from mysql.connector.abstracts import MySQLCursorAbstract @@ -27,7 +27,6 @@ from mysql.connector.pooling import PooledMySQLConnection from mysql.connector.types import ParamsSequenceOrDictType, RowType -ParamsSequenceOrDictType = ParamsSequenceOrDictType MySQLConnectionAbstract: TypeAlias = MySQLConnection | PooledMySQLConnection @@ -37,10 +36,10 @@ class DatabaseError(IOError): """ def __init__(self, message: str) -> None: - super(DatabaseError, self).__init__(message) + super().__init__(message) -class AbstractSQLCommands(object): +class AbstractSQLCommands: """ Abstract base class for sql commands for testing """ @@ -75,8 +74,8 @@ def query_returning_cursor( @abstractmethod def _execute_command( - self, command: str, is_query: bool, bound_variables: Optional[ParamsSequenceOrDictType] - ) -> Optional[List[RowType]]: + self, command: str, is_query: bool, bound_variables: ParamsSequenceOrDictType | None + ) -> list[RowType] | None: """Executes a command on the database, and returns all values Args: @@ -89,8 +88,8 @@ def _execute_command( raise NotImplementedError() def query( - self, command: str, bound_variables: Optional[ParamsSequenceOrDictType] = None - ) -> Optional[List[RowType]]: + self, command: str, bound_variables: ParamsSequenceOrDictType | None = None + ) -> list[RowType] | None: """Executes a query on the database, and returns all values Args: @@ -103,9 +102,7 @@ def query( """ return self._execute_command(command, True, bound_variables) - def update( - self, command: str, bound_variables: Optional[ParamsSequenceOrDictType] = None - ) -> None: + def update(self, command: str, bound_variables: ParamsSequenceOrDictType | None = None) -> None: """Executes an update on the database, and returns all values Args: @@ -134,7 +131,7 @@ def __init__(self, dbid: str, user: str, password: str, host: str = "127.0.0.1") password: The password to use to connect to the database host: The host address to use, defaults to local host """ - super(SQLAbstraction, self).__init__() + super().__init__() self._dbid = dbid self._user = user self._password = password @@ -154,7 +151,7 @@ def _generate_pool_name(self) -> str: a connection in the pool is made with the frist set of credentials passed, so we have to make sure a pool name is not used with different credentials """ - return "DBSVR_%s_%s_%s" % (self._host, self._dbid, self._user) + return f"DBSVR_{self._host}_{self._dbid}_{self._user}" def _close_handles( self, curs: MySQLCursorAbstract | None, conn: MySQLConnectionAbstract | None @@ -190,7 +187,7 @@ def _start_connection_pool(self) -> None: curs.execute("SHOW TABLES") if len(curs.fetchall()) == 0: # Database does not exist - raise Exception("Requested Database %s does not exist" % self._dbid) + raise OSError(f"Requested Database {self._dbid} does not exist") self._close_handles(curs, conn) def _get_connection(self) -> MySQLConnectionAbstract: @@ -199,11 +196,11 @@ def _get_connection(self) -> MySQLConnectionAbstract: assert isinstance(conn, MySQLConnectionAbstract) return conn except Exception as err: - raise Exception("Unable to get connection from pool: %s" % str(err)) + raise OSError(f"Unable to get connection from pool: {err!s}") def _execute_command( - self, command: str, is_query: bool, bound_variables: Optional[ParamsSequenceOrDictType] - ) -> Optional[List[RowType]]: + self, command: str, is_query: bool, bound_variables: ParamsSequenceOrDictType | None + ) -> list[RowType] | None: """Executes a command on the database, and returns all values Args: @@ -226,7 +223,7 @@ def _execute_command( if is_query: values = curs.fetchall() if values is not None and len(values) > 0: - isinstance(values[0], List) + isinstance(values[0], list) # Commit as part of the query or results won't be updated between subsequent # transactions. Can lead to values not auto-updating in the GUI. @@ -260,8 +257,7 @@ def query_returning_cursor( assert isinstance(curs, Iterable) curs.execute(command, bound_variables) - for row in curs: - yield row + yield from curs # Commit as part of the query or results won't be updated between subsequent # transactions. Can lead to values not auto-updating in the GUI. diff --git a/src/genie_python/testing_utils/script_checker.py b/src/genie_python/testing_utils/script_checker.py index a144f706..71181abd 100644 --- a/src/genie_python/testing_utils/script_checker.py +++ b/src/genie_python/testing_utils/script_checker.py @@ -18,7 +18,7 @@ def write_to_temp_file(message: list[str], suffix: str = "", dir: str = "") -> I Returns: temporary file """ - temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir) + temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=suffix, dir=dir) # noqa: SIM115 for line in message: temp_file.write(line.encode("utf-8")) temp_file.close() @@ -58,6 +58,6 @@ def __exit__( self, exc_type: type[BaseException] | None, exc_value: BaseException | None, - exc_traceback: TracebackType, + exc_traceback: TracebackType | None, ) -> None: os.unlink(self.temp_script_file.name) diff --git a/src/genie_python/typings/CaChannel/CaChannel.pyi b/src/genie_python/typings/CaChannel/CaChannel.pyi index e4f75805..17d04847 100644 --- a/src/genie_python/typings/CaChannel/CaChannel.pyi +++ b/src/genie_python/typings/CaChannel/CaChannel.pyi @@ -2,18 +2,17 @@ This type stub file was generated by pyright. """ -from typing import Any, Callable, Tuple, TypeVar +from collections.abc import Callable +from typing import Any, TypeVar -from numpy import NDArray from ibex_non_ca_helpers.typing import PVValue -__all__ = ["CaChannelException", "CaChannel"] +__all__ = ["CaChannel", "CaChannelException"] T = TypeVar("T") class CaChannelException(Exception): def __init__(self, status) -> None: ... def __int__(self) -> int: ... - def __str__(self) -> str: ... class CaChannel: """CaChannel: A Python class with identical API as of caPython/CaChannel. @@ -58,7 +57,7 @@ class CaChannel: ... @staticmethod def create_context(): ... - def attach_ca_context(func): # -> _Wrapped[Callable[..., Any], Any, Callable[..., Any], Any]: + def attach_ca_context(self): # -> _Wrapped[Callable[..., Any], Any, Callable[..., Any], Any]: ... def __init__(self, pvName=...) -> None: ... def __del__(self) -> None: ... @@ -74,7 +73,6 @@ class CaChannel: >>> chan.getTimeout() 10.0 """ - ... def getTimeout(self) -> float: """Retrieve the timeout set for this channel object. @@ -86,14 +84,13 @@ class CaChannel: >>> chan.getTimeout() == CaChannel.ca_timeout True """ - ... @classmethod @attach_ca_context def replace_printf_handler( cls, - callback: Callable[[str, Tuple[T, ...]], None] = ..., - user_args: Tuple[T, ...] | None = ..., + callback: Callable[[str, tuple[T, ...]], None] = ..., + user_args: tuple[T, ...] | None = ..., ) -> None: """ Install or replace the callback used for formatted CA diagnostic message output. @@ -111,7 +108,6 @@ class CaChannel: .. versionadded:: 3.1 """ - ... @classmethod @attach_ca_context @@ -145,7 +141,6 @@ class CaChannel: .. versionadded:: 3.1 """ - ... def replace_access_rights_event(self, callback=..., user_args=...): # -> None: """ @@ -174,7 +169,6 @@ class CaChannel: .. versionadded:: 3.0 """ - ... @attach_ca_context def search_and_connect(self, pvName, callback, *user_args): # -> None: @@ -214,7 +208,6 @@ class CaChannel: cabo is connected >>> chan.clear_channel() """ - ... @attach_ca_context def search(self, pvName=...): # -> None: @@ -234,7 +227,6 @@ class CaChannel: >>> chan.state() """ - ... def clear_channel(self): # -> None: """Close a channel created by one of the search functions. @@ -249,7 +241,6 @@ class CaChannel: is called. This allows several requests to be efficiently sent over the network in one message. """ - ... def change_connection_event(self, callback, *user_args): # -> None: """Change the connection callback function @@ -273,7 +264,6 @@ class CaChannel: >>> chan.change_connection_event(None) # remove connection callback """ - ... def array_put(self, value, req_type=..., count=...): # -> None: """Write a value or array of values to a channel @@ -316,7 +306,6 @@ class CaChannel: >>> chan.getw(count=4) [49, 50, 51, 0] """ - ... def array_put_callback(self, value, req_type, count, callback, *user_args): # -> None: """Write a value or array of values to a channel and execute the user @@ -375,7 +364,6 @@ class CaChannel: >>> status = chan.pend_event(1) cawavec put completed """ - ... def getValue(self): # -> dict[Any, Any] | None: """ @@ -404,7 +392,6 @@ class CaChannel: pv_status State pv_value 1 """ - ... def array_get(self, req_type=..., count=..., **keywords): # -> None: """Read a value or array of values from a channel. @@ -440,7 +427,6 @@ class CaChannel: >>> chan.getValue() 123.0 """ - ... def array_get_callback(self, req_type, count, callback, *user_args, **keywords): # -> None: """Read a value or array of values from a channel and execute the user @@ -559,7 +545,6 @@ class CaChannel: pv_status No pv_value 0 """ - ... def add_masked_array_event( self, req_type, count, mask, callback, *user_args, **keywords @@ -613,7 +598,6 @@ class CaChannel: pv_severity Minor >>> chan.clear_event() """ - ... def clear_event(self): # -> None: """Remove previously installed callback function. @@ -622,7 +606,6 @@ class CaChannel: the IOC until one of execution methods (:meth:`pend_io`, :meth:`poll`, :meth:`pend_event`, :meth:`flush_io`) is called. This allows several requests to be efficiently sent over the network in one message. """ - ... @attach_ca_context def pend_io(self, timeout=...): # -> None: @@ -634,7 +617,6 @@ class CaChannel: :raises CaChannelException: if timeout or other error happens """ - ... @attach_ca_context def pend_event(self, timeout=...): @@ -645,7 +627,6 @@ class CaChannel: :param float timeout: seconds to wait :return: :data:`ca.ECA_TIMEOUT` """ - ... @attach_ca_context def poll(self): @@ -655,7 +636,6 @@ class CaChannel: .. note:: It is an alias to ``pend_event(1e-12)``. """ - ... @attach_ca_context def flush_io(self): # -> None: @@ -664,7 +644,6 @@ class CaChannel: :raises CaChannelException: if error happens """ - ... def field_type(self): """ @@ -680,7 +659,6 @@ class CaChannel: >>> ca.DBF_DOUBLE == ftype True """ - ... def element_count(self): """ @@ -691,7 +669,6 @@ class CaChannel: >>> chan.element_count() 1 """ - ... def name(self): """ @@ -702,7 +679,6 @@ class CaChannel: >>> chan.name() 'catest' """ - ... def state(self): """ @@ -725,7 +701,6 @@ class CaChannel: >>> chan.state() """ - ... def host_name(self): """ @@ -736,7 +711,6 @@ class CaChannel: >>> host_name = chan.host_name() """ - ... def read_access(self): """Access right to read the channel. @@ -748,7 +722,6 @@ class CaChannel: >>> chan.read_access() True """ - ... def write_access(self): """Access right to write the channel. @@ -760,7 +733,6 @@ class CaChannel: >>> chan.write_access() True """ - ... def searchw(self, pvName=...): # -> None: """ @@ -778,7 +750,6 @@ class CaChannel: ... CaChannelException: User specified timeout on IO operation expired """ - ... def putw(self, value, req_type=...): # -> None: """ @@ -831,7 +802,6 @@ class CaChannel: >>> chan.getw() ['string 1', 'string 2', ''] """ - ... def getw(self, req_type=..., count=..., **keywords) -> dict[Any, Any] | PVValue: """Read the value from a channel. @@ -886,6 +856,5 @@ class CaChannel: each element as a character. """ - ... if __name__ == "__main__": ... diff --git a/src/genie_python/typings/CaChannel/ca.pyi b/src/genie_python/typings/CaChannel/ca.pyi index 4b12884b..cb66acab 100644 --- a/src/genie_python/typings/CaChannel/ca.pyi +++ b/src/genie_python/typings/CaChannel/ca.pyi @@ -4,12 +4,11 @@ This type stub file was generated by pyright. import os from enum import Enum -from typing import TypeAlias from caffi.ca import * from caffi.constants import * from caffi.macros import * -from numpy import NDArray +from ibex_non_ca_helpers.typing import PVValue from ._ca import * @@ -23,9 +22,6 @@ DBR_CTRL_ENUM: Enum cs_conn: Enum CA_OP_CONN_DOWN: int -PVBaseValue: TypeAlias = bool | int | float | str -PVValue: TypeAlias = PVBaseValue | list[PVBaseValue] | NDArray | None - -def dbr_type_is_CHAR(PVValue) -> bool: ... -def dbr_type_is_ENUM(PVValue) -> bool: ... -def dbr_type_is_STRING(PVValue) -> bool: ... +def dbr_type_is_CHAR(value: PVValue) -> bool: ... +def dbr_type_is_ENUM(value: PVValue) -> bool: ... +def dbr_type_is_STRING(value: PVValue) -> bool: ... diff --git a/src/genie_python/utilities.py b/src/genie_python/utilities.py index b06d7184..8e521a25 100644 --- a/src/genie_python/utilities.py +++ b/src/genie_python/utilities.py @@ -1,14 +1,13 @@ -from __future__ import absolute_import, print_function - import codecs import json import os import re import unicodedata import zlib +from collections.abc import Callable, Iterable from datetime import timedelta from functools import wraps -from typing import TYPE_CHECKING, Any, Callable, Concatenate, Iterable, ParamSpec, TypeVar +from typing import TYPE_CHECKING, Any, Concatenate, ParamSpec, TypeVar P = ParamSpec("P") T = TypeVar("T") @@ -67,7 +66,7 @@ class PVReadException(Exception): # noqa N818 Historic name """ def __init__(self, message: str) -> None: - super(PVReadException, self).__init__(message) + super().__init__(message) def compress_and_hex(value: str) -> bytes: @@ -188,7 +187,7 @@ def _correct_path_casing_existing(path: str) -> str: return win32api.GetLongPathName(win32api.GetShortPathName(path)) except Exception as err: - raise OSError("Invalid file path entered: %s" % err) + raise OSError(f"Invalid file path entered: {err}") else: # Nothing to do for unix return path @@ -265,7 +264,7 @@ def crc8(value: str) -> str: else: crc <<= 1 - return "{0:02X}".format(crc) + return f"{crc:02X}" def get_json_pv_value(pv_name: str, api: "API", attempts: int = 3) -> Any: # No known type @@ -287,20 +286,20 @@ def get_json_pv_value(pv_name: str, api: "API", attempts: int = 3) -> Any: # No try: raw = api.get_pv_value(pv_name, to_string=True, attempts=attempts) except Exception: - raise PVReadException("Can not read '{0}'".format(pv_name)) + raise PVReadException(f"Can not read '{pv_name}'") if not isinstance(raw, (str, bytes)): - raise PVReadException("Expected reading PV {} to give a string".format(pv_name)) + raise PVReadException(f"Expected reading PV {pv_name} to give a string") try: raw = dehex_and_decompress(raw) except Exception: - raise PVReadException("Can not decompress '{0}'".format(pv_name)) + raise PVReadException(f"Can not decompress '{pv_name}'") try: result = json.loads(raw) except Exception: - raise PVReadException("Can not unmarshal '{0}'".format(pv_name)) + raise PVReadException(f"Can not unmarshal '{pv_name}'") return result @@ -327,11 +326,7 @@ def check_lowlimit_against_highlimit(lowlimit: float | None, highlimit: float | Check the lowlimit is below the highlimit, and warns if this is the case """ if lowlimit is not None and highlimit is not None and lowlimit > highlimit: - print( - "WARNING: You have set the lowlimit({}) above the highlimit({})".format( - lowlimit, highlimit - ) - ) + print(f"WARNING: You have set the lowlimit({lowlimit}) above the highlimit({highlimit})") def require_runstate( @@ -352,8 +347,9 @@ def _wrapper(self: "Dae", *args: P.args, **kwargs: P.kwargs) -> T: if not kwargs.pop("skip_required_runstates", False): run_state = self.get_run_state() if run_state not in set(runstates): - e_string = "{} can only be run in the following runstates: {}".format( - func.__name__, runstates_string + e_string = ( + f"{func.__name__} can only be run " + f"in the following runstates: {runstates_string}" ) raise ValueError(e_string) return func(self, *args, **kwargs) @@ -363,7 +359,7 @@ def _wrapper(self: "Dae", *args: P.args, **kwargs: P.kwargs) -> T: return _check_runstate -class EnvironmentDetails(object): +class EnvironmentDetails: """ Details of the computer environment the code is running in. """ @@ -372,7 +368,7 @@ class EnvironmentDetails(object): INSTRUMENT_LIST_PV = "CS:INSTLIST" # List of instruments dictionary similar to CS:INSTLIST - DEFAULT_INST_LIST = [ + DEFAULT_INST_LIST = ( {"name": "LARMOR"}, {"name": "ALF"}, {"name": "DEMO"}, @@ -380,7 +376,7 @@ class EnvironmentDetails(object): {"name": "MUONFE"}, {"name": "ZOOM"}, {"name": "IRIS"}, - ] + ) def __init__(self, host_name: str | None = None) -> None: """ @@ -420,11 +416,9 @@ def get_instrument_list(self, api: "API") -> list[dict[str, Any]]: try: return get_json_pv_value(self.INSTRUMENT_LIST_PV, api, attempts=1) except PVReadException as ex: - print("Error: {!r}. Using internal instrument list.".format(ex)) + print(f"Error: {ex!r}. Using internal instrument list.") return self.DEFAULT_INST_LIST def get_settings_directory(self) -> str: - default_directory = "C:/Instrument/Settings/config/{}/configurations".format( - self._host_name - ) + default_directory = f"C:/Instrument/Settings/config/{self._host_name}/configurations" return os.environ.get("ICPCONFIGROOT", default_directory) diff --git a/tests/py3_test_genie_experimental_data.py b/tests/py3_test_genie_experimental_data.py index a3878fee..5e4d6847 100644 --- a/tests/py3_test_genie_experimental_data.py +++ b/tests/py3_test_genie_experimental_data.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import datetime import unittest @@ -47,8 +46,10 @@ def setUp(self): [1, "a", "%"], [1, None, None], True, - 'User with name "a" was not found. Please make sure the title and' - ' name are correct (e.g. "Dr John Smith").', + ( + 'User with name "a" was not found. Please make sure the title and' + ' name are correct (e.g. "Dr John Smith").' + ), ), ([1, "a", "b"], [1, "a", "b"], False, "%"), ([1, "a", "b"], [1, "a", None], True, 'Role "{role}" was not found. Existing roles: '), @@ -57,8 +58,10 @@ def setUp(self): ["%", "a", "%"], [None, None, None], True, - 'User with name "a" was not found. Please make sure the title and' - ' name are correct (e.g. "Dr John Smith").', + ( + 'User with name "a" was not found. Please make sure the title and' + ' name are correct (e.g. "Dr John Smith").' + ), ), (["%", "%", "b"], [None, None, "b"], False, "%"), ( @@ -88,57 +91,73 @@ def test_GIVEN_parameters_WHEN_validate_parameters_THEN_correct_validation( "%", "%", "%", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND u.name LIKE '%' AND r.name LIKE '%' " - f"ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND u.name LIKE '%' AND r.name LIKE '%' " + f"ORDER BY experimentID DESC" + ), ), ( 123, "%", "%", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND u.name LIKE '%' AND r.name LIKE '%' " - f"ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND u.name LIKE '%' AND r.name LIKE '%' " + f"ORDER BY experimentID DESC" + ), ), ( "%", "name", "%", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND upper(u.name) = upper(%s) " - f"AND r.name LIKE '%' ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND upper(u.name) = upper(%s) " + f"AND r.name LIKE '%' ORDER BY experimentID DESC" + ), ), ( "%", "%", "role", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND u.name LIKE '%' AND " - f"upper(r.name) = upper(%s) ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND u.name LIKE '%' AND " + f"upper(r.name) = upper(%s) ORDER BY experimentID DESC" + ), ), ( 123, "user", "%", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND upper(u.name) = upper(%s) AND r.name LIKE '%' " - f"ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND upper(u.name) = upper(%s) AND r.name LIKE '%' " + f"ORDER BY experimentID DESC" + ), ), ( 123, "%", "role", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND u.name LIKE '%' AND upper(r.name) = upper(%s) " - f"ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND u.name LIKE '%' AND upper(r.name) = upper(%s) " + f"ORDER BY experimentID DESC" + ), ), ( "%", "name", "role", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND upper(u.name) = upper(%s) " - f"AND upper(r.name) = upper(%s) ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID LIKE '%' AND upper(u.name) = upper(%s) " + f"AND upper(r.name) = upper(%s) ORDER BY experimentID DESC" + ), ), ( 123, "name", "role", - f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND upper(u.name) = upper(%s) " - f"AND upper(r.name) = upper(%s) ORDER BY experimentID DESC", + ( + f"{SELECT_FOR_EXP_DETAILS} WHERE e.experimentID = %s AND upper(u.name) = upper(%s) " + f"AND upper(r.name) = upper(%s) ORDER BY experimentID DESC" + ), ), ] ) @@ -304,18 +323,30 @@ def test_GIVEN_records_WHEN_query_exp_data_THEN_correct_exp_data_returned( }, ], [ - "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_1", - "Experiment RB number: 22 | User: user_name_-- | Role: role_name_-- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_22", - "Experiment RB number: 332 | User: user_name_--- | Role: role_name_-- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_332", - "Experiment RB number: 4413 | User: user_name_- | Role: role_name_--- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_4413", - "Experiment RB number: 167 | User: user_name_------ | Role: role_name_------- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_167", - "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_1", + ( + "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_1" + ), + ( + "Experiment RB number: 22 | User: user_name_-- | Role: role_name_-- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_22" + ), + ( + "Experiment RB number: 332 | User: user_name_--- | Role: role_name_-- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_332" + ), + ( + "Experiment RB number: 4413 | User: user_name_- | Role: role_name_--- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_4413" + ), + ( + "Experiment RB number: 167 | User: user_name_------ | Role: role_name_------- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_167" + ), + ( + "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_1" + ), ], ), ( @@ -329,8 +360,10 @@ def test_GIVEN_records_WHEN_query_exp_data_THEN_correct_exp_data_returned( }, ], [ - "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: 2020-09-15 08:30:00" - " | Duration: duration_1" + ( + "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: 2020-09-15 08:30:00" + " | Duration: duration_1" + ) ], ), ( @@ -351,10 +384,14 @@ def test_GIVEN_records_WHEN_query_exp_data_THEN_correct_exp_data_returned( }, ], [ - "Experiment RB number: 167 | User: user_name_------ | Role: role_name_------- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_167", - "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: " - "2020-09-15 08:30:00 | Duration: duration_1", + ( + "Experiment RB number: 167 | User: user_name_------ | Role: role_name_------- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_167" + ), + ( + "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: " + "2020-09-15 08:30:00 | Duration: duration_1" + ), ], ), ( @@ -375,10 +412,14 @@ def test_GIVEN_records_WHEN_query_exp_data_THEN_correct_exp_data_returned( }, ], [ - "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: 2020-09-15 " - "08:30:00 | Duration: duration_1", - "Experiment RB number: 22 | User: user_name_-- | Role: role_name_-- | Start date: 2020-09-15 " - "08:30:00 | Duration: duration_22", + ( + "Experiment RB number: 1 | User: user_name_- | Role: role_name_- | Start date: 2020-09-15 " + "08:30:00 | Duration: duration_1" + ), + ( + "Experiment RB number: 22 | User: user_name_-- | Role: role_name_-- | Start date: 2020-09-15 " + "08:30:00 | Duration: duration_22" + ), ], ), ] diff --git a/tests/test_block_names.py b/tests/test_block_names.py index 16c64acc..7fc6f9b1 100644 --- a/tests/test_block_names.py +++ b/tests/test_block_names.py @@ -16,8 +16,6 @@ # http://opensource.org/licenses/eclipse-1.0.php """ -from __future__ import absolute_import - import json import unittest from time import sleep @@ -136,7 +134,7 @@ def test_GIVEN_prefix_set_WHEN_set_prefix_THEN_previous_call_back_is_cleared( call_back = Mock() add_monitor_mock.return_value = call_back - block_names, blocks_name_manager = create_block_names(get_pv_value_mock, []) + _block_names, blocks_name_manager = create_block_names(get_pv_value_mock, []) blocks_name_manager.update_prefix("new") sleep(1) # wait for timer to be called @@ -218,7 +216,7 @@ def test_GIVEN_request_invalid_block_WHEN_inspect_block_THEN_attribute_error_thr block_names, _ = create_block_names(get_pv_value_mock, []) try: - block_names._blocks_cant_start_with_hash + _ = block_names._blocks_cant_start_with_hash self.fail("No exception thrown") except AttributeError: pass diff --git a/tests/test_genie.py b/tests/test_genie.py index dba955fc..8b07f8ee 100644 --- a/tests/test_genie.py +++ b/tests/test_genie.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import, print_function import os import unittest @@ -321,14 +320,12 @@ def test_WHEN_waitfor_given_maxwait_and_frames_not_reached_THEN_times_out(self, self.assertEqual(len(output), 5) self.assertEqual("Waiting for 5000 frames [timeout=0.1]", output[1]) self.assertIn( - "Exception in waitfor loop: {}".format(UnableToConnectToPVException.__name__), + f"Exception in waitfor loop: {UnableToConnectToPVException.__name__}", output[2], ) self.assertIn("Waitfor timed out", output[3]) except Exception as e: - self.fail( - "start_waiting should catch the exception and continue. Exception: {}".format(e) - ) + self.fail(f"start_waiting should catch the exception and continue. Exception: {e}") @patch("sys.stdout", new_callable=StringIO) def test_WHEN_context_starts_THEN_state_is_connected_and_no_output(self, std_fake_out): @@ -356,14 +353,14 @@ def test_WHEN_exception_occurs_then_clears_THEN_exception_and_clearance_printed( self.assertEqual(len(output_lines), 2) # First line should be occurrence of exception self.assertIn( - "Exception in waitfor loop: {}".format(UnableToConnectToPVException.__name__), + f"Exception in waitfor loop: {UnableToConnectToPVException.__name__}", output_lines[0], ) self.assertNotIn("Exception cleared", output_lines[0]) # Second line should be exception clearing self.assertIn("Exception cleared", output_lines[1]) self.assertNotIn( - "Exception in waitfor loop: {}".format(UnableToConnectToPVException.__name__), + f"Exception in waitfor loop: {UnableToConnectToPVException.__name__}", output_lines[1], ) @@ -390,7 +387,7 @@ def test_WHEN_exception_continuously_occurs_for_more_than_15_minutes_THEN_except self.assertEqual(len(output_lines), number_of_5_minutes + 1) for i in range(len(output_lines)): self.assertIn( - "Exception in waitfor loop: {}".format(UnableToConnectToPVException.__name__), + f"Exception in waitfor loop: {UnableToConnectToPVException.__name__}", output_lines[i], ) @@ -410,12 +407,10 @@ def test_WHEN_exception_changes_THEN_new_exception_printed(self, std_fake_out): # One line for each exception self.assertEqual(len(output_lines), 2) self.assertIn( - "Exception in waitfor loop: {}".format(UnableToConnectToPVException.__name__), + f"Exception in waitfor loop: {UnableToConnectToPVException.__name__}", output_lines[0], ) - self.assertIn( - "Exception in waitfor loop: {}".format(ReadAccessException.__name__), output_lines[1] - ) + self.assertIn(f"Exception in waitfor loop: {ReadAccessException.__name__}", output_lines[1]) class TestGenieMockedAPI(unittest.TestCase): @@ -585,7 +580,7 @@ def test_GIVEN_defaults_WHEN_change_dir_to_existing_dir_using_multiple_arguments root = r"c:/scripts" dir = r"test" subdir = r"test_sub" - expected_dir = "{}/{}/{}/".format(root, dir, subdir) + expected_dir = f"{root}/{dir}/{subdir}/" self.setup_mocks(expected_dir, correct_casing, make_dirs_mock) @@ -621,7 +616,7 @@ def test_GIVEN_defaults_WHEN_change_dir_but_top_three_dirs_do_not_exist_THEN_pat subdir = "test" subdir1 = "subdir1" subdir2 = "subdir2" - expected_dir = "{}/{}/{}/{}/".format(existing_dir, subdir, subdir1, subdir2) + expected_dir = f"{existing_dir}/{subdir}/{subdir1}/{subdir2}/" genie.change_script_dir(os.path.join(existing_dir, subdir, subdir1, subdir2) + "/") result = genie.get_script_dir() diff --git a/tests/test_genie_alerts.py b/tests/test_genie_alerts.py index 643b804e..81a1a3fa 100644 --- a/tests/test_genie_alerts.py +++ b/tests/test_genie_alerts.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import, print_function import unittest from unittest.mock import patch diff --git a/tests/test_genie_api_setup.py b/tests/test_genie_api_setup.py index 3b11db95..e5556c67 100644 --- a/tests/test_genie_api_setup.py +++ b/tests/test_genie_api_setup.py @@ -16,8 +16,6 @@ # http://opensource.org/licenses/eclipse-1.0.php """ -from __future__ import absolute_import - import os import unittest from unittest.mock import patch @@ -115,7 +113,7 @@ def test_GIVEN_line_starts_load_script_with_various_partial_paths_WHEN_auto_comp expected_completer = (search_path, [expected_result]) # Act - result = self.load_script.pydev_complete(line_buffer=r'load_script("{}'.format(search_path)) + result = self.load_script.pydev_complete(line_buffer=rf'load_script("{search_path}') # Assert assert_that(result, is_(expected_completer)) @@ -131,9 +129,7 @@ def test_GIVEN_line_with_g_dot_load_script_with_partial_path_WHEN_auto_complete_ expected_completer = (search_path, [str(filename) for filename in filenames]) # Act - result = self.load_script.pydev_complete( - line_buffer=r'g.load_script("{}'.format(search_path) - ) + result = self.load_script.pydev_complete(line_buffer=rf'g.load_script("{search_path}') # Assert assert_that(result, is_(expected_completer)) @@ -148,9 +144,7 @@ def test_GIVEN_line_with_load_script_with_no_matching_paths_WHEN_auto_complete_T expected_completer = (search_path, []) # Act - result = self.load_script.pydev_complete( - line_buffer=r'g.load_script("{}'.format(search_path) - ) + result = self.load_script.pydev_complete(line_buffer=rf'g.load_script("{search_path}') # Assert assert_that(result, is_(expected_completer)) @@ -161,13 +155,11 @@ def test_GIVEN_line_with_load_script_with_quote_WHEN_auto_complete_THEN_show_mat ): # Arrange search_path = DRIVE_ROOT_PREFIX + "/scripts/" - mock_glob.return_value = ["{}filename.py".format(SCRIPT_DIR)] + mock_glob.return_value = [f"{SCRIPT_DIR}filename.py"] expected_completer = (search_path, ["filename.py"]) # Act - result = self.load_script.pydev_complete( - line_buffer=r"g.load_script({}".format(search_path) - ) + result = self.load_script.pydev_complete(line_buffer=rf"g.load_script({search_path}") # Assert assert_that(result, is_(expected_completer)) @@ -219,9 +211,7 @@ def test_GIVEN_line_with_load_script_with_no_matching_paths_WHEN_auto_complete_T expected_completer = (search_path, []) # Act - result = self.load_script.pydev_complete( - line_buffer=r'g.load_script("{}'.format(search_path) - ) + result = self.load_script.pydev_complete(line_buffer=rf'g.load_script("{search_path}') # Assert assert_that(result, is_(expected_completer)) @@ -262,7 +252,7 @@ def test_GIVEN_line_starts_load_script_with_various_partial_paths_WHEN_auto_comp expected_completer = (search_path, [expected_result]) # Act - result = self.load_script.pydev_complete(line_buffer=r'load_script("{}'.format(search_path)) + result = self.load_script.pydev_complete(line_buffer=rf'load_script("{search_path}') # Assert assert_that(result, is_(expected_completer)) @@ -283,10 +273,10 @@ def test_GIVEN_line_starts_load_script_with_non_absolute_path_THEN_return_conten expected_completer = (DRIVE_ROOT_PREFIX + "/scripts/" + search_path, [expected_result]) # Act - result = self.load_script.pydev_complete(line_buffer=r'load_script("{}'.format(search_path)) + result = self.load_script.pydev_complete(line_buffer=rf'load_script("{search_path}') # Assert - mock_glob.assert_called_with("{}{}*".format(SCRIPT_DIR, search_path)) + mock_glob.assert_called_with(f"{SCRIPT_DIR}{search_path}*") assert_that(result, is_(expected_completer)) @patch("glob.glob") @@ -295,14 +285,14 @@ def test_GIVEN_line_starts_load_script_with_nothing_else_THEN_return_contents_of ): # Arrange expected_result = '"play.py' - mock_glob.return_value = ["{}play.py".format(SCRIPT_DIR)] + mock_glob.return_value = [f"{SCRIPT_DIR}play.py"] expected_completer = (DRIVE_ROOT_PREFIX + "/scripts/", [expected_result]) # Act result = self.load_script.pydev_complete(line_buffer=r"load_script(") # Assert - mock_glob.assert_called_with("{}*".format(SCRIPT_DIR)) + mock_glob.assert_called_with(f"{SCRIPT_DIR}*") assert_that(result, is_(expected_completer)) @@ -315,7 +305,7 @@ def test_GIVEN_decorated_method_that_raises_and_squshed_exceptions_WHEN_called_T @log_command_and_handle_exception def test_func(): - raise Exception() + raise ValueError("unittest error") test_func() self.assertTrue(True) @@ -328,7 +318,7 @@ def test_GIVEN_decorated_method_that_raises_and_no_squashed_exception_WHEN_calle @log_command_and_handle_exception def test_func(): - raise Exception() + raise ValueError("unittest error") # self.assertRaises(Exception, test_func, ()) doesn't seem to work here? try: diff --git a/tests/test_genie_blockserver_tests.py b/tests/test_genie_blockserver_tests.py index 9511502a..f2bd5095 100644 --- a/tests/test_genie_blockserver_tests.py +++ b/tests/test_genie_blockserver_tests.py @@ -14,7 +14,6 @@ # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import unittest from unittest.mock import MagicMock, Mock diff --git a/tests/test_genie_change_cache.py b/tests/test_genie_change_cache.py index 7285cd3d..26f1898a 100644 --- a/tests/test_genie_change_cache.py +++ b/tests/test_genie_change_cache.py @@ -14,7 +14,6 @@ # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import unittest diff --git a/tests/test_genie_dae.py b/tests/test_genie_dae.py index 6d8099aa..e36f3f40 100644 --- a/tests/test_genie_dae.py +++ b/tests/test_genie_dae.py @@ -14,7 +14,6 @@ # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import unittest from unittest.mock import MagicMock, patch @@ -34,7 +33,7 @@ SPECTRA_PATH = "path/to/spectra.dat" DETECTOR_PATH = "path/to/detector.dat" -DAE_SETTINGS_XML = """ +DAE_SETTINGS_XML = f""" Data Acquisition 4 @@ -47,15 +46,15 @@ Wiring Table - {wiring_path} + {WIRING_PATH} Detector Table - {detector_path} + {DETECTOR_PATH} Spectra Table - {spectra_path} + {SPECTRA_PATH} DAETimingSource @@ -68,9 +67,7 @@ TS1 Only 2 -""".format( - wiring_path=WIRING_PATH, detector_path=DETECTOR_PATH, spectra_path=SPECTRA_PATH -) +""" TCB_SETTINGS_XML = """ Time Channels @@ -191,7 +188,7 @@ def test_WHEN_create_tcb_string_with_low_and_high_changed_THEN_range_changed_str new_high = 10 ans = self.dae._create_tcb_return_string(new_low, new_high, None, True) - self.assertEqual(ans, "Setting TCB range {} to {} (LOG binning)".format(new_low, new_high)) + self.assertEqual(ans, f"Setting TCB range {new_low} to {new_high} (LOG binning)") def test_WHEN_create_tcb_string_with_only_low_changed_THEN_low_limit_changed_string_returned( self, @@ -200,7 +197,7 @@ def test_WHEN_create_tcb_string_with_only_low_changed_THEN_low_limit_changed_str new_low = 0 ans = self.dae._create_tcb_return_string(new_low, None, None, True) - self.assertEqual(ans, "Setting TCB low limit to {} (LOG binning)".format(new_low)) + self.assertEqual(ans, f"Setting TCB low limit to {new_low} (LOG binning)") def test_WHEN_create_tcb_string_with_only_high_changed_THEN_high_limit_changed_string_returned( self, @@ -209,14 +206,14 @@ def test_WHEN_create_tcb_string_with_only_high_changed_THEN_high_limit_changed_s new_high = 10 ans = self.dae._create_tcb_return_string(None, new_high, None, True) - self.assertEqual(ans, "Setting TCB high limit to {} (LOG binning)".format(new_high)) + self.assertEqual(ans, f"Setting TCB high limit to {new_high} (LOG binning)") def test_WHEN_create_tcb_string_with_only_step_changed_THEN_step_changed_string_returned(self): self.dae.in_change = True new_step = 10 ans = self.dae._create_tcb_return_string(None, None, new_step, False) - self.assertEqual(ans, "Setting TCB step {} (LINEAR binning)".format(new_step)) + self.assertEqual(ans, f"Setting TCB step {new_step} (LINEAR binning)") def test_WHEN_create_tcb_string_with_all_changed_THEN_all_changed_string_returned(self): self.dae.in_change = True @@ -227,7 +224,7 @@ def test_WHEN_create_tcb_string_with_all_changed_THEN_all_changed_string_returne self.assertEqual( ans, - "Setting TCB range {} to {} step {} (LOG binning)".format(new_low, new_high, new_step), + f"Setting TCB range {new_low} to {new_high} step {new_step} (LOG binning)", ) def test_WHEN_create_tcb_string_with_low_and_step_changed_THEN_low_limit_and_step_string_returned( @@ -238,9 +235,7 @@ def test_WHEN_create_tcb_string_with_low_and_step_changed_THEN_low_limit_and_ste new_step = 2 ans = self.dae._create_tcb_return_string(new_low, None, new_step, True) - self.assertEqual( - ans, "Setting TCB low limit to {} step {} (LOG binning)".format(new_low, new_step) - ) + self.assertEqual(ans, f"Setting TCB low limit to {new_low} step {new_step} (LOG binning)") def test_WHEN_create_tcb_string_with_high_and_step_changed_THEN_high_limit_and_step_string_returned( self, @@ -250,9 +245,7 @@ def test_WHEN_create_tcb_string_with_high_and_step_changed_THEN_high_limit_and_s new_step = 2 ans = self.dae._create_tcb_return_string(None, new_high, new_step, True) - self.assertEqual( - ans, "Setting TCB high limit to {} step {} (LOG binning)".format(new_high, new_step) - ) + self.assertEqual(ans, f"Setting TCB high limit to {new_high} step {new_step} (LOG binning)") def test_GIVEN_change_not_started_WHEN_change_finished_called_THEN_exception_thrown(self): self.assertRaises(Exception, self.dae.change_finish) @@ -350,13 +343,13 @@ def check_all_vetos(self, set): """ for k, d in self.change_cache.__dict__.items(): if k.endswith("veto") and "fermi" not in k: - self.assertEqual(set, d, "{} incorrect".format(k)) + self.assertEqual(set, d, f"{k} incorrect") def set_all_vetos(self, set): """ Helper function to set all vetos to a value by the 'backdoor'. """ - for k in self.change_cache.__dict__.keys(): + for k in self.change_cache.__dict__: if k.endswith("veto") and "fermi" not in k: self.change_cache.__dict__[k] = set @@ -729,11 +722,11 @@ def test_WHEN_temporarily_kill_isisicp_context_manager_used_THEN_isisicp_killed( ), patch.object(self.dae, "_isis_dae_triggered_state_was_reached", return_value=True), patch.object(self.dae, "_get_pv_value", return_value="On"), + self.dae.temporarily_kill_icp(), ): - with self.dae.temporarily_kill_icp(): - isisicp.kill.assert_called_once() - dead_process.kill.assert_not_called() - live_process.kill.assert_not_called() + isisicp.kill.assert_called_once() + dead_process.kill.assert_not_called() + live_process.kill.assert_not_called() def test_WHEN_get_autosave_frequency_THEN_reads_from_PV(self): self.api.get_pv_value = MagicMock(return_value=10) diff --git a/tests/test_genie_epics_api.py b/tests/test_genie_epics_api.py index 569dba83..02953504 100644 --- a/tests/test_genie_epics_api.py +++ b/tests/test_genie_epics_api.py @@ -14,7 +14,6 @@ # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import unittest from unittest.mock import MagicMock, patch @@ -60,7 +59,7 @@ def mock_get_pv_value(pv_name, to_string): Returns: - A string saying "INVALID" if the pv name argument is ALARM_PV.SEVR. - A mock list of instruments if the pv name argument is CS:INSTLIST. - - Otherwise it returns an exception. + - Otherwise it raises an exception. """ if pv_name == "ALARM_PV.SEVR": return "INVALID" @@ -89,7 +88,7 @@ def mock_get_pv_value(pv_name, to_string): }, ] else: - raise Exception + raise ValueError("Invalid PV name provided") def test_WHEN_reloading_current_config_THEN_command_is_delegated_to_blockserver(self): # Arrange @@ -114,7 +113,7 @@ def test_GIVEN_list_of_one_element_with_PV_prefix_sample_WHEN_get_sample_pars_is # Assert self.assertEqual(len(val), 1) - self.assertEqual(list(val.keys())[0], pv_suffix) + self.assertEqual(next(iter(val.keys())), pv_suffix) self.assertEqual(val[pv_suffix], self.mock_pv_value) def test_GIVEN_list_of_one_element_with_PV_prefix_not_sample_WHEN_get_sample_pars_is_called_THEN_returns_an_empty_dictionary( @@ -146,7 +145,7 @@ def test_GIVEN_list_of_one_element_with_PV_prefix_bl_WHEN_get_beamline_pars_is_c # Assert self.assertEqual(len(val), 1) - self.assertEqual(list(val.keys())[0], pv_suffix) + self.assertEqual(next(iter(val.keys())), pv_suffix) self.assertEqual(val[pv_suffix], self.mock_pv_value) def test_GIVEN_list_of_one_element_with_PV_prefix_not_bl_WHEN_get_beamline_pars_is_called_THEN_returns_an_empty_dictionary( @@ -185,7 +184,7 @@ def setUp(self): def test_WHEN_machine_identifier_begins_ndx_THEN_instrument_is_name_without_ndx(self): # Act expected = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier( + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier( "NDX" + expected ) @@ -195,7 +194,7 @@ def test_WHEN_machine_identifier_begins_ndx_THEN_instrument_is_name_without_ndx( def test_WHEN_machine_identifier_begins_ndx_THEN_machine_name_is_machine_identifier(self): # Act expected = "NDXNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, machine) @@ -203,7 +202,9 @@ def test_WHEN_machine_identifier_begins_ndx_THEN_machine_name_is_machine_identif def test_WHEN_machine_identifier_begins_ndx_THEN_pv_prefix_begins_with_in_colon(self): # Act name = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier("NDX" + name) + _instrument, _machine, pv_prefix = self.api._get_machine_details_from_identifier( + "NDX" + name + ) expected = "IN:" + name + ":" # Assert @@ -212,7 +213,7 @@ def test_WHEN_machine_identifier_begins_ndx_THEN_pv_prefix_begins_with_in_colon( def test_WHEN_machine_identifier_begins_ndw_THEN_instrument_is_same_as_name(self): # Act expected = "NDWNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, instrument) @@ -220,7 +221,7 @@ def test_WHEN_machine_identifier_begins_ndw_THEN_instrument_is_same_as_name(self def test_WHEN_machine_identifier_begins_ndw_THEN_machine_name_is_machine_identifier(self): # Act expected = "NDWNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, machine) @@ -228,7 +229,7 @@ def test_WHEN_machine_identifier_begins_ndw_THEN_machine_name_is_machine_identif def test_WHEN_machine_identifier_begins_ndw_THEN_pv_prefix_begins_with_the_colon(self): # Act machine = "NDWname" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(machine) + _instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(machine) expected = "TE:" + machine + ":" # Assert @@ -237,7 +238,7 @@ def test_WHEN_machine_identifier_begins_ndw_THEN_pv_prefix_begins_with_the_colon def test_WHEN_machine_identifier_begins_nde_THEN_instrument_is_name_without_nde(self): # Act expected = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier( + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier( "NDE" + expected ) @@ -247,7 +248,7 @@ def test_WHEN_machine_identifier_begins_nde_THEN_instrument_is_name_without_nde( def test_WHEN_machine_identifier_begins_nde_THEN_machine_name_is_machine_identifier(self): # Act expected = "NDENAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, machine) @@ -255,7 +256,9 @@ def test_WHEN_machine_identifier_begins_nde_THEN_machine_name_is_machine_identif def test_WHEN_machine_identifier_begins_nde_THEN_pv_prefix_begins_with_in_colon(self): # Act name = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier("NDE" + name) + _instrument, _machine, pv_prefix = self.api._get_machine_details_from_identifier( + "NDE" + name + ) expected = "IN:" + name + ":" # Assert @@ -264,7 +267,7 @@ def test_WHEN_machine_identifier_begins_nde_THEN_pv_prefix_begins_with_in_colon( def test_WHEN_machine_identifier_begins_ndlt_THEN_instrument_is_same_as_machine_name(self): # Act expected = "NDLTNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, instrument) @@ -272,7 +275,7 @@ def test_WHEN_machine_identifier_begins_ndlt_THEN_instrument_is_same_as_machine_ def test_WHEN_machine_identifier_begins_ndlt_THEN_machine_name_is_machine_identifier(self): # Act expected = "NDLTNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, machine) @@ -280,7 +283,7 @@ def test_WHEN_machine_identifier_begins_ndlt_THEN_machine_name_is_machine_identi def test_WHEN_machine_identifier_begins_in_colon_THEN_instrument_is_name_without_prefix(self): # Act expected = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier( + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier( "IN:" + expected + ":" ) @@ -290,7 +293,7 @@ def test_WHEN_machine_identifier_begins_in_colon_THEN_instrument_is_name_without def test_WHEN_machine_identifier_begins_in_colon_THEN_machine_name_is_machine_identifier(self): # Act name = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier( + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier( "IN:" + name + ":" ) @@ -302,7 +305,7 @@ def test_WHEN_machine_identifier_begins_with_instrument_prefix_THEN_pv_prefix_be ): # Act expected = "IN:NAME:" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, _machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, pv_prefix) @@ -310,7 +313,7 @@ def test_WHEN_machine_identifier_begins_with_instrument_prefix_THEN_pv_prefix_be def test_WHEN_machine_identifier_begins_ndlt_THEN_pv_prefix_begins_with_the_colon(self): # Act machine = "NDLTNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(machine) + _instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(machine) expected = "TE:" + machine + ":" # Assert @@ -319,7 +322,7 @@ def test_WHEN_machine_identifier_begins_ndlt_THEN_pv_prefix_begins_with_the_colo def test_WHEN_machine_identifier_begins_ndh_THEN_instrument_is_name_without_ndh(self): # Act expected = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier( + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier( "NDH" + expected ) @@ -329,7 +332,7 @@ def test_WHEN_machine_identifier_begins_ndh_THEN_instrument_is_name_without_ndh( def test_WHEN_machine_identifier_begins_ndh_THEN_machine_name_is_machine_identifier(self): # Act expected = "NDHNAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, machine) @@ -337,7 +340,9 @@ def test_WHEN_machine_identifier_begins_ndh_THEN_machine_name_is_machine_identif def test_WHEN_machine_identifier_begins_ndh_THEN_pv_prefix_begins_with_te_colon(self): # Act name = "NAME" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier("NDH" + name) + _instrument, _machine, pv_prefix = self.api._get_machine_details_from_identifier( + "NDH" + name + ) expected = "TE:" + name + ":" # Assert @@ -346,7 +351,7 @@ def test_WHEN_machine_identifier_begins_ndh_THEN_pv_prefix_begins_with_te_colon( def test_WHEN_machine_identifier_is_in_instrument_list_THEN_instrument_name_is_corrent(self): # Act name = "IN:TEST2:" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(name) + instrument, _machine, _pv_prefix = self.api._get_machine_details_from_identifier(name) expected = "TEST2" # Assert @@ -355,7 +360,7 @@ def test_WHEN_machine_identifier_is_in_instrument_list_THEN_instrument_name_is_c def test_WHEN_machine_identifier_is_in_instrument_list_THEN_machine_name_is_corrent(self): # Act name = "IN:TEST2:" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(name) + _instrument, machine, _pv_prefix = self.api._get_machine_details_from_identifier(name) expected = "NDXTEST2" # Assert @@ -364,7 +369,7 @@ def test_WHEN_machine_identifier_is_in_instrument_list_THEN_machine_name_is_corr def test_WHEN_machine_identifier_is_in_instrument_list_THEN_pv_prefix_is_corrent(self): # Act expected = "IN:TEST2:" - instrument, machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) + _instrument, _machine, pv_prefix = self.api._get_machine_details_from_identifier(expected) # Assert self.assertEqual(expected, pv_prefix) @@ -427,7 +432,7 @@ def test_GIVEN_mock_pv_WHEN_get_local_pv_value_THEN_pv_is_returned( pv_wrapper_mock.pv_exists.return_value = True pv_wrapper_mock.get_pv_value.return_value = expected_value pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" result = self.api.get_pv_value(pv_name, is_local=True) @@ -442,7 +447,7 @@ def test_GIVEN_mock_pv_WHEN_get_value_of_local_pv_with_local_prefix_on_start_THE pv_wrapper_mock.pv_exists.return_value = True pv_wrapper_mock.get_pv_value.return_value = expected_value pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" result = self.api.get_pv_value(expected_pv_name, is_local=True) @@ -533,7 +538,7 @@ def test_GIVEN_mock_pv_WHEN_get_pv_value_raises_exception_except_on_last_trial_T def test_GIVEN_mock_pv_WHEN_set_local_pv_value_THEN_pv_is_set(self, pv_wrapper_mock: MagicMock): expected_value = 10 pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" self.api.set_pv_value(pv_name, expected_value, is_local=True) @@ -547,7 +552,7 @@ def test_GIVEN_mock_pv_WHEN_set_local_pv_value_with_inst_prefix_THEN_pv_is_set_p ): expected_value = 10 pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" self.api.set_pv_value(expected_pv_name, expected_value, is_local=True) @@ -575,7 +580,7 @@ def test_GIVEN_mock_pv_WHEN_set_local_pv_value_but_wrapper_exceptions_THEN_excep pv_wrapper_mock.set_pv_value.side_effect = ValueError() expected_value = 10 pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" assert_that( calling(self.api.set_pv_value).with_args( @@ -595,7 +600,7 @@ def test_GIVEN_mock_pv_WHEN_set_local_pv_value_but_wrapper_exception_on_first_tr ): expected_value = 10 pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" pv_wrapper_mock.set_pv_value.side_effect = [ValueError(), expected_value] @@ -616,7 +621,7 @@ def test_GIVEN_mock_pv_WHEN_set_local_pv_value_raises_exception_except_on_last_t ): expected_value = 10 pv_name = "PV" - expected_pv_name = "{}{}".format(self.instrument_prefix, pv_name) + expected_pv_name = f"{self.instrument_prefix}{pv_name}" pv_wrapper_mock.set_pv_value.side_effect = [ ValueError(), @@ -833,9 +838,7 @@ def test_WHEN_set_block_value_called_with_wait_and_various_odd_high_lows_THEN_me self.api.set_block_value(block_name, val, wait=True, lowlimit=low, highlimit=high) if should_warn: mock_print.assert_called_with( - "Warning the range {} to {} does not cover setpoint of {}, may wait forever".format( - low, high, val - ) + f"Warning the range {low} to {high} does not cover setpoint of {val}, may wait forever" ) else: mock_print.assert_not_called() diff --git a/tests/test_genie_wait_for_move.py b/tests/test_genie_wait_for_move.py index 7cd6fe24..9db8ddd2 100644 --- a/tests/test_genie_wait_for_move.py +++ b/tests/test_genie_wait_for_move.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import unittest @@ -68,7 +67,7 @@ def test_GIVEN_method_check_timeouts_valid_WHEN_check_timeouts_THEN_returns_vali @parameterized.expand( [("start_valid_move_none", 10.0, None), ("start_valid_move_invalid", 12.0, -30.0)] ) - def test_GIVEN_method_check_timeouts_valid_WHEN_check_timeouts_THEN_returns_valid_values( + def test_GIVEN_method_check_timeouts_valid_WHEN_check_timeouts_THEN_returns_none( self, _, start_input, move_input ): start_timeout = start_input diff --git a/tests/test_genie_waitfor.py b/tests/test_genie_waitfor.py index 194510c9..160e212d 100644 --- a/tests/test_genie_waitfor.py +++ b/tests/test_genie_waitfor.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import sys import unittest @@ -28,57 +27,57 @@ def setUp(self): self.genie_waitfor = WaitForController(API()) def test_GIVEN_silent_waitfor_time_WHEN_waitfor_time_THEN_no_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(seconds=0, quiet=True) sys.stdout = sys.__stdout__ - self.assertIs("", capturedOutput.getvalue()) + self.assertIs("", captured_output.getvalue()) def test_GIVEN_waitfor_time_WHEN_waitfor_time_THEN_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(seconds=0, quiet=False) sys.stdout = sys.__stdout__ - self.assertIsNot("", capturedOutput.getvalue()) + self.assertIsNot("", captured_output.getvalue()) def test_GIVEN_silent_waitfor_uamps_WHEN_waitfor_uamps_THEN_no_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(uamps=0, quiet=True) sys.stdout = sys.__stdout__ - self.assertIs("", capturedOutput.getvalue()) + self.assertIs("", captured_output.getvalue()) def test_GIVEN_waitfor_uamps_WHEN_waitfor_uamps_THEN_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(uamps=0, quiet=False) sys.stdout = sys.__stdout__ - self.assertIsNot("", capturedOutput.getvalue()) + self.assertIsNot("", captured_output.getvalue()) def test_GIVEN_silent_waitfor_raw_frames_WHEN_waitfor_raw_frames_THEN_no_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(raw_frames=0, quiet=True) sys.stdout = sys.__stdout__ - self.assertIs("", capturedOutput.getvalue()) + self.assertIs("", captured_output.getvalue()) def test_GIVEN_waitfor_raw_frames_WHEN_waitfor_raw_frames_THEN_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(raw_frames=0, quiet=False) sys.stdout = sys.__stdout__ - self.assertIsNot("", capturedOutput.getvalue()) + self.assertIsNot("", captured_output.getvalue()) def test_GIVEN_silent_waitfor_mevents_WHEN_waitfor_mevents_THEN_no_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(mevents=0, quiet=True) sys.stdout = sys.__stdout__ - self.assertIs("", capturedOutput.getvalue()) + self.assertIs("", captured_output.getvalue()) def test_GIVEN_waitfor_mevents_WHEN_waitfor_mevents_THEN_output(self): - capturedOutput = StringIO() - sys.stdout = capturedOutput + captured_output = StringIO() + sys.stdout = captured_output self.genie_waitfor.start_waiting(mevents=0, quiet=False) sys.stdout = sys.__stdout__ - self.assertIsNot("", capturedOutput.getvalue()) + self.assertIsNot("", captured_output.getvalue()) diff --git a/tests/test_matplotlib_backend.py b/tests/test_matplotlib_backend.py index 5e2a9f9a..5beab1b5 100644 --- a/tests/test_matplotlib_backend.py +++ b/tests/test_matplotlib_backend.py @@ -5,7 +5,7 @@ from genie_python.matplotlib_backend import ibex_websocket_backend -class ErroringWebAggApplication(object): +class ErroringWebAggApplication: started = False @classmethod @@ -14,7 +14,7 @@ def initialize(cls, *a, **k): @classmethod def start(cls, *a, **k): - raise IOError("Mock matplotlib worker thread error") + raise OSError("Mock matplotlib worker thread error") class TestMatplotlibBackend(unittest.TestCase): diff --git a/tests/test_mysql_abstraction_layer.py b/tests/test_mysql_abstraction_layer.py index 6a5cb8c0..fbcb4802 100644 --- a/tests/test_mysql_abstraction_layer.py +++ b/tests/test_mysql_abstraction_layer.py @@ -15,7 +15,6 @@ # http://opensource.org/licenses/eclipse-1.0.php import unittest -from typing import List from unittest.mock import Mock, patch from hamcrest import assert_that, equal_to @@ -60,7 +59,7 @@ def close(self) -> bool: def execute(self, operation, params=None, multi: bool = False): pass - def fetchall(self) -> List[RowType]: + def fetchall(self) -> list[RowType]: return [] diff --git a/tests/test_script_checker.py b/tests/test_script_checker.py index 85b7f755..02056047 100644 --- a/tests/test_script_checker.py +++ b/tests/test_script_checker.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import builtins import contextlib @@ -230,8 +229,10 @@ def test_GIVEN_g_non_existing_command_WHEN_call_THEN_error_message(self): self.assertEqual( errors, [ - "E: 3: Module 'genie_python.genie' has no " - "'aitfor_time' member; maybe 'waitfor_time'? (no-member)" + ( + "E: 3: Module 'genie_python.genie' has no " + "'aitfor_time' member; maybe 'waitfor_time'? (no-member)" + ) ], ) @@ -562,8 +563,10 @@ def test_GIVEN_trying_to_call_var_of_optional_type_WHEN_pyright_script_checker_c ): script_lines = [ "from typing import Optional, Callable\n", - "def execute_callback(callback: Optional[Callable[[], None]]) -> None:\n" - " callback()\n", + ( + "def execute_callback(callback: Optional[Callable[[], None]]) -> None:\n" + " callback()\n" + ), ] with CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors: @@ -626,10 +629,10 @@ def test_GIVEN_scanning_instrument_WHEN_calling_pylint_THEN_pylint_does_not_cras ] captured_stderr = io.StringIO() - with contextlib.redirect_stderr(captured_stderr): - with CreateTempScriptAndReturnErrors( - self.checker, self.machine, script_lines - ) as errors: - self.assertEqual(errors, []) + with ( + contextlib.redirect_stderr(captured_stderr), + CreateTempScriptAndReturnErrors(self.checker, self.machine, script_lines) as errors, + ): + self.assertEqual(errors, []) self.assertEqual(captured_stderr.getvalue(), "") diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 93071b8c..53f437e7 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import unittest from collections import OrderedDict diff --git a/tests/test_utilities.py b/tests/test_utilities.py index 0f349bf6..a6dd9d22 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import absolute_import import json import unittest @@ -54,7 +53,7 @@ def test_GIVEN_string_WHEN_string_is_compressed_and_hexed_THEN_return_value_is_v strings_to_test = self.get_test_strings() # Act - hexed_strings = list() + hexed_strings = [] for string in strings_to_test: hexed_strings.append(compress_and_hex(string)) @@ -72,7 +71,7 @@ def test_GIVEN_string_WHEN_string_is_compressed_and_decompressed_THEN_return_val strings_to_test = self.get_test_strings() # Act - manipulated_strings = list() + manipulated_strings = [] for string in strings_to_test: manipulated_strings.append(dehex_and_decompress(compress_and_hex(string))) @@ -193,7 +192,7 @@ def test_GIVEN_windows_style_filepath_with_unescaped_chars_WHEN_corrected_THEN_r def test_GIVEN_mixed_style_filepath_WHEN_corrected_THEN_result_is_unix_style(self): # Arrange - filepath = "C:/TestDir\TestSubDir/file.py" + filepath = r"C:/TestDir\TestSubDir/file.py" # Act ans = get_correct_path(filepath) @@ -221,9 +220,7 @@ def test_GIVEN_pv_with_no_field_WHEN_remove_field_called_THEN_pv_returned_unchan class TestCRC8Util(unittest.TestCase): def calc_and_test(self, expected, inst): result = crc8(inst) - self.assertEqual( - result, expected, 'CRC value was "{0}" expected "{1}"'.format(result, expected) - ) + self.assertEqual(result, expected, f'CRC value was "{result}" expected "{expected}"') def setUp(self): pass diff --git a/tests/test_utils_with_unicode_literals.py b/tests/test_utils_with_unicode_literals.py index 972b57fa..6d3daa58 100644 --- a/tests/test_utils_with_unicode_literals.py +++ b/tests/test_utils_with_unicode_literals.py @@ -13,7 +13,6 @@ # along with this program; if not, you can obtain a copy from # https://www.eclipse.org/org/documents/epl-v10.php or # http://opensource.org/licenses/eclipse-1.0.php -from __future__ import unicode_literals import unittest @@ -27,7 +26,7 @@ class TestUtilsUnicode(unittest.TestCase): def test_GIVEN_windows_style_filepath_WHEN_corrected_THEN_result_is_unix_style(self): # Arrange - filepath = "C:\\TestDir\TestSubDir\file.py" + filepath = "C:\\TestDir\\TestSubDir\file.py" # Act ans = get_correct_path(filepath) @@ -50,7 +49,7 @@ def test_GIVEN_windows_style_filepath_with_unescaped_chars_WHEN_corrected_THEN_r def test_GIVEN_mixed_style_filepath_WHEN_corrected_THEN_result_is_unix_style(self): # Arrange - filepath = "C:/TestDir\TestSubDir/file.py" + filepath = r"C:/TestDir\TestSubDir/file.py" # Act ans = get_correct_path(filepath) From 470e240570132c01ca5af80202d56820509b5b59 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 19 Oct 2026 13:27:23 +0100 Subject: [PATCH 3/3] corrections --- pyproject.toml | 5 +++-- src/genie_python/genie.py | 4 ++-- src/genie_python/genie_change_cache.py | 6 +++--- src/genie_python/genie_dae.py | 4 ++-- src/genie_python/genie_plot.py | 12 +++++++----- src/genie_python/genie_simulate_impl.py | 10 +++++----- src/genie_python/utilities.py | 4 ++-- tests/test_genie_epics_api.py | 2 +- tests/test_genie_wait_for_move.py | 2 +- tests/test_simulation.py | 2 +- 10 files changed, 27 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a2fd9bb8..231a1a0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,8 @@ exclude_lines = [ directory = "coverage_html_report" [tool.pyright] -include = ["src", "tests"] +include = ["src"] +exclude = ["src/genie_python/typings"] # Generated type-stubs reportConstantRedefinition = true reportDeprecated = true reportInconsistentConstructor = true @@ -118,7 +119,7 @@ reportMissingTypeArgument = true reportUnnecessaryCast = true reportUnnecessaryComparison = true reportUnnecessaryContains = true -reportUnnecessaryIsInstance = true +reportUnnecessaryIsInstance = false # Users might pass wrong types reportUntypedBaseClass = true reportUntypedClassDecorator = true reportUntypedFunctionDecorator = true diff --git a/src/genie_python/genie.py b/src/genie_python/genie.py index 0106f6d5..c9a16098 100644 --- a/src/genie_python/genie.py +++ b/src/genie_python/genie.py @@ -112,8 +112,8 @@ class _GetbeamlineparsReturn(TypedDict): print("\ngenie_python version " + VERSION) -MIN_SUPPORTED_PYTHON_VERSION = (3, 11, 0) -MAX_SUPPORTED_PYTHON_VERSION = (3, 13, 999) +MIN_SUPPORTED_PYTHON_VERSION = (3, 12, 0) +MAX_SUPPORTED_PYTHON_VERSION = (3, 14, 999) if not (MIN_SUPPORTED_PYTHON_VERSION <= sys.version_info[0:3] <= MAX_SUPPORTED_PYTHON_VERSION): message = ( diff --git a/src/genie_python/genie_change_cache.py b/src/genie_python/genie_change_cache.py index 59c7ebf1..bba43bf6 100644 --- a/src/genie_python/genie_change_cache.py +++ b/src/genie_python/genie_change_cache.py @@ -10,8 +10,8 @@ def __init__(self) -> None: self.mon_spect = None self.mon_from = None self.mon_to = None - self.dae_sync = None - self.tcb_file = None + self.dae_sync: int | None = None + self.tcb_file: str | None = None self.tcb_tables = [] self.tcb_calculation_method = None self.smp_veto = None @@ -26,7 +26,7 @@ def __init__(self) -> None: self.fermi_width = None self.periods_soft_num = None self.periods_type = None - self.periods_src = None + self.periods_src: int | None = None self.periods_file = None self.periods_seq = None self.periods_delay = None diff --git a/src/genie_python/genie_dae.py b/src/genie_python/genie_dae.py index 276729ff..ce2e3c51 100644 --- a/src/genie_python/genie_dae.py +++ b/src/genie_python/genie_dae.py @@ -10,7 +10,7 @@ from datetime import datetime, timedelta from stat import S_IREAD, S_IWUSR from time import sleep, strftime -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, Any, cast import numpy as np import numpy.typing as npt @@ -1465,7 +1465,7 @@ def change_vetos(self, **params: bool) -> None: if did_change: self.change_finish() - def _change_runtime_vetos(self, params: dict) -> None: + def _change_runtime_vetos(self, params: dict[str, Any]) -> dict[str, Any]: """ Change the DAE veto settings whilst the DAE is running. diff --git a/src/genie_python/genie_plot.py b/src/genie_python/genie_plot.py index 7c1afb13..2b4f1cf0 100644 --- a/src/genie_python/genie_plot.py +++ b/src/genie_python/genie_plot.py @@ -1,9 +1,12 @@ from collections.abc import Callable from functools import wraps -from typing import Any, Self +from typing import Any, ParamSpec, Self, TypeVar +P = ParamSpec("P") +T = TypeVar("T") -def _plotting_func[T, R](func: Callable[T, R]) -> Callable[T, R]: + +def _plotting_func(func: Callable[P, T]) -> Callable[P, T]: """ Decorator for functions that interact with MPL. @@ -12,7 +15,7 @@ def _plotting_func[T, R](func: Callable[T, R]) -> Callable[T, R]: """ @wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> R: + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: import matplotlib.pyplot as pyplt was_interactive = pyplt.isinteractive() @@ -27,7 +30,7 @@ def wrapper(*args: Any, **kwargs: Any) -> R: class SpectraPlot: @_plotting_func - def __init__(self, api: Any, spectrum: int, period: int, dist: bool) -> None: + def __init__(self: Self, api: Any, spectrum: int, period: int, dist: bool) -> None: import matplotlib.pyplot as pyplt self.api = api @@ -61,7 +64,6 @@ def refresh(self) -> None: @_plotting_func def delete_plot(self, plotnum: int) -> None: - del self.ax.lines[plotnum] del self.spectra[plotnum] self.__update_legend() diff --git a/src/genie_python/genie_simulate_impl.py b/src/genie_python/genie_simulate_impl.py index a5289f8d..2416e2c5 100644 --- a/src/genie_python/genie_simulate_impl.py +++ b/src/genie_python/genie_simulate_impl.py @@ -92,8 +92,8 @@ def __init__(self) -> None: self.mon_spect = None self.mon_from = None self.mon_to = None - self.dae_sync = None - self.tcb_file = None + self.dae_sync: int | None = None + self.tcb_file: str | None = None self.tcb_tables = [] self.smp_veto = None self.ts2_veto = None @@ -107,7 +107,7 @@ def __init__(self) -> None: self.fermi_width = None self.periods_soft_num = None self.periods_type = None - self.periods_src = None + self.periods_src: int | None = None self.periods_file = None self.periods_seq = None self.periods_delay = None @@ -1161,7 +1161,7 @@ def reload_current_config(self) -> None: def correct_blockname(self, name: str, add_prefix: bool = True) -> str: return name - def get_block_names(self) -> []: + def get_block_names(self) -> list[str]: return list(self.block_dict.keys()) def block_exists(self, name: str) -> bool: @@ -1254,7 +1254,7 @@ def get_beamline_pars(self) -> "_GetbeamlineparsReturn": def set_beamline_par(self, name: str, value: "PVValue") -> None: self.beamline_pars[name] = value - def get_runcontrol_settings(self, name: str) -> (): + def get_runcontrol_settings(self, name: str) -> tuple[bool, float, float]: return ( self.block_dict[name]["runcontrol"], self.block_dict[name]["lowlimit"], diff --git a/src/genie_python/utilities.py b/src/genie_python/utilities.py index 8e521a25..5ddc7619 100644 --- a/src/genie_python/utilities.py +++ b/src/genie_python/utilities.py @@ -368,7 +368,7 @@ class EnvironmentDetails: INSTRUMENT_LIST_PV = "CS:INSTLIST" # List of instruments dictionary similar to CS:INSTLIST - DEFAULT_INST_LIST = ( + DEFAULT_INST_LIST = [ # ruff: ignore[RUF012] {"name": "LARMOR"}, {"name": "ALF"}, {"name": "DEMO"}, @@ -376,7 +376,7 @@ class EnvironmentDetails: {"name": "MUONFE"}, {"name": "ZOOM"}, {"name": "IRIS"}, - ) + ] def __init__(self, host_name: str | None = None) -> None: """ diff --git a/tests/test_genie_epics_api.py b/tests/test_genie_epics_api.py index 02953504..0178f208 100644 --- a/tests/test_genie_epics_api.py +++ b/tests/test_genie_epics_api.py @@ -146,7 +146,7 @@ def test_GIVEN_list_of_one_element_with_PV_prefix_bl_WHEN_get_beamline_pars_is_c # Assert self.assertEqual(len(val), 1) self.assertEqual(next(iter(val.keys())), pv_suffix) - self.assertEqual(val[pv_suffix], self.mock_pv_value) + self.assertEqual(val[pv_suffix], self.mock_pv_value) # pyright: ignore def test_GIVEN_list_of_one_element_with_PV_prefix_not_bl_WHEN_get_beamline_pars_is_called_THEN_returns_an_empty_dictionary( self, diff --git a/tests/test_genie_wait_for_move.py b/tests/test_genie_wait_for_move.py index 9db8ddd2..4030a60a 100644 --- a/tests/test_genie_wait_for_move.py +++ b/tests/test_genie_wait_for_move.py @@ -68,7 +68,7 @@ def test_GIVEN_method_check_timeouts_valid_WHEN_check_timeouts_THEN_returns_vali [("start_valid_move_none", 10.0, None), ("start_valid_move_invalid", 12.0, -30.0)] ) def test_GIVEN_method_check_timeouts_valid_WHEN_check_timeouts_THEN_returns_none( - self, _, start_input, move_input + self, _, start_input: float, move_input: float | None ): start_timeout = start_input move_timeout = move_input diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 53f437e7..d5753fdf 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -108,7 +108,7 @@ def setUp(self): genie.set_instrument(None, import_instrument_init=False) def tearDown(self): - self.sim.__exit__() + self.sim.__exit__(None, None, None) def test_GIVEN_one_block_WHEN_cset_value_for_block_THEN_set_correct_value(self): create_dummy_blocks(["a"])