From 95b25cb7b602cd4e7a8eb2a3d1faf98c8eb032c9 Mon Sep 17 00:00:00 2001 From: Talley Lambert Date: Fri, 8 Nov 2024 08:22:07 -0500 Subject: [PATCH] drop py3.8 --- .github/workflows/test_and_deploy.yml | 2 +- .pre-commit-config.yaml | 3 +- docs/examples/demo_widgets/file_dialog.py | 1 - docs/examples/demo_widgets/files_dialog.py | 2 +- docs/examples/demo_widgets/range_slider.py | 4 +- docs/examples/matplotlib/waveform.py | 2 +- docs/examples/napari/napari_img_math.py | 2 + .../examples/napari/napari_parameter_sweep.py | 13 ++- .../examples/notebooks/magicgui_jupyter.ipynb | 3 +- docs/examples/notebooks/magicgui_jupyter.py | 4 +- docs/index.md | 4 - docs/scripts/_hooks.py | 4 +- pyproject.toml | 5 +- src/magicgui/_type_resolution.py | 35 +++----- src/magicgui/_util.py | 11 ++- src/magicgui/application.py | 3 +- src/magicgui/backends/_ipynb/widgets.py | 4 +- src/magicgui/backends/_qtpy/widgets.py | 4 +- src/magicgui/schema/_guiclass.py | 8 +- src/magicgui/schema/_ui_field.py | 12 +-- src/magicgui/signature.py | 5 +- src/magicgui/tqdm.py | 5 +- src/magicgui/type_map/_type_map.py | 17 ++-- src/magicgui/types.py | 7 +- src/magicgui/widgets/_concrete.py | 18 ++-- src/magicgui/widgets/_dialogs.py | 12 +-- src/magicgui/widgets/_function_gui.py | 2 +- src/magicgui/widgets/_image/_mpl_image.py | 3 +- src/magicgui/widgets/_table.py | 14 ++-- src/magicgui/widgets/bases/__init__.py | 26 +++--- .../widgets/bases/_container_widget.py | 5 +- src/magicgui/widgets/bases/_ranged_widget.py | 5 +- src/magicgui/widgets/bases/_slider_widget.py | 4 +- src/magicgui/widgets/bases/_toolbar.py | 4 +- src/magicgui/widgets/bases/_widget.py | 3 +- src/magicgui/widgets/protocols.py | 4 +- tests/test_backends.py | 1 - tests/test_docs.py | 5 +- tests/test_signature.py | 3 +- tests/test_types.py | 20 ++--- tests/test_ui_field.py | 4 +- tests/test_util.py | 82 +++++++++---------- tests/test_widgets.py | 76 ++++++++++++----- 43 files changed, 230 insertions(+), 221 deletions(-) diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml index fb9ca1180..488611214 100644 --- a/.github/workflows/test_and_deploy.yml +++ b/.github/workflows/test_and_deploy.yml @@ -73,7 +73,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] test-pydantic1: name: Test pydantic 1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eabbee82f..aec7ea353 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,8 @@ repos: rev: v0.7.3 hooks: - id: ruff - args: ["--fix"] + args: ["--fix", "--unsafe-fixes"] + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.13.0 diff --git a/docs/examples/demo_widgets/file_dialog.py b/docs/examples/demo_widgets/file_dialog.py index a98f9d407..821713cb5 100644 --- a/docs/examples/demo_widgets/file_dialog.py +++ b/docs/examples/demo_widgets/file_dialog.py @@ -15,6 +15,5 @@ def filepicker(filename=Path("~")) -> Path: return filename - filepicker.filename.changed.connect(print) filepicker.show(run=True) diff --git a/docs/examples/demo_widgets/files_dialog.py b/docs/examples/demo_widgets/files_dialog.py index 68ca6ffcf..1ffd730f1 100644 --- a/docs/examples/demo_widgets/files_dialog.py +++ b/docs/examples/demo_widgets/files_dialog.py @@ -3,8 +3,8 @@ A dialog to select multiple files. """ +from collections.abc import Sequence from pathlib import Path -from typing import Sequence from magicgui import magicgui diff --git a/docs/examples/demo_widgets/range_slider.py b/docs/examples/demo_widgets/range_slider.py index cf3c8a464..8fef6f4c1 100644 --- a/docs/examples/demo_widgets/range_slider.py +++ b/docs/examples/demo_widgets/range_slider.py @@ -3,13 +3,11 @@ A double ended range slider widget. """ -from typing import Tuple - from magicgui import magicgui @magicgui(auto_call=True, range_value={"widget_type": "RangeSlider", "max": 500}) -def func(range_value: Tuple[int, int] = (20, 380)): +def func(range_value: tuple[int, int] = (20, 380)): """Double ended range slider.""" print(range_value) diff --git a/docs/examples/matplotlib/waveform.py b/docs/examples/matplotlib/waveform.py index 4ea94c0a7..4385580cc 100644 --- a/docs/examples/matplotlib/waveform.py +++ b/docs/examples/matplotlib/waveform.py @@ -6,12 +6,12 @@ from dataclasses import dataclass, field from enum import Enum from functools import partial +from typing import Annotated import matplotlib.pyplot as plt import numpy as np from matplotlib.backends.backend_qt5agg import FigureCanvas from scipy import signal -from typing_extensions import Annotated from magicgui import magicgui, register_type, widgets diff --git a/docs/examples/napari/napari_img_math.py b/docs/examples/napari/napari_img_math.py index c32993c0d..c86af1436 100644 --- a/docs/examples/napari/napari_img_math.py +++ b/docs/examples/napari/napari_img_math.py @@ -58,6 +58,7 @@ def image_arithmetic( # Add, subtracts, multiplies, or divides to image layers. return operation.value(layerA, layerB) + # create a viewer and add a couple image layers viewer = napari.Viewer() viewer.add_image(numpy.random.rand(20, 20), name="Layer 1") @@ -107,6 +108,7 @@ def image_arithmetic(array1, operation, array2): ```python from napari.types import ImageData + def image_arithmetic( layerA: ImageData, operation: Operation, layerB: ImageData ) -> ImageData: diff --git a/docs/examples/napari/napari_parameter_sweep.py b/docs/examples/napari/napari_parameter_sweep.py index acca30f39..d55991b97 100644 --- a/docs/examples/napari/napari_parameter_sweep.py +++ b/docs/examples/napari/napari_parameter_sweep.py @@ -60,6 +60,7 @@ def gaussian_blur(layer: ImageData, sigma: float = 1.0, mode="nearest") -> Image if layer is not None: return skimage.filters.gaussian(layer, sigma=sigma, mode=mode) + # create a viewer and add some images viewer = napari.Viewer() viewer.add_image(skimage.data.astronaut().mean(-1), name="astronaut") @@ -88,9 +89,7 @@ def gaussian_blur(layer: ImageData, sigma: float = 1.0, mode="nearest") -> Image the blur radius, and a `mode` that determines how edges are handled. ```python -def gaussian_blur( - layer: Image, sigma: float = 1, mode="nearest" -) -> Image: +def gaussian_blur(layer: Image, sigma: float = 1, mode="nearest") -> Image: return filters.gaussian(layer.data, sigma=sigma, mode=mode) ``` @@ -124,9 +123,7 @@ def gaussian_blur( sigma={"widget_type": "FloatSlider", "max": 6}, mode={"choices": ["reflect", "constant", "nearest", "mirror", "wrap"]}, ) -def gaussian_blur( - layer: ImageData, sigma: float = 1.0, mode="nearest" -) -> ImageData: +def gaussian_blur(layer: ImageData, sigma: float = 1.0, mode="nearest") -> ImageData: # Apply a gaussian blur to ``layer``. if layer is not None: return skimage.filters.gaussian(layer, sigma=sigma, mode=mode) @@ -152,8 +149,8 @@ def gaussian_blur( the result of our decorated function anytime it is called. ```python -def do_something_with_result(result): - ... +def do_something_with_result(result): ... + gaussian_blur.called.connect(do_something_with_result) ``` diff --git a/docs/examples/notebooks/magicgui_jupyter.ipynb b/docs/examples/notebooks/magicgui_jupyter.ipynb index 7ab7fb470..b5aebc7c3 100644 --- a/docs/examples/notebooks/magicgui_jupyter.ipynb +++ b/docs/examples/notebooks/magicgui_jupyter.ipynb @@ -28,6 +28,7 @@ "\n", "use_app(\"ipynb\")\n", "\n", + "\n", "class Medium(Enum):\n", " \"\"\"Enum for various media and their refractive indices.\"\"\"\n", "\n", @@ -38,7 +39,7 @@ "\n", "\n", "@magicgui(\n", - " call_button=\"calculate\", result_widget=True, layout='vertical', auto_call=True\n", + " call_button=\"calculate\", result_widget=True, layout=\"vertical\", auto_call=True\n", ")\n", "def snells_law(aoi=1.0, n1=Medium.Glass, n2=Medium.Water, degrees=True):\n", " \"\"\"Calculate the angle of refraction given two media and an AOI.\"\"\"\n", diff --git a/docs/examples/notebooks/magicgui_jupyter.py b/docs/examples/notebooks/magicgui_jupyter.py index a108f232b..66a7d707f 100644 --- a/docs/examples/notebooks/magicgui_jupyter.py +++ b/docs/examples/notebooks/magicgui_jupyter.py @@ -11,8 +11,10 @@ from enum import Enum from magicgui import magicgui, use_app + use_app("ipynb") + class Medium(Enum): # Various media and their refractive indices. Glass = 1.520 @@ -22,7 +24,7 @@ class Medium(Enum): @magicgui( - call_button="calculate", result_widget=True, layout='vertical', auto_call=True + call_button="calculate", result_widget=True, layout="vertical", auto_call=True ) def snells_law(aoi=1.0, n1=Medium.Glass, n2=Medium.Water, degrees=True): # Calculate the angle of refraction given two media and an angle of incidence. diff --git a/docs/index.md b/docs/index.md index b7a7137a6..43b914831 100644 --- a/docs/index.md +++ b/docs/index.md @@ -97,10 +97,6 @@ autogenerate a compound Widget based on the parameters of a function: ```python from typing import Annotated, Literal -# for Python <= 3.8 -# from typing_extensions import Annotated -# for Python <= 3.7 -# from typing_extensions import Annotated, Literal from magicgui import magicgui diff --git a/docs/scripts/_hooks.py b/docs/scripts/_hooks.py index b3d0eaa09..0dfe5b23e 100644 --- a/docs/scripts/_hooks.py +++ b/docs/scripts/_hooks.py @@ -12,7 +12,7 @@ from importlib.machinery import ModuleSpec from itertools import count from textwrap import dedent -from typing import TYPE_CHECKING, Any, Mapping +from typing import TYPE_CHECKING, Any from griffe import Alias from mkdocstrings_handlers.python.handler import PythonHandler @@ -22,6 +22,8 @@ warnings.simplefilter("ignore", DeprecationWarning) if TYPE_CHECKING: + from collections.abc import Mapping + from mkdocs.structure.pages import Page diff --git a/pyproject.toml b/pyproject.toml index f8d97928f..c30a4f6ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ name = "magicgui" description = " build GUIs from python types" keywords = ["gui", "widgets", "type annotations"] readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = { text = "MIT" } authors = [{ email = "talley.lambert@gmail.com", name = "Talley Lambert" }] classifiers = [ @@ -47,7 +47,6 @@ dependencies = [ pyqt5 = ["pyqt5>=5.12.0"] pyqt6 = ["pyqt6"] pyside2 = [ - "pyside2>=5.14 ; python_version=='3.8'", "pyside2>=5.15 ; python_version>='3.9'", ] pyside6 = ["pyside6"] @@ -141,7 +140,7 @@ matrix.backend.features = [ # https://docs.astral.sh/ruff [tool.ruff] line-length = 88 -target-version = "py38" +target-version = "py39" src = ["src", "tests"] [tool.ruff.lint] diff --git a/src/magicgui/_type_resolution.py b/src/magicgui/_type_resolution.py index a7b382b2f..e7df55414 100644 --- a/src/magicgui/_type_resolution.py +++ b/src/magicgui/_type_resolution.py @@ -1,39 +1,35 @@ -import sys import types import typing from copy import copy from functools import lru_cache, partial from importlib import import_module -from typing import Any, Callable, Dict, Optional, Tuple, Type, Union - -# required for python 3.8 to strip annotations -from typing_extensions import get_type_hints +from typing import Any, Callable, Optional, Union, get_type_hints try: from toolz import curry - PARTIAL_TYPES: Tuple[Type, ...] = (partial, curry) + PARTIAL_TYPES: tuple[type, ...] = (partial, curry) except ImportError: # pragma: no cover PARTIAL_TYPES = (partial,) @lru_cache(maxsize=1) -def _typing_names() -> Dict[str, Any]: +def _typing_names() -> dict[str, Any]: return {**typing.__dict__, **types.__dict__} def _unwrap_partial(func: Any) -> Any: while isinstance(func, PARTIAL_TYPES): - func = func.func + func = func.func # type: ignore return func def resolve_types( obj: Union[Callable, types.ModuleType, types.MethodType, type], - globalns: Optional[Dict[str, Any]] = None, - localns: Optional[Dict[str, Any]] = None, + globalns: Optional[dict[str, Any]] = None, + localns: Optional[dict[str, Any]] = None, do_imports: bool = False, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Resolve type hints from an object. Parameters @@ -56,7 +52,7 @@ def resolve_types( obj = _unwrap_partial(obj) try: - hints = get_type_hints(obj, globalns=globalns, localns=localns) # type: ignore + hints = get_type_hints(obj, globalns=globalns, localns=localns) except NameError as e: if do_imports: # try to import the top level name and try again @@ -69,20 +65,9 @@ def resolve_types( return resolve_types(obj, globalns, ns, do_imports=do_imports) raise e - # before version 3.9, typing.get_type_hints did not resolve hint.__args__ - if sys.version_info < (3, 9): - _iterdict(hints, _resolve_forwards) return hints -def _iterdict(d: Dict[str, Any], func: Callable) -> None: - for k, v in d.items(): - if isinstance(v, dict): - _iterdict(v, func) - else: - d[k] = func(v) - - def _resolve_forwards(v: Any) -> Any: if isinstance(v, typing.ForwardRef): return _try_cached_resolve(v.__forward_arg__) @@ -94,8 +79,8 @@ def _resolve_forwards(v: Any) -> Any: def resolve_single_type( hint: Any, - globalns: Optional[Dict[str, Any]] = None, - localns: Optional[Dict[str, Any]] = None, + globalns: Optional[dict[str, Any]] = None, + localns: Optional[dict[str, Any]] = None, do_imports: bool = True, ) -> Any: """Resolve a single type hint. diff --git a/src/magicgui/_util.py b/src/magicgui/_util.py index 6f2cd496a..465b70ae8 100644 --- a/src/magicgui/_util.py +++ b/src/magicgui/_util.py @@ -9,7 +9,6 @@ from typing import ( TYPE_CHECKING, Callable, - Iterable, get_args, get_origin, overload, @@ -18,6 +17,7 @@ from docstring_parser import DocstringParam, parse if TYPE_CHECKING: + from collections.abc import Iterable from typing import TypeVar from typing_extensions import ParamSpec @@ -176,9 +176,8 @@ def _safe_isinstance_tuple(obj: object, superclass: object) -> bool: # case 2 return all(safe_issubclass(o, superclass_args[0]) for o in obj_args) # fallback to simple compare - return ( - len(obj_args) == len(superclass_args) and - all(safe_issubclass(o, s) for o, s in zip(obj_args, superclass_args)) + return len(obj_args) == len(superclass_args) and all( + safe_issubclass(o, s) for o, s in zip(obj_args, superclass_args) ) if len(obj_args) == 2 and obj_args[1] is Ellipsis: @@ -196,14 +195,14 @@ def safe_issubclass(obj: object, superclass: object) -> bool: try: if obj_origin is None: if superclass_origin is None: - return issubclass(obj, superclass) # type: ignore + return issubclass(obj, superclass) # type: ignore if not superclass_args: return issubclass(obj, superclass_origin) # type: ignore # if obj is not generic type, but superclass is with # we can't say anything about it return False if obj_origin is not None and superclass_origin is None: - return issubclass(obj_origin, superclass) # type: ignore + return issubclass(obj_origin, superclass) # type: ignore if not issubclass(obj_origin, superclass_origin): # type: ignore return False obj_args = get_args(obj) diff --git a/src/magicgui/application.py b/src/magicgui/application.py index 2792ad824..9f6837767 100644 --- a/src/magicgui/application.py +++ b/src/magicgui/application.py @@ -5,11 +5,12 @@ import signal from contextlib import contextmanager from importlib import import_module -from typing import TYPE_CHECKING, Any, Callable, Iterator, Union +from typing import TYPE_CHECKING, Any, Callable, Union from magicgui.backends import BACKENDS if TYPE_CHECKING: + from collections.abc import Iterator from types import ModuleType from magicgui.widgets.protocols import BaseApplicationBackend diff --git a/src/magicgui/backends/_ipynb/widgets.py b/src/magicgui/backends/_ipynb/widgets.py index bbc9294b4..aa0778325 100644 --- a/src/magicgui/backends/_ipynb/widgets.py +++ b/src/magicgui/backends/_ipynb/widgets.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Iterable, get_type_hints +from typing import TYPE_CHECKING, Any, Callable, get_type_hints try: import ipywidgets @@ -16,6 +16,8 @@ from magicgui.widgets import protocols if TYPE_CHECKING: + from collections.abc import Iterable + from magicgui.widgets.bases import Widget diff --git a/src/magicgui/backends/_qtpy/widgets.py b/src/magicgui/backends/_qtpy/widgets.py index 4d1ff7539..7d2605173 100644 --- a/src/magicgui/backends/_qtpy/widgets.py +++ b/src/magicgui/backends/_qtpy/widgets.py @@ -8,7 +8,7 @@ from contextlib import contextmanager, suppress from functools import partial from itertools import chain -from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, Sequence +from typing import TYPE_CHECKING, Any, Callable import qtpy import superqt @@ -31,6 +31,8 @@ from magicgui.widgets._concrete import _LabeledWidget if TYPE_CHECKING: + from collections.abc import Iterable, Iterator, Sequence + import numpy diff --git a/src/magicgui/schema/_guiclass.py b/src/magicgui/schema/_guiclass.py index fce6a30bb..9f9f24c57 100644 --- a/src/magicgui/schema/_guiclass.py +++ b/src/magicgui/schema/_guiclass.py @@ -12,7 +12,7 @@ import contextlib import warnings from dataclasses import Field, dataclass, field, is_dataclass -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Mapping, TypeVar, overload +from typing import TYPE_CHECKING, Any, Callable, ClassVar, TypeVar, overload from psygnal import SignalGroup, SignalInstance, evented from psygnal import __version__ as psygnal_version @@ -22,6 +22,7 @@ from magicgui.widgets.bases import ContainerWidget, ValueWidget if TYPE_CHECKING: + from collections.abc import Mapping from typing import Protocol from typing_extensions import TypeGuard @@ -130,13 +131,12 @@ def guiclass( >>> @guiclass ... class MyData: ... x: int = 0 - ... y: str = 'hi' + ... y: str = "hi" ... ... @button ... def reset(self): ... self.x = 0 - ... self.y = 'hi' - ... + ... self.y = "hi" >>> data = MyData() >>> data.gui.show() """ diff --git a/src/magicgui/schema/_ui_field.py b/src/magicgui/schema/_ui_field.py index 67f61bb7a..98f85d2e4 100644 --- a/src/magicgui/schema/_ui_field.py +++ b/src/magicgui/schema/_ui_field.py @@ -4,27 +4,27 @@ import sys import warnings from dataclasses import dataclass, field -from functools import lru_cache +from functools import cache from types import FunctionType from typing import ( TYPE_CHECKING, + Annotated, Any, Callable, Generic, - Iterable, - Iterator, Literal, TypeVar, Union, cast, ) -from typing_extensions import Annotated, TypeGuard, get_args, get_origin +from typing_extensions import TypeGuard, get_args, get_origin from magicgui.types import JsonStringFormats, Undefined, _Undefined if TYPE_CHECKING: - from typing import Mapping, Protocol + from collections.abc import Iterable, Iterator, Mapping + from typing import Protocol import attrs import pydantic @@ -758,7 +758,7 @@ def _iter_ui_fields(object: Any) -> Iterator[UiField]: ) # pragma: no cover -@lru_cache(maxsize=None) +@cache def _cached_iter_ui_fields(cls: type) -> tuple[UiField, ...]: return tuple(_iter_ui_fields(cls)) diff --git a/src/magicgui/signature.py b/src/magicgui/signature.py index 7170d25e8..9cec459b9 100644 --- a/src/magicgui/signature.py +++ b/src/magicgui/signature.py @@ -17,10 +17,11 @@ import inspect import typing import warnings +from collections.abc import Sequence from types import MappingProxyType -from typing import TYPE_CHECKING, Any, Callable, Sequence, cast +from typing import TYPE_CHECKING, Annotated, Any, Callable, cast -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import get_args, get_origin from magicgui.types import Undefined diff --git a/src/magicgui/tqdm.py b/src/magicgui/tqdm.py index 231ece857..8cddf1173 100644 --- a/src/magicgui/tqdm.py +++ b/src/magicgui/tqdm.py @@ -4,11 +4,14 @@ import contextlib import inspect -from typing import Any, Iterable, cast +from typing import TYPE_CHECKING, Any, cast from magicgui.application import use_app from magicgui.widgets import FunctionGui, ProgressBar +if TYPE_CHECKING: + from collections.abc import Iterable + try: from tqdm import tqdm as _tqdm_std except ImportError as e: # pragma: no cover diff --git a/src/magicgui/type_map/_type_map.py b/src/magicgui/type_map/_type_map.py index c4882839d..ca838c72b 100644 --- a/src/magicgui/type_map/_type_map.py +++ b/src/magicgui/type_map/_type_map.py @@ -11,26 +11,22 @@ import types import warnings from collections import defaultdict +from collections.abc import Iterator, Sequence from contextlib import contextmanager from enum import EnumMeta from typing import ( + Annotated, Any, Callable, - Dict, ForwardRef, - Iterator, Literal, - Sequence, - Set, - Tuple, - Type, TypeVar, Union, cast, overload, ) -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import get_args, get_origin from magicgui import widgets from magicgui._type_resolution import resolve_single_type @@ -42,9 +38,9 @@ # redefining these here for the sake of sphinx autodoc forward refs -WidgetClass = Union[Type[widgets.Widget], Type[WidgetProtocol]] +WidgetClass = Union[type[widgets.Widget], type[WidgetProtocol]] WidgetRef = Union[str, WidgetClass] -WidgetTuple = Tuple[WidgetRef, Dict[str, Any]] +WidgetTuple = tuple[WidgetRef, dict[str, Any]] class MissingWidget(RuntimeError): @@ -81,6 +77,7 @@ class MissingWidget(RuntimeError): Sequence[pathlib.Path]: {"mode": "rm"} } + def match_type(type_: Any, default: Any | None = None) -> WidgetTuple | None: """Check simple type mappings.""" if type_ in _SIMPLE_ANNOTATIONS: @@ -103,7 +100,7 @@ def match_type(type_: Any, default: Any | None = None) -> WidgetTuple | None: if choices is not None: # it's a Literal type return widgets.ComboBox, {"choices": choices, "nullable": nullable} - if safe_issubclass(origin, Set): + if safe_issubclass(origin, set): for arg in get_args(type_): if get_origin(arg) is Literal: return widgets.Select, {"choices": get_args(arg)} diff --git a/src/magicgui/types.py b/src/magicgui/types.py index e2c42fb9a..9ebfdab9f 100644 --- a/src/magicgui/types.py +++ b/src/magicgui/types.py @@ -2,9 +2,10 @@ from __future__ import annotations +from collections.abc import Iterable from enum import Enum, EnumMeta from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Literal, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Literal, Union from typing_extensions import TypedDict @@ -27,9 +28,9 @@ class ChoicesDict(TypedDict): #: A generic reference to a :attr:`WidgetClass` as a string, or the class itself. WidgetRef = Union[str, WidgetClass] #: A :attr:`WidgetClass` (or a string representation of one) and a dict of kwargs -WidgetTuple = Tuple[WidgetRef, Dict[str, Any]] +WidgetTuple = tuple[WidgetRef, dict[str, Any]] #: An iterable that can be used as a valid argument for widget ``choices`` -ChoicesIterable = Union[Iterable[Tuple[str, Any]], Iterable[Any]] +ChoicesIterable = Union[Iterable[tuple[str, Any]], Iterable[Any]] #: An callback that can be used as a valid argument for widget ``choices``. It takes #: a categorical widget and returns a :attr:`ChoicesIterable`. ChoicesCallback = Callable[["CategoricalWidget[Any]"], ChoicesIterable] diff --git a/src/magicgui/widgets/_concrete.py b/src/magicgui/widgets/_concrete.py index 1dc21ea56..733503033 100644 --- a/src/magicgui/widgets/_concrete.py +++ b/src/magicgui/widgets/_concrete.py @@ -13,17 +13,12 @@ from pathlib import Path from typing import ( TYPE_CHECKING, + Annotated, Any, Callable, ForwardRef, Generic, - Iterable, - Iterator, - List, Literal, - Sequence, - Tuple, - Type, TypeVar, Union, cast, @@ -31,7 +26,7 @@ ) from weakref import ref -from typing_extensions import Annotated, get_args, get_origin +from typing_extensions import get_args, get_origin from magicgui._type_resolution import resolve_single_type from magicgui._util import merge_super_sigs, safe_issubclass @@ -55,6 +50,8 @@ from magicgui.widgets.bases._mixins import _OrientationMixin, _ReadOnlyMixin if TYPE_CHECKING: + from collections.abc import Iterable, Iterator, Sequence + from typing_extensions import Unpack from magicgui.widgets import protocols @@ -63,7 +60,7 @@ WidgetVar = TypeVar("WidgetVar", bound=Widget) -WidgetTypeVar = TypeVar("WidgetTypeVar", bound=Type[Widget]) +WidgetTypeVar = TypeVar("WidgetTypeVar", bound=type[Widget]) _V = TypeVar("_V") @@ -620,6 +617,7 @@ def value(self, value: Any) -> None: """Set value of the child widget.""" self.value_widget.value = value + @merge_super_sigs class ListEdit(Container[ValueWidget[_V]]): """A widget to represent a list of values. @@ -712,7 +710,7 @@ def annotation(self, value: Any) -> None: arg = args[0] if len(args) > 0 else None args_resolved = get_args(value_resolved) if len(args_resolved) > 0: - value = List[args_resolved[0]] # type: ignore + value = list[args_resolved[0]] # type: ignore else: value = list @@ -946,7 +944,7 @@ def annotation(self, value: Any) -> None: ) args = get_args(value) args_resolved = get_args(value_resolved) - value = Tuple[args_resolved] + value = tuple[args_resolved] # type: ignore [valid-type] self._annotation = value self._args_types = args diff --git a/src/magicgui/widgets/_dialogs.py b/src/magicgui/widgets/_dialogs.py index a7d71d209..b0cb9867d 100644 --- a/src/magicgui/widgets/_dialogs.py +++ b/src/magicgui/widgets/_dialogs.py @@ -1,12 +1,15 @@ from __future__ import annotations -from typing import Any, Callable, Hashable, Iterable, Mapping +from typing import TYPE_CHECKING, Any, Callable from magicgui.types import FileDialogMode from ._concrete import Dialog from .bases import create_widget +if TYPE_CHECKING: + from collections.abc import Hashable, Iterable, Mapping + def show_file_dialog( mode: str | FileDialogMode = FileDialogMode.EXISTING_FILE, @@ -96,13 +99,10 @@ def request_values( >>> request_values( ... age=dict(value=40), ... name=dict(annotation=str, label="Enter your name:"), - ... title="Hi! Who are you?" + ... title="Hi! Who are you?", ... ) - >>> request_values( - ... values={'age': int, 'name': str}, - ... title="Hi! Who are you?" - ... ) + >>> request_values(values={"age": int, "name": str}, title="Hi! Who are you?") """ widgets = [] if title: diff --git a/src/magicgui/widgets/_function_gui.py b/src/magicgui/widgets/_function_gui.py index d94f4a041..83a9ad672 100644 --- a/src/magicgui/widgets/_function_gui.py +++ b/src/magicgui/widgets/_function_gui.py @@ -15,7 +15,6 @@ Any, Callable, Generic, - Iterator, NoReturn, TypeVar, cast, @@ -29,6 +28,7 @@ from magicgui.widgets.bases import ValueWidget if TYPE_CHECKING: + from collections.abc import Iterator from pathlib import Path from typing_extensions import ParamSpec diff --git a/src/magicgui/widgets/_image/_mpl_image.py b/src/magicgui/widgets/_image/_mpl_image.py index abcc66e9a..82104b868 100644 --- a/src/magicgui/widgets/_image/_mpl_image.py +++ b/src/magicgui/widgets/_image/_mpl_image.py @@ -52,8 +52,9 @@ """ import logging +from collections.abc import Collection from functools import lru_cache -from typing import TYPE_CHECKING, Collection, Optional, Union +from typing import TYPE_CHECKING, Optional, Union try: import numpy as np diff --git a/src/magicgui/widgets/_table.py b/src/magicgui/widgets/_table.py index 71d7f95f7..233c27c2a 100644 --- a/src/magicgui/widgets/_table.py +++ b/src/magicgui/widgets/_table.py @@ -2,20 +2,22 @@ import operator import sys -from itertools import zip_longest -from typing import ( - TYPE_CHECKING, - Any, +from collections.abc import ( Collection, - Generic, ItemsView, Iterable, Iterator, KeysView, - Literal, Mapping, MutableMapping, Sequence, +) +from itertools import zip_longest +from typing import ( + TYPE_CHECKING, + Any, + Generic, + Literal, TypeVar, Union, cast, diff --git a/src/magicgui/widgets/bases/__init__.py b/src/magicgui/widgets/bases/__init__.py index 506eda56a..6d717c1d1 100644 --- a/src/magicgui/widgets/bases/__init__.py +++ b/src/magicgui/widgets/bases/__init__.py @@ -10,22 +10,18 @@ .. code-block:: python class Widget: - def __init__( - self, - - # widget_type is a class, likely from the `backends` module - # that implements one of the `WidgetProtocols` defined in _protocols. - widget_type: Type[protocols.WidgetProtocol], - - # backend_kwargs is a key-value map of arguments that will be provided - # to the concrete (backend) implementation of the WidgetProtocol - backend_kwargs: dict = dict(), - - # additional kwargs will be provided to the magicgui.Widget itself - # things like, `name`, `value`, etc... - **kwargs - ): + self, + # widget_type is a class, likely from the `backends` module + # that implements one of the `WidgetProtocols` defined in _protocols. + widget_type: Type[protocols.WidgetProtocol], + # backend_kwargs is a key-value map of arguments that will be provided + # to the concrete (backend) implementation of the WidgetProtocol + backend_kwargs: dict = dict(), + # additional kwargs will be provided to the magicgui.Widget itself + # things like, `name`, `value`, etc... + **kwargs, + ): # instantiation of the backend widget. self._widget = widget_type(**backend_kwargs) diff --git a/src/magicgui/widgets/bases/_container_widget.py b/src/magicgui/widgets/bases/_container_widget.py index 192a6b65f..7f8d9db2a 100644 --- a/src/magicgui/widgets/bases/_container_widget.py +++ b/src/magicgui/widgets/bases/_container_widget.py @@ -1,16 +1,13 @@ from __future__ import annotations import contextlib +from collections.abc import Iterable, Mapping, MutableSequence, Sequence from typing import ( TYPE_CHECKING, Any, Callable, Generic, - Iterable, - Mapping, - MutableSequence, NoReturn, - Sequence, TypeVar, overload, ) diff --git a/src/magicgui/widgets/bases/_ranged_widget.py b/src/magicgui/widgets/bases/_ranged_widget.py index b6a0e0933..821148587 100644 --- a/src/magicgui/widgets/bases/_ranged_widget.py +++ b/src/magicgui/widgets/bases/_ranged_widget.py @@ -2,8 +2,9 @@ import builtins from abc import ABC, abstractmethod +from collections.abc import Iterable from math import ceil, log10 -from typing import TYPE_CHECKING, Callable, Iterable, Tuple, TypeVar, Union, cast +from typing import TYPE_CHECKING, Callable, TypeVar, Union, cast from magicgui.types import Undefined, _Undefined @@ -16,7 +17,7 @@ from ._widget import WidgetKwargs -T = TypeVar("T", int, float, Tuple[Union[int, float], ...]) +T = TypeVar("T", int, float, tuple[Union[int, float], ...]) DEFAULT_MIN = 0.0 DEFAULT_MAX = 1000.0 diff --git a/src/magicgui/widgets/bases/_slider_widget.py b/src/magicgui/widgets/bases/_slider_widget.py index 2198c33ee..cf8cc1721 100644 --- a/src/magicgui/widgets/bases/_slider_widget.py +++ b/src/magicgui/widgets/bases/_slider_widget.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Tuple, Union +from typing import TYPE_CHECKING, Callable, Union from magicgui.types import Undefined, _Undefined @@ -119,7 +119,7 @@ def readout(self, value: bool) -> None: class MultiValuedSliderWidget( - MultiValueRangedWidget[Tuple[Union[int, float], ...]], SliderWidget + MultiValueRangedWidget[tuple[Union[int, float], ...]], SliderWidget ): """Slider widget that expects a iterable value.""" diff --git a/src/magicgui/widgets/bases/_toolbar.py b/src/magicgui/widgets/bases/_toolbar.py index c8d87766e..5dfa7e62a 100644 --- a/src/magicgui/widgets/bases/_toolbar.py +++ b/src/magicgui/widgets/bases/_toolbar.py @@ -1,13 +1,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Tuple, TypeVar, Union +from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union from ._widget import Widget if TYPE_CHECKING: from magicgui.widgets import protocols -T = TypeVar("T", int, float, Tuple[Union[int, float], ...]) +T = TypeVar("T", int, float, tuple[Union[int, float], ...]) DEFAULT_MIN = 0.0 DEFAULT_MAX = 1000.0 diff --git a/src/magicgui/widgets/bases/_widget.py b/src/magicgui/widgets/bases/_widget.py index 57c5607ce..8550765d8 100644 --- a/src/magicgui/widgets/bases/_widget.py +++ b/src/magicgui/widgets/bases/_widget.py @@ -3,7 +3,7 @@ import inspect import warnings from contextlib import contextmanager -from typing import TYPE_CHECKING, Any, Iterator +from typing import TYPE_CHECKING, Any from psygnal import Signal, SignalInstance @@ -12,6 +12,7 @@ from magicgui.widgets import protocols if TYPE_CHECKING: + from collections.abc import Iterator from weakref import ReferenceType import numpy as np diff --git a/src/magicgui/widgets/protocols.py b/src/magicgui/widgets/protocols.py index 348d5be5d..f052c2e2c 100644 --- a/src/magicgui/widgets/protocols.py +++ b/src/magicgui/widgets/protocols.py @@ -15,14 +15,14 @@ TYPE_CHECKING, Any, Callable, - Iterable, NoReturn, Protocol, - Sequence, runtime_checkable, ) if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + import numpy as np from magicgui.widgets.bases import Widget diff --git a/tests/test_backends.py b/tests/test_backends.py index 65903cd60..849698ec7 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -2,7 +2,6 @@ """Tests for `magicgui._qt` module.""" - import pytest from qtpy import API_NAME, QtCore from qtpy import QtWidgets as QtW diff --git a/tests/test_docs.py b/tests/test_docs.py index 27f57d503..37eefc0eb 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -42,11 +42,10 @@ def test_doc_code_cells(fname): str(f) for f in EXAMPLES.rglob("*.py") if all(x not in str(f) for x in EXCLUDED) ] -# if os is Linux and python version is 3.9 and backend is PyQt5 +# if os is Linux and backend is PyQt5 LINUX = sys.platform.startswith("linux") -PY39 = sys.version_info >= (3, 9) PYQT5 = "PyQt5" in sys.modules -if LINUX and PY39 and PYQT5: +if LINUX and PYQT5: # skip range_slider example because of superqt c++ wrapped item bug example_files = [f for f in example_files if "range_slider" not in f] diff --git a/tests/test_signature.py b/tests/test_signature.py index 1d750cccc..302830b3d 100644 --- a/tests/test_signature.py +++ b/tests/test_signature.py @@ -1,5 +1,6 @@ +from typing import Annotated + import pytest -from typing_extensions import Annotated from magicgui.signature import magic_signature, make_annotated diff --git a/tests/test_types.py b/tests/test_types.py index 7c538d226..f84584c97 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,10 +1,11 @@ +from collections.abc import Sequence from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, Annotated, Optional, Union from unittest.mock import Mock import pytest -from typing_extensions import Annotated, get_args +from typing_extensions import get_args from magicgui import magicgui, register_type, type_map, type_registered, types, widgets from magicgui._type_resolution import resolve_single_type @@ -117,8 +118,7 @@ def widget2(fn: Union[bytes, pathlib.Path, str]): def test_optional_type(): @magicgui(x={"choices": ["a", "b"]}) - def widget(x: Optional[str] = None): - ... + def widget(x: Optional[str] = None): ... assert isinstance(widget.x, widgets.ComboBox) assert widget.x.value is None @@ -136,13 +136,11 @@ def test_widget_options(): def test_nested_forward_refs(): - resolved = resolve_single_type(Optional['List["numpy.ndarray"]']) - - from typing import List + resolved = resolve_single_type(Optional['list["numpy.ndarray"]']) import numpy as np - assert resolved == Optional[List[np.ndarray]] + assert resolved == Optional[list[np.ndarray]] def test_type_registered(): @@ -150,7 +148,9 @@ def test_type_registered(): with type_registered(Path, widget_type=widgets.LineEdit): assert isinstance(widgets.create_widget(annotation=Path), widgets.LineEdit) assert isinstance(widgets.create_widget(annotation=Path), widgets.FileEdit) - assert isinstance(widgets.create_widget(annotation=Sequence[Path]), widgets.FileEdit) + assert isinstance( + widgets.create_widget(annotation=Sequence[Path]), widgets.FileEdit + ) def test_type_registered_callbacks(): @@ -238,5 +238,5 @@ def test_pick_widget_literal(): def test_redundant_annotation() -> None: # just shouldn't raise @magicgui - def f(a: Annotated[List[int], {"annotation": List[int]}]): + def f(a: Annotated[list[int], {"annotation": list[int]}]): pass diff --git a/tests/test_ui_field.py b/tests/test_ui_field.py index cdfd9b74b..f5d7f8355 100644 --- a/tests/test_ui_field.py +++ b/tests/test_ui_field.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field -from typing import NamedTuple, Optional +from typing import Annotated, NamedTuple, Optional import pytest -from typing_extensions import Annotated, TypedDict +from typing_extensions import TypedDict from magicgui.schema._ui_field import UiField, build_widget, get_ui_fields from magicgui.widgets import Container diff --git a/tests/test_util.py b/tests/test_util.py index d3ce2325f..2973ea84b 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,10 +1,7 @@ -import sys import typing from collections.abc import Mapping, Sequence from concurrent.futures import Future -import pytest - from magicgui._util import safe_issubclass @@ -14,75 +11,72 @@ def test_basic(self): assert safe_issubclass(int, object) def test_generic_base(self): - assert safe_issubclass(typing.List[int], list) - assert safe_issubclass(typing.List[int], typing.List) + assert safe_issubclass(list[int], list) + assert safe_issubclass(list[int], list) def test_multiple_generic_base(self): - assert safe_issubclass(typing.List[int], (typing.List, typing.Dict)) + assert safe_issubclass(list[int], (list, dict)) def test_no_exception(self): assert not safe_issubclass(int, 1) def test_typing_inheritance(self): - assert safe_issubclass(typing.List, list) - assert safe_issubclass(list, typing.List) - assert safe_issubclass(typing.Tuple, tuple) - assert safe_issubclass(tuple, typing.Tuple) - assert safe_issubclass(typing.Dict, dict) - assert safe_issubclass(dict, typing.Dict) + assert safe_issubclass(list, list) + assert safe_issubclass(list, list) + assert safe_issubclass(tuple, tuple) + assert safe_issubclass(tuple, tuple) + assert safe_issubclass(dict, dict) + assert safe_issubclass(dict, dict) def test_inheritance_generic_list(self): assert safe_issubclass(list, typing.Sequence) - assert safe_issubclass(typing.List, typing.Sequence) - assert safe_issubclass(typing.List[int], typing.Sequence[int]) - assert safe_issubclass(typing.List[int], typing.Sequence) - assert safe_issubclass(typing.List[int], Sequence) + assert safe_issubclass(list, typing.Sequence) + assert safe_issubclass(list[int], typing.Sequence[int]) + assert safe_issubclass(list[int], typing.Sequence) + assert safe_issubclass(list[int], Sequence) def test_no_inheritance_generic_super(self): - assert not safe_issubclass(list, typing.List[int]) + assert not safe_issubclass(list, list[int]) def test_inheritance_generic_mapping(self): assert safe_issubclass(dict, typing.Mapping) - assert safe_issubclass(typing.Dict, typing.Mapping) - assert safe_issubclass(typing.Dict[int, str], typing.Mapping[int, str]) - assert safe_issubclass(typing.Dict[int, str], typing.Mapping) - assert safe_issubclass(typing.Dict[int, str], Mapping) + assert safe_issubclass(dict, typing.Mapping) + assert safe_issubclass(dict[int, str], typing.Mapping[int, str]) + assert safe_issubclass(dict[int, str], typing.Mapping) + assert safe_issubclass(dict[int, str], Mapping) - @pytest.mark.skipif(sys.version_info < (3, 9), reason="PEP-585 is supported in 3.9+") def test_typing_builtins_list(self): assert safe_issubclass(list[int], list) assert safe_issubclass(list[int], Sequence) assert safe_issubclass(list[int], typing.Sequence) assert safe_issubclass(list[int], typing.Sequence[int]) - assert safe_issubclass(list[int], typing.List[int]) - assert safe_issubclass(typing.List[int], list) - assert safe_issubclass(typing.List[int], list[int]) + assert safe_issubclass(list[int], list[int]) + assert safe_issubclass(list[int], list) + assert safe_issubclass(list[int], list[int]) - @pytest.mark.skipif(sys.version_info < (3, 9), reason="PEP-585 is supported in 3.9+") def test_typing_builtins_dict(self): assert safe_issubclass(dict[int, str], dict) assert safe_issubclass(dict[int, str], Mapping) assert safe_issubclass(dict[int, str], typing.Mapping) assert safe_issubclass(dict[int, str], typing.Mapping[int, str]) - assert safe_issubclass(dict[int, str], typing.Dict[int, str]) - assert safe_issubclass(typing.Dict[int, str], dict) - assert safe_issubclass(typing.Dict[int, str], dict[int, str]) + assert safe_issubclass(dict[int, str], dict[int, str]) + assert safe_issubclass(dict[int, str], dict) + assert safe_issubclass(dict[int, str], dict[int, str]) def test_tuple_check(self): - assert safe_issubclass(typing.Tuple[int, str], tuple) - assert safe_issubclass(typing.Tuple[int], typing.Sequence[int]) - assert safe_issubclass(typing.Tuple[int, int], typing.Sequence[int]) - assert safe_issubclass(typing.Tuple[int, ...], typing.Sequence[int]) - assert safe_issubclass(typing.Tuple[int, ...], typing.Iterable[int]) - assert not safe_issubclass(typing.Tuple[int, ...], typing.Dict[int, typing.Any]) - assert safe_issubclass(typing.Tuple[int, ...], typing.Tuple[int, ...]) - assert safe_issubclass(typing.Tuple[int, int], typing.Tuple[int, ...]) - assert not safe_issubclass(typing.Tuple[int, int], typing.Tuple[int, str]) - assert not safe_issubclass(typing.Tuple[int, int], typing.Tuple[int, int, int]) + assert safe_issubclass(tuple[int, str], tuple) + assert safe_issubclass(tuple[int], typing.Sequence[int]) + assert safe_issubclass(tuple[int, int], typing.Sequence[int]) + assert safe_issubclass(tuple[int, ...], typing.Sequence[int]) + assert safe_issubclass(tuple[int, ...], typing.Iterable[int]) + assert not safe_issubclass(tuple[int, ...], dict[int, typing.Any]) + assert safe_issubclass(tuple[int, ...], tuple[int, ...]) + assert safe_issubclass(tuple[int, int], tuple[int, ...]) + assert not safe_issubclass(tuple[int, int], tuple[int, str]) + assert not safe_issubclass(tuple[int, int], tuple[int, int, int]) - @pytest.mark.skipif(sys.version_info < (3, 9), reason="Future[] is supported in 3.9+") def test_subclass_future(self): - assert safe_issubclass(Future[typing.List[int]], Future[list[int]]) - assert safe_issubclass(Future[typing.List[int]], Future[list]) - assert safe_issubclass(Future[list[int]], Future[typing.List[int]]) - assert not safe_issubclass(Future[list[int]], Future[typing.List[str]]) + assert safe_issubclass(Future[list[int]], Future[list[int]]) + assert safe_issubclass(Future[list[int]], Future[list]) + assert safe_issubclass(Future[list[int]], Future[list[int]]) + assert not safe_issubclass(Future[list[int]], Future[list[str]]) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 0917daff7..938bc0f0e 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -2,11 +2,10 @@ import inspect from enum import Enum from pathlib import Path -from typing import List, Optional, Tuple +from typing import Annotated, Optional from unittest.mock import MagicMock, patch import pytest -from typing_extensions import Annotated from magicgui import magicgui, types, use_app, widgets from magicgui.widgets import Container, request_values @@ -908,13 +907,14 @@ def f1(x=[2, 4, 6]): # noqa: B006 assert f1.x._args_type is int assert f1.x.value == [2, 4, 6] + def test_list_edit_annotations(): @magicgui - def f2(x: List[int]): + def f2(x: list[int]): pass assert type(f2.x) is widgets.ListEdit - assert f2.x.annotation == List[int] + assert f2.x.annotation == list[int] assert f2.x._args_type is int assert f2.x.value == [] f2.x.btn_plus.changed() @@ -923,7 +923,7 @@ def f2(x: List[int]): @magicgui( x={"options": {"widget_type": "Slider", "min": -10, "max": 10, "step": 5}} ) - def f3(x: List[int] = [0]): # noqa: B006 + def f3(x: list[int] = [0]): # noqa: B006 pass assert type(f3.x) is widgets.ListEdit @@ -933,11 +933,11 @@ def f3(x: List[int] = [0]): # noqa: B006 assert f3.x[0].value_widget.step == 5 @magicgui - def f4(x: List[int] = ()): # type: ignore + def f4(x: list[int] = ()): # type: ignore pass assert type(f4.x) is widgets.ListEdit - assert f4.x.annotation == List[int] + assert f4.x.annotation == list[int] assert f4.x._args_type is int assert f4.x.value == [] f4.x.btn_plus.changed() @@ -945,18 +945,17 @@ def f4(x: List[int] = ()): # type: ignore assert f4.x.value == [0] @magicgui - def f5(x: List[Annotated[int, {"max": 3}]]): + def f5(x: list[Annotated[int, {"max": 3}]]): pass assert type(f5.x) is widgets.ListEdit - assert f5.x.annotation == List[int] + assert f5.x.annotation == list[int] f5.x.btn_plus.changed() assert f5.x[0].value_widget.max == 3 def test_tuple_edit(): """Test TupleEdit.""" - from typing import Tuple mock = MagicMock() @@ -986,19 +985,19 @@ def f1(x=(2, 4, 6)): assert f1.x.value == (2, 4, 6) @magicgui - def f2(x: Tuple[int, str]): + def f2(x: tuple[int, str]): pass assert type(f2.x) is widgets.TupleEdit - assert f2.x.annotation == Tuple[int, str] + assert f2.x.annotation == tuple[int, str] assert f2.x.value == (0, "") @magicgui - def f3(x: Tuple[Annotated[int, {"max": 3}], str]): + def f3(x: tuple[Annotated[int, {"max": 3}], str]): pass assert type(f3.x) is widgets.TupleEdit - assert f2.x.annotation == Tuple[int, str] + assert f2.x.annotation == tuple[int, str] assert f3.x[0].max == 3 @@ -1034,7 +1033,7 @@ def _exec(self, **k): def test_range_slider(): @magicgui(auto_call=True, range_value={"widget_type": "RangeSlider", "max": 500}) - def func(range_value: Tuple[int, int] = (20, 380)): + def func(range_value: tuple[int, int] = (20, 380)): print(range_value) assert isinstance(func.range_value, widgets.RangeSlider) @@ -1044,7 +1043,7 @@ def func(range_value: Tuple[int, int] = (20, 380)): def test_float_range_slider(): @magicgui(auto_call=True, range_value={"widget_type": "FloatRangeSlider", "max": 1}) - def func(range_value: Tuple[float, float] = (0.2, 0.8)): + def func(range_value: tuple[float, float] = (0.2, 0.8)): print(range_value) assert isinstance(func.range_value, widgets.FloatRangeSlider) @@ -1053,7 +1052,7 @@ def func(range_value: Tuple[float, float] = (0.2, 0.8)): def test_literal(): - from typing import Literal, Set + from typing import Literal from typing_extensions import get_args @@ -1067,7 +1066,7 @@ def f(x: Lit): ... assert cbox.choices == get_args(Lit) @magicgui - def f(x: Set[Lit]): ... + def f(x: set[Lit]): ... sel = f.x assert type(sel) is widgets.Select @@ -1088,8 +1087,40 @@ def test_separator(backend: str): use_app(backend) sep = [[Separator] * (i + 1) for i in range(4)] - a = [1, 2, *sep[0], 4, *sep[1], 6, 7, *sep[2], 9, *sep[3], 11, 12, *sep[0], 14, *sep[0]] - b = [1, 2, *sep[0], 4, *sep[1], 6, 7, *sep[2], 9, *sep[3], 6, 7, *sep[0], 9, *sep[0]] + a = [ + 1, + 2, + *sep[0], + 4, + *sep[1], + 6, + 7, + *sep[2], + 9, + *sep[3], + 11, + 12, + *sep[0], + 14, + *sep[0], + ] + b = [ + 1, + 2, + *sep[0], + 4, + *sep[1], + 6, + 7, + *sep[2], + 9, + *sep[3], + 6, + 7, + *sep[0], + 9, + *sep[0], + ] b2 = [1, 2, *sep[0], 4, *sep[1], 6, 7, *sep[2], 9, *sep[3], *sep[0], *sep[0]] combo_a = widgets.ComboBox(choices=a, value=a[0]) @@ -1099,6 +1130,7 @@ def test_separator(backend: str): assert len(combo_b) == len(combo_b.choices) if backend == "qt": + def get_all_itemdata(combo_box): return [combo_box.itemData(index) for index in range(combo_box.count())] @@ -1116,8 +1148,8 @@ def get_all_itemdata(combo_box): if backend == "ipynb": # Separator singletons themselves are used as separator item data - assert combo_a.options['choices'] == a - assert combo_b.options['choices'] == b # items are not unique + assert combo_a.options["choices"] == a + assert combo_b.options["choices"] == b # items are not unique # Choices only returns duplicated, non-separator items assert combo_a.choices == (1, 2, 4, 6, 7, 9, 11, 12, 14)