From 1df31abfce925ce33f1635176bd376bcaf4c04f9 Mon Sep 17 00:00:00 2001 From: Liam Keegan Date: Tue, 30 Jan 2024 11:02:12 +0100 Subject: [PATCH] Replace black/reorder-python-imports/flake8 with ruff --- .pre-commit-config.yaml | 24 +++------- docs/notebooks/area_calculation.ipynb | 17 ++++--- docs/notebooks/example.ipynb | 5 +-- docs/notebooks/raw_data.ipynb | 25 +++++------ pyproject.toml | 20 +++++++++ src/vstt/__main__.py | 4 +- src/vstt/common.py | 11 ++--- src/vstt/display.py | 4 +- src/vstt/display_widget.py | 12 +++-- src/vstt/experiment.py | 32 ++++++-------- src/vstt/geom.py | 17 +++---- src/vstt/gui.py | 18 ++++---- src/vstt/joystick_wrapper.py | 6 +-- src/vstt/meta.py | 4 +- src/vstt/meta_widget.py | 24 ++++------ src/vstt/results_widget.py | 7 ++- src/vstt/stats.py | 27 +++++------ src/vstt/task.py | 64 ++++++++++++--------------- src/vstt/trial.py | 20 +++------ src/vstt/trials_widget.py | 14 +++--- src/vstt/update.py | 8 ++-- src/vstt/vis.py | 50 +++++++++------------ src/vstt/vtypes.py | 4 +- tests/conftest.py | 3 +- tests/helpers/gui_test_utils.py | 11 +++-- tests/helpers/qt_test_utils.py | 19 +++----- tests/test_display_widget.py | 2 +- tests/test_gui.py | 26 ++++++----- tests/test_meta_widget.py | 14 +++--- tests/test_results_widget.py | 5 +-- tests/test_stats.py | 6 +-- tests/test_task.py | 15 +++---- tests/test_update.py | 3 +- tests/test_vis.py | 8 +--- 34 files changed, 233 insertions(+), 296 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5987fb5b..34d82838 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,11 +9,14 @@ repos: - id: mixed-line-ending - id: check-toml - - repo: https://github.com/psf/black - rev: 23.12.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.15 hooks: - - id: black - - id: black-jupyter + - id: ruff + types_or: [python, pyi, jupyter] + args: [--fix] + - id: ruff-format + types_or: [python, pyi, jupyter] - repo: https://github.com/kynan/nbstripout rev: 0.6.1 @@ -46,19 +49,6 @@ repos: --warn-redundant-casts, ] - - repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 - hooks: - - id: reorder-python-imports - - - repo: https://github.com/pycqa/flake8 - rev: "7.0.0" - hooks: - - id: flake8 - args: - - "--max-line-length=88" - - "--ignore=E501,W503,E203" - - repo: https://github.com/rhysd/actionlint rev: "v1.6.26" hooks: diff --git a/docs/notebooks/area_calculation.ipynb b/docs/notebooks/area_calculation.ipynb index 61e0f191..68edd6f9 100644 --- a/docs/notebooks/area_calculation.ipynb +++ b/docs/notebooks/area_calculation.ipynb @@ -13,9 +13,9 @@ "metadata": {}, "outputs": [], "source": [ - "from psychopy.misc import fromFile\n", + "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "import matplotlib.pyplot as plt" + "from psychopy.misc import fromFile" ] }, { @@ -118,9 +118,10 @@ "metadata": {}, "outputs": [], "source": [ + "from typing import List, Tuple\n", + "\n", "from shapely.geometry import LineString\n", - "from shapely.ops import polygonize, unary_union\n", - "from typing import List, Tuple" + "from shapely.ops import polygonize, unary_union" ] }, { @@ -167,11 +168,13 @@ "outputs": [], "source": [ "def plot_and_calculate_area(data):\n", + " colors = [\"blue\", \"green\", \"red\", \"cyan\", \"magenta\", \"yellow\", \"black\", \"orange\"]\n", " nTrials, nReps = data.sequenceIndices.shape\n", " for trial in range(nTrials):\n", " for rep in range(nReps):\n", " loc = (trial, rep)\n", " condition = data.sequenceIndices[loc]\n", + " target_radius = data.trialList[condition][\"target_size\"]\n", " central_target_radius = data.trialList[condition][\"central_target_size\"]\n", "\n", " # if condition \"automove_cursor_to_center\" is deselected, plot the line to center, fill the enclosed area and output the area\n", @@ -211,7 +214,7 @@ " alpha=0.1,\n", " )\n", " )\n", - " print(\"%s, area: %f\" % (color, area))\n", + " print(f\"{color}, area: {area:f}\")\n", "\n", " ax.add_patch(\n", " plt.Circle(\n", @@ -349,9 +352,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.9.15" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/docs/notebooks/example.ipynb b/docs/notebooks/example.ipynb index d9fb3014..aaaad4a9 100644 --- a/docs/notebooks/example.ipynb +++ b/docs/notebooks/example.ipynb @@ -13,10 +13,9 @@ "metadata": {}, "outputs": [], "source": [ - "import vstt\n", + "import matplotlib.pyplot as plt\n", "import pandas as pd\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" + "import vstt" ] }, { diff --git a/docs/notebooks/raw_data.ipynb b/docs/notebooks/raw_data.ipynb index 90a0cbaa..6f314398 100644 --- a/docs/notebooks/raw_data.ipynb +++ b/docs/notebooks/raw_data.ipynb @@ -17,15 +17,12 @@ "metadata": {}, "outputs": [], "source": [ - "from psychopy.misc import fromFile\n", - "import pandas as pd\n", - "import numpy as np\n", "import matplotlib.pyplot as plt\n", - "from shapely.geometry import Polygon, LineString, MultiPolygon, LinearRing\n", - "from shapely.ops import polygonize, unary_union\n", - "from shapely.validation import make_valid\n", - "\n", - "# from matplotlib.patches import Polygon" + "import numpy as np\n", + "import pandas as pd\n", + "from psychopy.misc import fromFile\n", + "from shapely.geometry import LineString, Polygon\n", + "from shapely.ops import polygonize, unary_union" ] }, { @@ -288,7 +285,7 @@ " f\"area plot for Trial {trial}, Rep {rep} [Condition {condition}]\"\n", " )\n", " print(\"---------------------------------------------\")\n", - " print(\"area of Trial %d, Rep %d [Condition %s]\" % (trial, rep, condition))\n", + " print(f\"area of Trial {trial}, Rep {rep} [Condition {condition}]\")\n", " for (\n", " to_target_mouse_positions,\n", " to_center_mouse_positions,\n", @@ -327,7 +324,7 @@ " alpha=0.1,\n", " )\n", " )\n", - " print(\"%s, area: %f\" % (color, area))\n", + " print(f\"{color}, area: {area}\")\n", "\n", " ax.add_patch(\n", " plt.Circle(\n", @@ -370,6 +367,7 @@ "metadata": {}, "outputs": [], "source": [ + "colors = [\"blue\", \"green\", \"red\", \"cyan\", \"magenta\", \"yellow\", \"black\", \"orange\"]\n", "nConditions = len(psydata.trialList)\n", "nTrials, nReps = psydata.sequenceIndices.shape\n", "fig, axs = plt.subplots(nConditions, 1, figsize=(6, 6 * nConditions))\n", @@ -429,10 +427,7 @@ "central_target_radius = psydata.trialList[condition][\"central_target_size\"]\n", "for dest, ax in zip([\"target\", \"center\"], axs):\n", " positions = psydata.data[f\"to_{dest}_mouse_positions\"][loc][i_target]\n", - " if dest == \"target\":\n", - " target = psydata.data[\"target_pos\"][loc][i_target]\n", - " else:\n", - " target = [0, 0]\n", + " target = psydata.data[\"target_pos\"][loc][i_target] if dest == \"target\" else [0, 0]\n", " times = psydata.data[f\"to_{dest}_timestamps\"][loc][i_target]\n", " ax.set_title(f\"Mouse movements to {dest}\")\n", " ax.plot(times, positions[:, 0], label=\"x\")\n", @@ -459,7 +454,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.9.15" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index aa801069..d7aa219f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,3 +69,23 @@ version = { attr = "vstt.__version__" } [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.ruff] +extend-include = ["*.ipynb"] + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +ignore = ["E501"] diff --git a/src/vstt/__main__.py b/src/vstt/__main__.py index 5540354a..2a834a4e 100644 --- a/src/vstt/__main__.py +++ b/src/vstt/__main__.py @@ -1,17 +1,17 @@ from __future__ import annotations import logging -from typing import Optional import click from psychopy.gui.qtgui import ensureQtApp from qtpy import QtWidgets + from vstt.gui import Gui @click.command() @click.argument("filename", required=False) -def main(filename: Optional[str]) -> None: +def main(filename: str | None) -> None: logging.basicConfig( format="%(levelname)s %(module)s.%(funcName)s.%(lineno)d :: %(message)s" ) diff --git a/src/vstt/common.py b/src/vstt/common.py index f9c8caaa..6c2ed914 100644 --- a/src/vstt/common.py +++ b/src/vstt/common.py @@ -2,14 +2,9 @@ import copy import logging -from typing import Any -from typing import Mapping -from typing import Type -from typing import TypeVar +from typing import Any, Mapping, TypeVar -from vstt.vtypes import DisplayOptions -from vstt.vtypes import Metadata -from vstt.vtypes import Trial +from vstt.vtypes import DisplayOptions, Metadata, Trial VsttTypedDict = TypeVar( "VsttTypedDict", @@ -19,7 +14,7 @@ ) -def _has_valid_type(var: Any, correct_type: Type) -> bool: +def _has_valid_type(var: Any, correct_type: type) -> bool: if isinstance(var, correct_type): # var has the correct type return True diff --git a/src/vstt/display.py b/src/vstt/display.py index 91b5ce43..e3da80ca 100644 --- a/src/vstt/display.py +++ b/src/vstt/display.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Dict - import vstt @@ -33,7 +31,7 @@ def default_display_options() -> vstt.vtypes.DisplayOptions: } -def display_options_labels() -> Dict[str, str]: +def display_options_labels() -> dict[str, str]: return { "to_target_paths": "Display cursor paths to target", "to_center_paths": "Display cursor paths back to center", diff --git a/src/vstt/display_widget.py b/src/vstt/display_widget.py index c09434ff..2464f62a 100644 --- a/src/vstt/display_widget.py +++ b/src/vstt/display_widget.py @@ -1,12 +1,10 @@ from __future__ import annotations from typing import Callable -from typing import Dict -from typing import Optional from psychopy.visual.window import Window -from qtpy import QtCore -from qtpy import QtWidgets +from qtpy import QtCore, QtWidgets + from vstt.display import display_options_labels from vstt.experiment import Experiment @@ -15,12 +13,12 @@ class DisplayOptionsWidget(QtWidgets.QWidget): experiment_modified = QtCore.Signal() def __init__( - self, parent: Optional[QtWidgets.QWidget] = None, win: Optional[Window] = None + self, parent: QtWidgets.QWidget | None = None, win: Window | None = None ): super().__init__(parent) self._win = win self._experiment: Experiment = Experiment() - self._widgets: Dict[str, QtWidgets.QCheckBox] = {} + self._widgets: dict[str, QtWidgets.QCheckBox] = {} outer_layout = QtWidgets.QVBoxLayout() group_box = QtWidgets.QGroupBox("Display Options") @@ -28,7 +26,7 @@ def __init__( inner_layout = QtWidgets.QVBoxLayout() group_box.setLayout(inner_layout) labels = display_options_labels() - for row_index, key in enumerate(labels.keys()): + for _row_index, key in enumerate(labels.keys()): checkbox = QtWidgets.QCheckBox(f"{labels[key]}", self) inner_layout.addWidget(checkbox) checkbox.clicked.connect(self._update_value_callback(key)) diff --git a/src/vstt/experiment.py b/src/vstt/experiment.py index 963a338e..21635433 100644 --- a/src/vstt/experiment.py +++ b/src/vstt/experiment.py @@ -4,32 +4,26 @@ import pathlib import pickle from typing import Any -from typing import Dict -from typing import List -from typing import Optional import pandas as pd from psychopy.data import TrialHandlerExt from psychopy.misc import fromFile -from vstt.display import default_display_options -from vstt.display import import_display_options -from vstt.meta import default_metadata -from vstt.meta import import_metadata -from vstt.stats import append_stats_data_to_excel -from vstt.stats import stats_dataframe -from vstt.trial import default_trial -from vstt.trial import import_and_validate_trial + +from vstt.display import default_display_options, import_display_options +from vstt.meta import default_metadata, import_metadata +from vstt.stats import append_stats_data_to_excel, stats_dataframe +from vstt.trial import default_trial, import_and_validate_trial class Experiment: - def __init__(self, filename: Optional[str] = None): + def __init__(self, filename: str | None = None): self.filename = "default-experiment.psydat" self.has_unsaved_changes = False self.metadata = default_metadata() self.display_options = default_display_options() self.trial_list = [default_trial()] - self.trial_handler_with_results: Optional[TrialHandlerExt] = None - self.stats: Optional[pd.DataFrame] = None + self.trial_handler_with_results: TrialHandlerExt | None = None + self.stats: pd.DataFrame | None = None if filename is not None: self.load_file(filename) @@ -50,7 +44,7 @@ def create_trialhandler(self) -> TrialHandlerExt: def clear_results(self) -> None: self.trial_handler_with_results = None - def _as_dict(self) -> Dict[str, Any]: + def _as_dict(self) -> dict[str, Any]: return { "metadata": self.metadata, "display_options": self.display_options, @@ -131,7 +125,7 @@ def save_json(self, filename: str) -> None: json.dump(self._as_dict(), f) def load_json(self, filename: str) -> None: - with open(filename, "r") as f: + with open(filename) as f: d = json.load(f) self.import_and_validate_dicts( filename, d["metadata"], d["display_options"], d["trial_list"] @@ -140,9 +134,9 @@ def load_json(self, filename: str) -> None: def import_and_validate_dicts( self, filename: str, - metadata_dict: Dict, - display_options_dict: Dict, - trial_dict_list: List[Dict], + metadata_dict: dict, + display_options_dict: dict, + trial_dict_list: list[dict], ) -> None: self.metadata = import_metadata(metadata_dict) self.display_options = import_display_options(display_options_dict) diff --git a/src/vstt/geom.py b/src/vstt/geom.py index 46d08200..421dbe0e 100644 --- a/src/vstt/geom.py +++ b/src/vstt/geom.py @@ -1,8 +1,5 @@ from __future__ import annotations -from typing import Optional -from typing import Tuple - import numpy as np @@ -27,10 +24,10 @@ def points_on_circle( def rotate_point( - point: Tuple[float, float], + point: tuple[float, float], angle_radians: float, - pivot_point: Tuple[float, float] = (0, 0), -) -> Tuple[float, float]: + pivot_point: tuple[float, float] = (0, 0), +) -> tuple[float, float]: """ Rotate `point` by an angle `angle_radians` around the point `pivot_point` """ @@ -52,7 +49,7 @@ def __init__(self, angle_degrees: float): self._s = np.sin(angle_radians) self._c = np.cos(angle_radians) - def __call__(self, point: Tuple[float, float]) -> np.ndarray: + def __call__(self, point: tuple[float, float]) -> np.ndarray: return np.array( [ self._c * point[0] - self._s * point[1], @@ -83,7 +80,7 @@ def __init__( self, angle_degrees: float, max_speed: float, - window_size: Optional[np.ndarray] = None, + window_size: np.ndarray | None = None, ): self.angle_degrees = angle_degrees self.max_speed = max_speed @@ -95,7 +92,7 @@ def __init__( self._s = self.max_speed * np.sin(angle_radians) self._c = self.max_speed * np.cos(angle_radians) - def __call__(self, point: np.ndarray, velocity: Tuple[float, float]) -> np.ndarray: + def __call__(self, point: np.ndarray, velocity: tuple[float, float]) -> np.ndarray: return np.clip( np.array( [ @@ -110,7 +107,7 @@ def __call__(self, point: np.ndarray, velocity: Tuple[float, float]) -> np.ndarr def to_target_dists( pos: np.ndarray, target_xys: np.ndarray, target_index: int, has_central_target: bool -) -> Tuple[float, float]: +) -> tuple[float, float]: rms_dists = np.linalg.norm(target_xys - np.array(pos), axis=1) n_targets_excluding_central = target_xys.shape[0] if has_central_target: diff --git a/src/vstt/gui.py b/src/vstt/gui.py index 6b7d8266..d62ee40a 100644 --- a/src/vstt/gui.py +++ b/src/vstt/gui.py @@ -3,26 +3,24 @@ import logging import pathlib from typing import Callable -from typing import Optional -import vstt from psychopy.visual.window import Window -from qtpy import QtGui -from qtpy import QtWidgets +from qtpy import QtGui, QtWidgets from qtpy.compat import getsavefilename from qtpy.QtCore import Qt + +import vstt from vstt.display_widget import DisplayOptionsWidget from vstt.experiment import Experiment from vstt.meta_widget import MetadataWidget from vstt.results_widget import ResultsWidget from vstt.task import MotorTask from vstt.trials_widget import TrialsWidget -from vstt.update import check_for_new_version -from vstt.update import do_pip_upgrade +from vstt.update import check_for_new_version, do_pip_upgrade class Gui(QtWidgets.QMainWindow): - def __init__(self, filename: Optional[str] = None, win: Optional[Window] = None): + def __init__(self, filename: str | None = None, win: Window | None = None): super().__init__() self.experiment = Experiment() self._win = win @@ -254,9 +252,9 @@ def _add_action( name: str, callback: Callable, menu: QtWidgets.QMenu, - toolbar: Optional[QtWidgets.QToolBar] = None, - shortcut: Optional[str] = None, - icon_pixmap: Optional[QtWidgets.QStyle.StandardPixmap] = None, + toolbar: QtWidgets.QToolBar | None = None, + shortcut: str | None = None, + icon_pixmap: QtWidgets.QStyle.StandardPixmap | None = None, ) -> None: action = QtWidgets.QAction(name, menu) action.triggered.connect(callback) diff --git a/src/vstt/joystick_wrapper.py b/src/vstt/joystick_wrapper.py index 49502861..449e4be6 100644 --- a/src/vstt/joystick_wrapper.py +++ b/src/vstt/joystick_wrapper.py @@ -5,15 +5,15 @@ If this device is already open (i.e. we already created a Joystick) then pyglet raises an exception. Here we keep track of the current joystick object and attempt to close the underlying pyglet device. """ + from __future__ import annotations import logging -from typing import Optional from psychopy.hardware import joystick from pyglet.input import DeviceOpenException -js: Optional[joystick.Joystick] = None +js: joystick.Joystick | None = None def _close_open_device() -> None: @@ -35,7 +35,7 @@ def _updated_joystick() -> joystick.Joystick: return joystick.Joystick(0) -def get_joystick() -> Optional[joystick.Joystick]: +def get_joystick() -> joystick.Joystick | None: global js if joystick.getNumJoysticks() == 0: return None diff --git a/src/vstt/meta.py b/src/vstt/meta.py index 3e6b7e16..ccc54393 100644 --- a/src/vstt/meta.py +++ b/src/vstt/meta.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Dict - import vstt @@ -24,7 +22,7 @@ def default_metadata() -> vstt.vtypes.Metadata: } -def metadata_labels() -> Dict: +def metadata_labels() -> dict: return { "name": "Experiment Name", "subject": "Subject Name", diff --git a/src/vstt/meta_widget.py b/src/vstt/meta_widget.py index 0c927a8a..bf7e1e1c 100644 --- a/src/vstt/meta_widget.py +++ b/src/vstt/meta_widget.py @@ -1,17 +1,13 @@ from __future__ import annotations from typing import Callable -from typing import Dict -from typing import Optional -from typing import Union from psychopy.visual.window import Window -from qtpy import QtCore -from qtpy import QtWidgets +from qtpy import QtCore, QtWidgets from qtpy.QtCore import Qt + from vstt.experiment import Experiment -from vstt.meta import default_metadata -from vstt.meta import metadata_labels +from vstt.meta import default_metadata, metadata_labels from vstt.vis import splash_screen @@ -19,14 +15,14 @@ class MetadataWidget(QtWidgets.QWidget): experiment_modified = QtCore.Signal() def __init__( - self, parent: Optional[QtWidgets.QWidget] = None, win: Optional[Window] = None + self, parent: QtWidgets.QWidget | None = None, win: Window | None = None ): super().__init__(parent) self._win = win self._experiment = Experiment() - self._str_widgets: Dict[str, QtWidgets.QLineEdit] = {} - self._bool_widgets: Dict[str, QtWidgets.QCheckBox] = {} - self._float_widgets: Dict[str, QtWidgets.QDoubleSpinBox] = {} + self._str_widgets: dict[str, QtWidgets.QLineEdit] = {} + self._bool_widgets: dict[str, QtWidgets.QCheckBox] = {} + self._float_widgets: dict[str, QtWidgets.QDoubleSpinBox] = {} group_box = QtWidgets.QGroupBox("Metadata") outer_layout = QtWidgets.QVBoxLayout() @@ -66,10 +62,8 @@ def __init__( inner_layout.addWidget(self._btn_preview_metadata) self.setLayout(outer_layout) - def _update_value_callback( - self, key: str - ) -> Callable[[Union[str, bool, float]], None]: - def _update_value(value: Union[str, bool, float]) -> None: + def _update_value_callback(self, key: str) -> Callable[[str | bool | float], None]: + def _update_value(value: str | bool | float) -> None: if value != self._experiment.metadata[key]: # type: ignore self._experiment.metadata[key] = value # type: ignore self._experiment.has_unsaved_changes = True diff --git a/src/vstt/results_widget.py b/src/vstt/results_widget.py index c401e9a2..2174fb0e 100644 --- a/src/vstt/results_widget.py +++ b/src/vstt/results_widget.py @@ -2,20 +2,19 @@ import logging import pathlib -from typing import List -from typing import Optional import numpy as np from psychopy.visual.window import Window from qtpy import QtWidgets from qtpy.compat import getsavefilename + from vstt.experiment import Experiment from vstt.vis import display_results class ResultsWidget(QtWidgets.QWidget): def __init__( - self, parent: Optional[QtWidgets.QWidget] = None, win: Optional[Window] = None + self, parent: QtWidgets.QWidget | None = None, win: Window | None = None ): super().__init__(parent) self._win = win @@ -26,7 +25,7 @@ def __init__( outer_layout.addWidget(group_box) inner_layout = QtWidgets.QGridLayout() group_box.setLayout(inner_layout) - self._list_trial_row_to_trial_index: List[int] = [] + self._list_trial_row_to_trial_index: list[int] = [] self._list_trials = QtWidgets.QListWidget() self._list_trials.currentRowChanged.connect(self._row_changed) self._list_trials.itemDoubleClicked.connect(self._btn_display_trial_clicked) diff --git a/src/vstt/stats.py b/src/vstt/stats.py index 5142b062..dcbc650b 100644 --- a/src/vstt/stats.py +++ b/src/vstt/stats.py @@ -2,10 +2,6 @@ import logging from typing import Any -from typing import Dict -from typing import List -from typing import Tuple -from typing import Union import numpy as np import pandas as pd @@ -13,11 +9,10 @@ from psychopy.data import TrialHandlerExt from psychopy.event import xydist from shapely.geometry import LineString -from shapely.ops import polygonize -from shapely.ops import unary_union +from shapely.ops import polygonize, unary_union -def list_dest_stat_label_units() -> List[Tuple[str, List[Tuple[str, str, str]]]]: +def list_dest_stat_label_units() -> list[tuple[str, list[tuple[str, str, str]]]]: list_dest_stats = [] for destination in ["target", "center"]: stats = [] @@ -39,7 +34,7 @@ def list_dest_stat_label_units() -> List[Tuple[str, List[Tuple[str, str, str]]]] return list_dest_stats -def _get_trial_data_columns() -> List[str]: +def _get_trial_data_columns() -> list[str]: return [ "i_trial", "i_rep", @@ -62,7 +57,7 @@ def _get_trial_data_columns() -> List[str]: def _get_dat( - data: Dict, key: str, index: Tuple, i_target: int, default_value: Any + data: dict, key: str, index: tuple, i_target: int, default_value: Any ) -> Any: ar = data.get(key) if ar is None: @@ -80,8 +75,8 @@ def _get_dat( def _get_target_data( - trial_handler: TrialHandlerExt, index: Tuple, i_target: int -) -> List: + trial_handler: TrialHandlerExt, index: tuple, i_target: int +) -> list: data = trial_handler.data condition_index = trial_handler.sequenceIndices[index] conditions = trial_handler.trialList[condition_index] @@ -98,7 +93,7 @@ def _get_target_data( np.array(data["to_target_mouse_positions"][index][i_target]) ) # type: ignore to_target_success = np.array(data["to_target_success"][index][i_target]) - to_center_success: Union[np.ndarray, bool] + to_center_success: np.ndarray | bool to_center_timestamps = np.array( _get_dat(data, "to_center_timestamps", index, i_target, []) ) @@ -152,7 +147,7 @@ def stats_dataframe(trial_handler: TrialHandlerExt) -> pd.DataFrame: _distance ) df[f"to_{destination}_reaction_time"] = df.apply( - lambda x: _reaction_time( + lambda x, destination=destination: _reaction_time( x[f"to_{destination}_timestamps"], x[f"to_{destination}_mouse_positions"], x[f"to_{destination}_num_timestamps_before_visible"], @@ -160,7 +155,7 @@ def stats_dataframe(trial_handler: TrialHandlerExt) -> pd.DataFrame: axis=1, ) df[f"to_{destination}_time"] = df.apply( - lambda x: _total_time( + lambda x, destination=destination: _total_time( x[f"to_{destination}_timestamps"], x[f"to_{destination}_num_timestamps_before_visible"], ), @@ -170,13 +165,13 @@ def stats_dataframe(trial_handler: TrialHandlerExt) -> pd.DataFrame: df[f"to_{destination}_time"] - df[f"to_{destination}_reaction_time"] ) df[f"to_{destination}_rmse"] = df.apply( - lambda x: _rmse( + lambda x, destination=destination: _rmse( x[f"to_{destination}_mouse_positions"], x[f"{destination}_pos"] ), axis=1, ) df[f"to_{destination}_spatial_error"] = df.apply( - lambda x: _spatial_error( + lambda x, destination=destination: _spatial_error( x[f"to_{destination}_mouse_positions"], x[f"{destination}_pos"], x[f"{destination}_radius"], diff --git a/src/vstt/task.py b/src/vstt/task.py index 695c0093..b50a0c1a 100644 --- a/src/vstt/task.py +++ b/src/vstt/task.py @@ -1,16 +1,9 @@ from __future__ import annotations import logging -from typing import Any -from typing import Dict -from typing import Iterable -from typing import List -from typing import Optional -from typing import Tuple -from typing import Union +from typing import Any, Iterable import numpy as np -import vstt.vtypes from psychopy.clock import Clock from psychopy.data import TrialHandlerExt from psychopy.event import Mouse @@ -20,16 +13,15 @@ from psychopy.visual.elementarray import ElementArrayStim from psychopy.visual.shape import ShapeStim from psychopy.visual.window import Window -from vstt import joystick_wrapper -from vstt import vis + +import vstt.vtypes +from vstt import joystick_wrapper, vis from vstt.experiment import Experiment -from vstt.geom import JoystickPointUpdater -from vstt.geom import PointRotator -from vstt.geom import to_target_dists +from vstt.geom import JoystickPointUpdater, PointRotator, to_target_dists from vstt.stats import stats_dataframe -def _get_target_indices(outer_target_index: int, trial: Dict[str, Any]) -> List[int]: +def _get_target_indices(outer_target_index: int, trial: dict[str, Any]) -> list[int]: # always include outer target index indices = [outer_target_index] if trial["add_central_target"] and not trial["automove_cursor_to_center"]: @@ -41,21 +33,21 @@ def _get_target_indices(outer_target_index: int, trial: Dict[str, Any]) -> List[ class TrialData: """Stores the data from the trial that will be stored in the psychopy trial_handler""" - def __init__(self, trial: Dict[str, Any], rng: np.random.Generator): + def __init__(self, trial: dict[str, Any], rng: np.random.Generator): self.target_indices = np.fromstring( trial["target_indices"], dtype="int", sep=" " ) if trial["target_order"] == "random": rng.shuffle(self.target_indices) - self.target_pos: List[np.ndarray] = [] - self.to_target_timestamps: List[np.ndarray] = [] - self.to_target_num_timestamps_before_visible: List[int] = [] - self.to_center_timestamps: List[np.ndarray] = [] - self.to_center_num_timestamps_before_visible: List[int] = [] - self.to_target_mouse_positions: List[np.ndarray] = [] - self.to_center_mouse_positions: List[np.ndarray] = [] - self.to_target_success: List[bool] = [] - self.to_center_success: List[bool] = [] + self.target_pos: list[np.ndarray] = [] + self.to_target_timestamps: list[np.ndarray] = [] + self.to_target_num_timestamps_before_visible: list[int] = [] + self.to_center_timestamps: list[np.ndarray] = [] + self.to_center_num_timestamps_before_visible: list[int] = [] + self.to_target_mouse_positions: list[np.ndarray] = [] + self.to_center_mouse_positions: list[np.ndarray] = [] + self.to_target_success: list[bool] = [] + self.to_center_success: list[bool] = [] class TrialManager: @@ -70,7 +62,7 @@ def __init__(self, win: Window, trial: vstt.vtypes.Trial): trial["add_central_target"], trial["central_target_size"], ) - self.drawables: List[Union[BaseVisualStim, ElementArrayStim]] = [self.targets] + self.drawables: list[BaseVisualStim | ElementArrayStim] = [self.targets] self.target_labels = None if trial["show_target_labels"]: self.target_labels = vis.make_target_labels( @@ -92,7 +84,7 @@ def __init__(self, win: Window, trial: vstt.vtypes.Trial): self._cursor_path = ShapeStim( win, vertices=[(0.0, 0.0)], lineColor="white", closeShape=False ) - self._cursor_path_vertices: List[Tuple[float, float]] = [] + self._cursor_path_vertices: list[tuple[float, float]] = [] self.clock = Clock() if trial["show_cursor_path"]: self.drawables.append(self._cursor_path) @@ -101,7 +93,7 @@ def __init__(self, win: Window, trial: vstt.vtypes.Trial): self.final_target_display_time_previous_trial = 0.0 def cursor_path_add_vertex( - self, vertex: Tuple[float, float], clear_existing: bool = False + self, vertex: tuple[float, float], clear_existing: bool = False ) -> None: if clear_existing: self._cursor_path_vertices = [] @@ -125,7 +117,7 @@ def add_trial_data_to_trial_handler( class MotorTask: - def __init__(self, experiment: Experiment, win: Optional[Window] = None): + def __init__(self, experiment: Experiment, win: Window | None = None): self.close_window_when_done = False self.experiment = experiment if win is None: @@ -165,7 +157,7 @@ def run(self) -> bool: def _do_trials(self) -> None: self._do_splash_screen() - condition_trial_indices: List[List[int]] = [ + condition_trial_indices: list[list[int]] = [ [] for _ in self.trial_handler.trialList ] current_condition_index = -1 @@ -227,11 +219,11 @@ def _do_splash_screen(self) -> None: def _do_trial( self, - trial: Dict[str, Any], + trial: dict[str, Any], trial_manager: TrialManager, - initial_cursor_pos: Tuple[float, float], + initial_cursor_pos: tuple[float, float], condition_timeout: float, - ) -> Tuple[float, float]: + ) -> tuple[float, float]: if trial["use_joystick"] and self.js is None: raise RuntimeError("Use joystick option is enabled, but no joystick found.") trial_manager.cursor.setPos(initial_cursor_pos) @@ -279,7 +271,7 @@ def _do_trial( return trial_manager.cursor.pos def _do_target( - self, trial: Dict[str, Any], index: int, tm: TrialManager, trial_data: TrialData + self, trial: dict[str, Any], index: int, tm: TrialManager, trial_data: TrialData ) -> None: minimum_window_for_flip = 1.0 / 60.0 mouse_pos = tm.cursor.pos @@ -333,7 +325,8 @@ def _do_target( if not trial["freeze_cursor_between_targets"]: if trial["use_joystick"]: mouse_pos = tm.joystick_point_updater( - mouse_pos, (self.js.getX(), self.js.getY()) # type: ignore + mouse_pos, + (self.js.getX(), self.js.getY()), # type: ignore ) else: mouse_pos = tm.point_rotator(self.mouse.getPos()) @@ -381,7 +374,8 @@ def _do_target( vis.draw_and_flip(self.win, tm.drawables, self.kb) if trial["use_joystick"]: mouse_pos = tm.joystick_point_updater( - mouse_pos, (self.js.getX(), self.js.getY()) # type: ignore + mouse_pos, + (self.js.getX(), self.js.getY()), # type: ignore ) else: mouse_pos = tm.point_rotator(self.mouse.getPos()) diff --git a/src/vstt/trial.py b/src/vstt/trial.py index 9a8382ad..7f077284 100644 --- a/src/vstt/trial.py +++ b/src/vstt/trial.py @@ -1,14 +1,11 @@ from __future__ import annotations import copy -from typing import Any -from typing import Dict -from typing import List -from typing import Mapping -from typing import Optional +from typing import Any, Mapping import numpy as np from psychopy.gui.qtgui import DlgFromDict + from vstt.common import import_typed_dict from vstt.vtypes import Trial @@ -22,7 +19,7 @@ def describe_trial(trial: Trial) -> str: return f"{repeats} of {trial['num_targets']} {trial['target_order']} {targets}" -def describe_trials(trials: List[Trial]) -> str: +def describe_trials(trials: list[Trial]) -> str: return "\n".join([" - " + describe_trial(trial) for trial in trials]) @@ -66,7 +63,7 @@ def default_trial() -> Trial: } -def trial_labels() -> Dict: +def trial_labels() -> dict: return { "weight": "Repetitions", "condition_timeout": "Maximum time, 0=unlimited (secs)", @@ -107,12 +104,9 @@ def trial_labels() -> Dict: def get_trial_from_user( - initial_trial: Optional[Trial] = None, -) -> Optional[Trial]: - if initial_trial: - trial = copy.deepcopy(initial_trial) - else: - trial = default_trial() + initial_trial: Trial | None = None, +) -> Trial | None: + trial = copy.deepcopy(initial_trial) if initial_trial else default_trial() order_of_targets = [trial["target_order"]] for target_order in ["clockwise", "anti-clockwise", "random", "fixed"]: if target_order != order_of_targets[0]: diff --git a/src/vstt/trials_widget.py b/src/vstt/trials_widget.py index 61459bb8..44d6ec46 100644 --- a/src/vstt/trials_widget.py +++ b/src/vstt/trials_widget.py @@ -1,21 +1,17 @@ from __future__ import annotations -from typing import Optional - from psychopy.visual.window import Window -from qtpy import QtCore -from qtpy import QtWidgets +from qtpy import QtCore, QtWidgets + from vstt.experiment import Experiment -from vstt.trial import describe_trial -from vstt.trial import get_trial_from_user -from vstt.trial import Trial +from vstt.trial import Trial, describe_trial, get_trial_from_user class TrialsWidget(QtWidgets.QWidget): experiment_modified = QtCore.Signal() def __init__( - self, parent: Optional[QtWidgets.QWidget] = None, win: Optional[Window] = None + self, parent: QtWidgets.QWidget | None = None, win: Window | None = None ): super().__init__(parent) self._win = win @@ -64,7 +60,7 @@ def _row_changed(self) -> None: self._btn_move_up.setEnabled(self._can_move_up(row)) self._btn_move_down.setEnabled(self._can_move_down(row)) - def _current_trial(self) -> Optional[Trial]: + def _current_trial(self) -> Trial | None: row = self._widget_list_trials.currentRow() if not self._is_valid(row): return None diff --git a/src/vstt/update.py b/src/vstt/update.py index c9f94b92..ec339089 100644 --- a/src/vstt/update.py +++ b/src/vstt/update.py @@ -3,12 +3,12 @@ import logging import subprocess import sys -from typing import Tuple import requests -import vstt from packaging import version +import vstt + def _get_latest_pypi_version() -> version.Version: logging.info("Requesting latest version from pypi...") @@ -19,7 +19,7 @@ def _get_latest_pypi_version() -> version.Version: return ver -def check_for_new_version() -> Tuple[bool, str]: +def check_for_new_version() -> tuple[bool, str]: try: current_version = version.Version(vstt.__version__) logging.info(f"Current version: {current_version}") @@ -43,7 +43,7 @@ def check_for_new_version() -> Tuple[bool, str]: ) -def do_pip_upgrade() -> Tuple[bool, str]: +def do_pip_upgrade() -> tuple[bool, str]: logging.info(f"Doing pip upgrade using {sys.executable}...") try: subprocess.check_call( diff --git a/src/vstt/vis.py b/src/vstt/vis.py index c59fd938..ad8475a7 100644 --- a/src/vstt/vis.py +++ b/src/vstt/vis.py @@ -1,9 +1,5 @@ from __future__ import annotations -from typing import List -from typing import Optional -from typing import Tuple - import numpy as np import pandas as pd from PIL.Image import Image @@ -18,12 +14,10 @@ from psychopy.visual.shape import ShapeStim from psychopy.visual.textbox2 import TextBox2 from psychopy.visual.window import Window + from vstt.geom import points_on_circle -from vstt.stats import get_closed_polygon -from vstt.stats import list_dest_stat_label_units -from vstt.stats import stats_dataframe -from vstt.vtypes import DisplayOptions -from vstt.vtypes import Metadata +from vstt.stats import get_closed_polygon, list_dest_stat_label_units, stats_dataframe +from vstt.vtypes import DisplayOptions, Metadata colors = [ color for name, color in colorNames.items() if name not in ["none", "transparent"] @@ -77,8 +71,8 @@ def make_target_labels( radius: float, point_radius: float, labels_string: str, -) -> List[TextBox2]: - text_boxes: List[TextBox2] = [] +) -> list[TextBox2]: + text_boxes: list[TextBox2] = [] positions = points_on_circle(n_circles, radius, include_centre=False) labels = labels_string.strip().split(" ") for label, position in zip( @@ -101,7 +95,7 @@ def make_target_labels( def update_target_colors( - targets: ElementArrayStim, show_inactive_targets: bool, index: Optional[int] = None + targets: ElementArrayStim, show_inactive_targets: bool, index: int | None = None ) -> None: inactive_rgb = 0.0 if show_inactive_targets: @@ -116,9 +110,9 @@ def update_target_colors( def update_target_label_colors( - target_labels: List[TextBox2], + target_labels: list[TextBox2], show_inactive_targets: bool, - index: Optional[int] = None, + index: int | None = None, ) -> None: inactive_rgb = 0.0 if show_inactive_targets: @@ -136,8 +130,8 @@ class MotorTaskCancelledByUser(Exception): def draw_and_flip( win: Window, - drawables: List[BaseVisualStim], - kb: Optional[Keyboard], + drawables: list[BaseVisualStim], + kb: Keyboard | None, kb_stop_key: str = "escape", ) -> None: for drawable in drawables: @@ -181,8 +175,8 @@ def _make_stats_drawables( stats_df: pd.DataFrame, win: Window, all_trials_for_this_condition: bool, -) -> List[BaseVisualStim]: - drawables: List[BaseVisualStim] = [] +) -> list[BaseVisualStim]: + drawables: list[BaseVisualStim] = [] if stats_df.shape[0] == 0: # no results to display return drawables @@ -340,15 +334,15 @@ def display_results( display_time_seconds: float, enter_to_skip_delay: bool, show_delay_countdown: bool, - trial_handler: Optional[TrialHandlerExt], + trial_handler: TrialHandlerExt | None, display_options: DisplayOptions, i_trial: int, all_trials_for_this_condition: bool, - win: Optional[Window] = None, - mouse: Optional[Mouse] = None, - mouse_pos: Optional[Tuple[float, float]] = None, + win: Window | None = None, + mouse: Mouse | None = None, + mouse_pos: tuple[float, float] | None = None, return_screenshot: bool = False, -) -> Optional[Image]: +) -> Image | None: close_window_when_done = False if win is None: win = _make_window() @@ -474,13 +468,13 @@ def display_drawables( display_time_seconds: float, enter_to_skip_delay: bool, show_delay_countdown: bool, - drawables: List[BaseVisualStim], + drawables: list[BaseVisualStim], win: Window, close_window_when_done: bool, - mouse: Optional[Mouse] = None, - mouse_pos: Optional[Tuple[float, float]] = None, + mouse: Mouse | None = None, + mouse_pos: tuple[float, float] | None = None, return_screenshot: bool = False, -) -> Optional[Image]: +) -> Image | None: screenshot = None if drawables is None: drawables = [] @@ -530,7 +524,7 @@ def splash_screen( enter_to_skip_delay: bool, show_delay_countdown: bool, metadata: Metadata, - win: Optional[Window] = None, + win: Window | None = None, ) -> None: close_window_when_done = False if win is None: diff --git a/src/vstt/vtypes.py b/src/vstt/vtypes.py index beac3ebe..d39c9075 100644 --- a/src/vstt/vtypes.py +++ b/src/vstt/vtypes.py @@ -1,8 +1,6 @@ from __future__ import annotations import sys -from typing import List -from typing import Union if sys.version_info >= (3, 8): from typing import TypedDict @@ -14,7 +12,7 @@ class Trial(TypedDict): weight: int condition_timeout: float num_targets: int - target_order: Union[str, List] + target_order: str | list target_indices: str add_central_target: bool hide_target_when_reached: bool diff --git a/tests/conftest.py b/tests/conftest.py index 61ff7202..ce018ebe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,6 @@ import copy import os import sys -from typing import Tuple import numpy as np import pyautogui @@ -58,7 +57,7 @@ def noise(scale: float = 0.02) -> float: def make_mouse_positions( - pos: Tuple[float, float], time_points: np.ndarray + pos: tuple[float, float], time_points: np.ndarray ) -> np.ndarray: return np.array([(pos[0] * t + noise(), pos[1] * t + noise()) for t in time_points]) diff --git a/tests/helpers/gui_test_utils.py b/tests/helpers/gui_test_utils.py index 4bd8d13c..95daaec5 100644 --- a/tests/helpers/gui_test_utils.py +++ b/tests/helpers/gui_test_utils.py @@ -5,7 +5,6 @@ import threading from time import sleep from typing import Callable -from typing import Tuple from ascii_magic import AsciiArt from psychopy.visual.window import Window @@ -34,7 +33,7 @@ def press_enter() -> None: # return true if pixel or any neighbours is of the desired color -def pixel_color(pixel: Tuple[float, float], color: Tuple[int, int, int]) -> bool: +def pixel_color(pixel: tuple[float, float], color: tuple[int, int, int]) -> bool: img = pyautogui.screenshot().load() for dx in [0, 1, -1]: for dy in [0, 1, -1]: @@ -45,7 +44,7 @@ def pixel_color(pixel: Tuple[float, float], color: Tuple[int, int, int]) -> bool # convert a position in units of screen height to a pair of pixels -def pos_to_pixels(pos: Tuple[float, float]) -> Tuple[float, float]: +def pos_to_pixels(pos: tuple[float, float]) -> tuple[float, float]: # pos is in units of screen height, with 0,0 in centre of screen width, height = pyautogui.size() # return equivalent position in pixels with 0,0 in top left of screen @@ -113,7 +112,7 @@ def get_screenshot_when_ready( # call target with args, get screenshot when ready, press Enter to close screen def call_target_and_get_screenshot( - target: Callable, args: Tuple, win: Window + target: Callable, args: tuple, win: Window ) -> np.ndarray: screenshot_queue: queue.Queue = queue.Queue() screenshot_before = pyautogui.screenshot() @@ -131,10 +130,10 @@ def call_target_and_get_screenshot( # return fraction of pixels in image of the supplied color -def pixel_color_fraction(img: np.ndarray, color: Tuple[int, int, int]) -> float: +def pixel_color_fraction(img: np.ndarray, color: tuple[int, int, int]) -> float: return np.count_nonzero((img == np.array(color)).all(axis=2)) / (img.size / 3) # return fraction of pixels in current screen of the supplied color -def screenshot_pixel_color_fraction(color: Tuple[int, int, int]) -> float: +def screenshot_pixel_color_fraction(color: tuple[int, int, int]) -> float: return pixel_color_fraction(np.asarray(pyautogui.screenshot()), color) diff --git a/tests/helpers/qt_test_utils.py b/tests/helpers/qt_test_utils.py index 634be837..714ef5d5 100644 --- a/tests/helpers/qt_test_utils.py +++ b/tests/helpers/qt_test_utils.py @@ -1,13 +1,9 @@ from __future__ import annotations from typing import Any -from typing import List -from typing import Optional import qtpy -from qtpy import QtCore -from qtpy import QtGui -from qtpy import QtWidgets +from qtpy import QtCore, QtGui, QtWidgets from qtpy.QtCore import Qt from qtpy.QtTest import QTest @@ -60,15 +56,15 @@ def clear(self) -> None: class ModalWidgetTimer: def __init__( self, - keys: List[str], - start_after: Optional[ModalWidgetTimer] = None, + keys: list[str], + start_after: ModalWidgetTimer | None = None, ) -> None: self._keys = keys if start_after is not None: start_after._other_mwt_to_start = self - self._other_mwt_to_start: Optional[ModalWidgetTimer] = None + self._other_mwt_to_start: ModalWidgetTimer | None = None self._timer = QtCore.QTimer() - self._widget_to_ignore: Optional[QtWidgets.QWidget] = None + self._widget_to_ignore: QtWidgets.QWidget | None = None self._timeout = 30000 self._timer.timeout.connect(self._send_keys) self._widget_type: str = "" @@ -112,10 +108,7 @@ def _send_keys(self) -> None: for key in self._keys: key_seq = QtGui.QKeySequence(key) str_key = key_seq.toString() - if qtpy.QT6: - qt_key = key_seq[0].key() - else: - qt_key = Qt.Key(key_seq[0]) + qt_key = key_seq[0].key() if qtpy.QT6 else Qt.Key(key_seq[0]) print( f"ModalWidgetTimer :: - sending '{str_key} [{qt_key}]' to {widget}", flush=True, diff --git a/tests/test_display_widget.py b/tests/test_display_widget.py index b41bfe3f..3578c41c 100644 --- a/tests/test_display_widget.py +++ b/tests/test_display_widget.py @@ -39,4 +39,4 @@ def test_display_options_widget(window: Window) -> None: assert signal_received # check that all values have the correct type for value in widget.experiment.display_options.values(): - assert type(value) is bool + assert isinstance(value, bool) diff --git a/tests/test_gui.py b/tests/test_gui.py index dad88371..7d761715 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -2,13 +2,11 @@ import pathlib from typing import Any -from typing import Tuple import qt_test_utils as qtu import vstt from pytest import MonkeyPatch -from qtpy.QtWidgets import QFileDialog -from qtpy.QtWidgets import QInputDialog +from qtpy.QtWidgets import QFileDialog, QInputDialog from vstt.experiment import Experiment from vstt.gui import Gui @@ -160,16 +158,24 @@ def test_gui_import_export( assert not export_filepath.is_file() # monkey patch QFileDialog.getSaveFileName to just return desired filename - def mock_getSaveFileName(*args: Any, **kwargs: Any) -> Tuple[str, str]: - return str(export_filepath), filter + def mock_get_save_file_name( + *args: Any, + mock_return_value: tuple[str, str] = (str(export_filepath), filter), + **kwargs: Any, + ) -> tuple[str, str]: + return mock_return_value - monkeypatch.setattr(QFileDialog, "getSaveFileName", mock_getSaveFileName) + monkeypatch.setattr(QFileDialog, "getSaveFileName", mock_get_save_file_name) # monkey patch QInputDialog.getItem to just return desired option - def mock_getItem(*args: Any, **kwargs: Any) -> Tuple[str, bool]: - return data_option, True - - monkeypatch.setattr(QInputDialog, "getItem", mock_getItem) + def mock_get_item( + *args: Any, + mock_return_value: tuple[str, bool] = (data_option, True), + **kwargs: Any, + ) -> tuple[str, bool]: + return mock_return_value + + monkeypatch.setattr(QInputDialog, "getItem", mock_get_item) gui = Gui(filename=str(filepath)) assert gui.experiment.metadata == experiment_with_results.metadata diff --git a/tests/test_meta_widget.py b/tests/test_meta_widget.py index 49adf0d4..187da20d 100644 --- a/tests/test_meta_widget.py +++ b/tests/test_meta_widget.py @@ -29,7 +29,7 @@ def test_metadata_widget(window: Window) -> None: experiment = Experiment() empty_metadata = vstt.meta.default_metadata() for key, value in empty_metadata.items(): - if type(value) is str: + if isinstance(value, str): empty_metadata[key] = "" # type: ignore empty_metadata["show_delay_countdown"] = False experiment.metadata = empty_metadata @@ -53,10 +53,10 @@ def test_metadata_widget(window: Window) -> None: assert widget.experiment.has_unsaved_changes is False assert widget.experiment.metadata == empty_metadata assert not signal_received - for key in widget._str_widgets.keys(): + for key in widget._str_widgets: line_edit = widget._str_widgets[key] assert line_edit is not None - assert type(line_edit) is QtWidgets.QLineEdit + assert isinstance(line_edit, QtWidgets.QLineEdit) assert widget.experiment.metadata[key] == "" # type: ignore signal_received.clear() qtu.press_keys(line_edit, key) @@ -64,7 +64,7 @@ def test_metadata_widget(window: Window) -> None: assert widget.experiment.has_unsaved_changes is True assert signal_received for key, value in widget.experiment.metadata.items(): - if type(value) is str: + if isinstance(value, str): assert value == key # assign another experiment to widget & update fields experiment2 = Experiment() @@ -73,7 +73,7 @@ def test_metadata_widget(window: Window) -> None: assert widget.experiment is experiment2 assert widget.experiment.has_unsaved_changes is False for key, value in vstt.meta.default_metadata().items(): - if type(value) is str: + if isinstance(value, str): line_edit = widget._str_widgets[key] signal_received.clear() assert line_edit is not None @@ -85,9 +85,9 @@ def test_metadata_widget(window: Window) -> None: assert signal_received # experiment2 has been updated for key, value in experiment2.metadata.items(): - if type(value) is str: + if isinstance(value, str): assert value == vstt.meta.default_metadata()[key] + "2" # type: ignore # previous experiment was not modified for key, value in experiment.metadata.items(): - if type(value) is str: + if isinstance(value, str): assert value == key diff --git a/tests/test_results_widget.py b/tests/test_results_widget.py index ec3dcb62..8dfb5542 100644 --- a/tests/test_results_widget.py +++ b/tests/test_results_widget.py @@ -2,7 +2,6 @@ import pathlib from typing import Any -from typing import Tuple import gui_test_utils as gtu import numpy as np @@ -49,10 +48,10 @@ def test_results_widget_experiment_with_results( png_path = tmp_path / "tmp.png" # monkey patch QFileDialog.getSaveFileName to just return desired filename - def mock_getSaveFileName(*args: Any, **kwargs: Any) -> Tuple[str, str]: + def mock_get_save_file_name(*args: Any, **kwargs: Any) -> tuple[str, str]: return str(png_path), "" - monkeypatch.setattr(QFileDialog, "getSaveFileName", mock_getSaveFileName) + monkeypatch.setattr(QFileDialog, "getSaveFileName", mock_get_save_file_name) widget = ResultsWidget(parent=None, win=window) # assign experiment with results diff --git a/tests/test_stats.py b/tests/test_stats.py index 556aec7b..7802d5a8 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -11,7 +11,7 @@ def test_data_df(experiment_with_results: Experiment) -> None: # results from 3 reps of 8-target trial, 1 rep of 4-target trial with automove back to center df = vstt.stats._data_df(experiment_with_results.trial_handler_with_results) assert len(df.columns) == len(vstt.stats._get_trial_data_columns()) - for index, row in df.iterrows(): + for _index, row in df.iterrows(): assert row["to_target_mouse_positions"].shape[1] == 2 assert ( row["to_center_mouse_positions"].shape[0] == 0 @@ -41,8 +41,8 @@ def test_data_df(experiment_with_results: Experiment) -> None: def test_stats_df(experiment_with_results: Experiment) -> None: df = vstt.stats.stats_dataframe(experiment_with_results.trial_handler_with_results) assert np.all(np.isnan(df.loc[df.condition_index == 1]["to_center_time"])) - for destination, stat_label_units in vstt.stats.list_dest_stat_label_units(): - for stat, label, unit in stat_label_units: + for _destination, stat_label_units in vstt.stats.list_dest_stat_label_units(): + for stat, _label, _unit in stat_label_units: assert stat in df.columns diff --git a/tests/test_task.py b/tests/test_task.py index 2c58f6af..e3d0bcf2 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -3,9 +3,6 @@ import threading from copy import deepcopy from time import sleep -from typing import Dict -from typing import List -from typing import Tuple import gui_test_utils as gtu import numpy as np @@ -19,8 +16,8 @@ def move_mouse_to_target( - target_pixel: Tuple[float, float], - target_color: Tuple[int, int, int] = (255, 0, 0), + target_pixel: tuple[float, float], + target_color: tuple[int, int, int] = (255, 0, 0), movement_time: float = 0.1, ) -> None: # wait until target is activated @@ -39,17 +36,17 @@ def move_mouse_to_target( def do_task( - experiment: Experiment, target_pixels: List[List[Tuple[float, float]]] + experiment: Experiment, target_pixels: list[list[tuple[float, float]]] ) -> None: gtu.ascii_screenshot() for target_pixels_block, trial in zip(target_pixels, experiment.trial_list): - for rep in range(trial["weight"]): + for _rep in range(trial["weight"]): for target_pixel in target_pixels_block: move_mouse_to_target(target_pixel) def launch_do_task( - experiment: Experiment, target_pixels: List[List[Tuple[float, float]]] + experiment: Experiment, target_pixels: list[list[tuple[float, float]]] ) -> threading.Thread: thread = threading.Thread( target=do_task, @@ -86,7 +83,7 @@ def test_task_no_trials(window: Window) -> None: ids=["automove_to_center", "no_central_target"], ) def test_task( - experiment_no_results: Experiment, window: Window, trial_settings: Dict + experiment_no_results: Experiment, window: Window, trial_settings: dict ) -> None: target_pixels = [] experiment_no_results.has_unsaved_changes = False diff --git a/tests/test_update.py b/tests/test_update.py index f182a07e..de8842b2 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -4,8 +4,7 @@ import vstt from pytest import MonkeyPatch -from vstt.update import check_for_new_version -from vstt.update import do_pip_upgrade +from vstt.update import check_for_new_version, do_pip_upgrade def test_new_version_available(monkeypatch: MonkeyPatch) -> None: diff --git a/tests/test_vis.py b/tests/test_vis.py index a4cdc61d..447f4376 100644 --- a/tests/test_vis.py +++ b/tests/test_vis.py @@ -1,9 +1,5 @@ from __future__ import annotations -from typing import Dict -from typing import List -from typing import Tuple - import gui_test_utils as gtu import numpy as np import pytest @@ -56,7 +52,7 @@ def test_make_cursor(window: Window) -> None: ], ) def test_make_targets( - window: Window, args: Dict, xys: List[Tuple[float, float]], add_central_target: bool + window: Window, args: dict, xys: list[tuple[float, float]], add_central_target: bool ) -> None: args["add_central_target"] = add_central_target targets = vstt.vis.make_targets(window, **args) @@ -142,7 +138,7 @@ def test_update_target_colors( ], ) def test_make_target_labels( - window: Window, args: Dict, xys: List[Tuple[float, float]] + window: Window, args: dict, xys: list[tuple[float, float]] ) -> None: # more labels than targets: label each target and ignore any extra labels args["labels_string"] = "a b c d e f"