Skip to content

Commit

Permalink
drop py3.8
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Nov 8, 2024
1 parent 83822e5 commit 95b25cb
Show file tree
Hide file tree
Showing 43 changed files with 230 additions and 221 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion docs/examples/demo_widgets/file_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ def filepicker(filename=Path("~")) -> Path:
return filename



filepicker.filename.changed.connect(print)
filepicker.show(run=True)
2 changes: 1 addition & 1 deletion docs/examples/demo_widgets/files_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 1 addition & 3 deletions docs/examples/demo_widgets/range_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/matplotlib/waveform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions docs/examples/napari/napari_img_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down
13 changes: 5 additions & 8 deletions docs/examples/napari/napari_parameter_sweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
```
Expand Down Expand Up @@ -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)
Expand All @@ -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)
```
Expand Down
3 changes: 2 additions & 1 deletion docs/examples/notebooks/magicgui_jupyter.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion docs/examples/notebooks/magicgui_jupyter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
4 changes: 0 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion docs/scripts/_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,6 +22,8 @@
warnings.simplefilter("ignore", DeprecationWarning)

if TYPE_CHECKING:
from collections.abc import Mapping

from mkdocs.structure.pages import Page


Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "[email protected]", name = "Talley Lambert" }]
classifiers = [
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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]
Expand Down
35 changes: 10 additions & 25 deletions src/magicgui/_type_resolution.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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__)
Expand All @@ -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.
Expand Down
11 changes: 5 additions & 6 deletions src/magicgui/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from typing import (
TYPE_CHECKING,
Callable,
Iterable,
get_args,
get_origin,
overload,
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion src/magicgui/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/magicgui/backends/_ipynb/widgets.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,6 +16,8 @@
from magicgui.widgets import protocols

if TYPE_CHECKING:
from collections.abc import Iterable

from magicgui.widgets.bases import Widget


Expand Down
4 changes: 3 additions & 1 deletion src/magicgui/backends/_qtpy/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,6 +31,8 @@
from magicgui.widgets._concrete import _LabeledWidget

if TYPE_CHECKING:
from collections.abc import Iterable, Iterator, Sequence

import numpy


Expand Down
Loading

0 comments on commit 95b25cb

Please sign in to comment.