Skip to content

Commit

Permalink
refactor: enable strict mypy type checking for most modules (#518)
Browse files Browse the repository at this point in the history
* more ruff fixes

* chore: working on types [wip]

* refactor: tons of type fixes

* style(pre-commit.ci): auto fixes [...]

* chore: first pass finished

* fix: fix tqdm

* styles: tqdm

* fix: fix table types

* fix: did some qt cleanup, but then gave up

* fix: fix py39?

* fix: fix py38?

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
tlambert03 and pre-commit-ci[bot] committed Nov 27, 2022
1 parent f46c574 commit 2d42d1e
Show file tree
Hide file tree
Showing 44 changed files with 1,041 additions and 899 deletions.
23 changes: 6 additions & 17 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
ci:
autoupdate_schedule: monthly
autofix_commit_msg: "style: [pre-commit.ci] auto fixes [...]"
autoupdate_commit_msg: "ci: [pre-commit.ci] autoupdate"

autofix_commit_msg: "style(pre-commit.ci): auto fixes [...]"
autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate"

repos:

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-docstring-first
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:
- id: isort

- repo: https://github.com/asottile/pyupgrade
rev: v3.2.2
hooks:
- id: pyupgrade
args: ["--py38-plus", "--keep-runtime-typing"]

- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.118
rev: v0.0.141
hooks:
- id: ruff
args: ["--fix"]
Expand All @@ -44,7 +32,8 @@ repos:
rev: v0.991
hooks:
- id: mypy
files: src/magicgui
files: "^src/"
exclude: src/magicgui/type_map/_magicgui.py
additional_dependencies:
- numpy
- psygnal
2 changes: 1 addition & 1 deletion examples/optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


# Using optional will add a '----' to the combobox, which returns "None"
@magicgui(path=dict(choices=["a", "b"]))
@magicgui(path={"choices": ["a", "b"]})
def f(path: Optional[str] = None):
print(path, type(path))

Expand Down
2 changes: 1 addition & 1 deletion examples/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
def long_running(steps=10, delay=0.1):
"""Long running computation with range iterator."""
# trange(steps) is a shortcut for `tqdm(range(steps))`
for i in trange(steps):
for _i in trange(steps):
sleep(delay)


Expand Down
2 changes: 1 addition & 1 deletion examples/progress_nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def long_function(
"""Long running computation with nested iterators."""
# trange and tqdm accept all the kwargs from tqdm itself, as well as any
# valid kwargs for magicgui.widgets.ProgressBar, (such as "label")
for r in trange(repeats, label="repeats"):
for _r in trange(repeats, label="repeats"):
letters = [random.choice(choices) for _ in range(steps)]
# `tqdm`, like `tqdm`, accepts any iterable
# this progress bar is nested and will be run & reset multiple times
Expand Down
2 changes: 1 addition & 1 deletion examples/range_slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from magicgui import magicgui


@magicgui(auto_call=True, range_value=dict(widget_type="RangeSlider", max=500))
@magicgui(auto_call=True, range_value={"widget_type": "RangeSlider", "max": 500})
def func(range_value: Tuple[int, int] = (20, 380)):
print(range_value)

Expand Down
2 changes: 1 addition & 1 deletion examples/values_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

vals = request_values(
age=int,
name=dict(annotation=str, label="Enter your name:"),
name={"annotation": str, "label": "Enter your name:"},
title="Hi, who are you?",
)
print(repr(vals))
60 changes: 36 additions & 24 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,38 @@ src_paths = ["src/magicgui", "tests"]

# https://github.com/charliermarsh/ruff
[tool.ruff]
target-version = "py38"
line-length = 88
extend-select = ["D", "M001"]
target-version = "py38"
src = ["src", "tests"]
extend-select = [
"E", # style errors
"F", # flakes
"D", # pydocstyle
"I001", # isort
"U", # pyupgrade
# "N", # pep8-naming
# "S", # bandit
"C", # flake8-comprehensions
"B", # flake8-bugbear
"A001", # flake8-builtins
"RUF", # ruff-specific rules
"RUF", # ruff-specific rules
"M001", # Unused noqa directive
]
extend-ignore = [
"D100",
"D107",
"D203",
"D212",
"D213",
"D402",
"D413",
"D415",
"D416",
"D100", # Missing docstring in public module
"D107", # Missing docstring in __init__
"D203", # 1 blank line required before class docstring
"D212", # Multi-line docstring summary should start at the first line
"D213", # Multi-line docstring summary should start at the second line
"D413", # Missing blank line after last section
"D416", # Section name should end with a colon
"C901", # Function is too complex
]

[tool.ruff.per-file-ignores]
"tests/*.py" = ["D", "E501"]
"examples/*.py" = ["D"]
"examples/*.py" = ["D", "B"]
"src/magicgui/widgets/_image/*.py" = ["D"]
"setup.py" = ["F821"]

Expand All @@ -183,23 +197,20 @@ filterwarnings = [
# https://mypy.readthedocs.io/en/stable/config_file.html
[tool.mypy]
files = "src/**/*.py"
warn_unused_configs = true
warn_unused_ignores = true
check_untyped_defs = true
implicit_reexport = false
show_column_numbers = false
strict = true
disallow_any_generics = false
disallow_subclassing_any = false
show_error_codes = true
ignore_missing_imports = true
pretty = true

[[tool.mypy.overrides]]
module = [
"magicgui.widgets._image.*",
".examples/",
"magicgui.backends._qtpy.widgets.*",
"_pytest.*",
]
module = ["_pytest.*", ".examples/", "magicgui.widgets._image.*", "magicgui.backends.*"]
ignore_errors = true

[[tool.mypy.overrides]]
module = ["ipywidgets.*", "toolz.*"]
ignore_missing_imports = true


# https://coverage.readthedocs.io/en/6.4/config.html
[tool.coverage.report]
Expand All @@ -211,6 +222,7 @@ exclude_lines = [
"except ImportError*",
"raise NotImplementedError()",
"pass",
"\\.\\.\\.",
]
omit = [
"src/magicgui/events.py",
Expand Down
3 changes: 2 additions & 1 deletion src/magicgui/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""magicgui is a utility for generating a GUI from a python function."""
from importlib.metadata import PackageNotFoundError, version
from typing import Any

try:
__version__ = version("magicgui")
Expand All @@ -24,7 +25,7 @@
]


def __getattr__(name: str):
def __getattr__(name: str) -> Any:
if name == "FunctionGui":
from warnings import warn

Expand Down
2 changes: 1 addition & 1 deletion src/magicgui/_type_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def resolve_single_type(
_cached_resolve = lru_cache(maxsize=None)(resolve_single_type)


def _try_cached_resolve(v):
def _try_cached_resolve(v: Any) -> Any:
try:
return _cached_resolve(v)
except TypeError:
Expand Down
47 changes: 34 additions & 13 deletions src/magicgui/_util.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
from __future__ import annotations

import os
import sys
import time
from functools import wraps
from pathlib import Path
from typing import Optional
from typing import TYPE_CHECKING, Callable, overload

if TYPE_CHECKING:
from typing import TypeVar

from typing_extensions import ParamSpec

T = TypeVar("T")
P = ParamSpec("P")


@overload
def debounce(function: Callable[P, T]) -> Callable[P, T | None]:
...


@overload
def debounce(*, wait: float = 0.2) -> Callable[[Callable[P, T]], Callable[P, T | None]]:
...


def debounce(function=None, wait: float = 0.2):
def debounce(function: Callable[P, T] | None = None, wait: float = 0.2) -> Callable:
"""Postpone function call until `wait` seconds since last invokation."""

def decorator(fn):
def decorator(fn: Callable[P, T]) -> Callable[P, T | None]:
from threading import Timer

_store: dict = {"timer": None, "last_call": 0.0, "args": (), "kwargs": {}}

@wraps(fn)
def debounced(*args, **kwargs):
def debounced(*args: P.args, **kwargs: P.kwargs) -> T | None:
_store["args"] = args
_store["kwargs"] = kwargs

def call_it():
def call_it() -> T:
_store["timer"] = None
_store["last_call"] = time.time()
return fn(*_store["args"], **_store["kwargs"])
Expand All @@ -31,22 +51,23 @@ def call_it():
time_since_last_call = time.time() - _store["last_call"]
_store["timer"] = Timer(wait - time_since_last_call, call_it)
_store["timer"].start() # type: ignore
return None

return debounced

return decorator if function is None else decorator(function)
return decorator if function is None else decorator(function) # type: ignore


def throttle(t):
def throttle(t: float) -> Callable[[Callable[P, T]], Callable[P, T | None]]:
"""Prevent a function from being called more than once in `t` seconds."""

def decorator(f):
def decorator(f: Callable[P, T]) -> Callable[P, T | None]:
last = [0.0]

@wraps(f)
def wrapper(*args, **kwargs):
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T | None:
if last[0] and (time.time() - last[0] < t):
return
return None
last[0] = time.time()
return f(*args, **kwargs)

Expand All @@ -58,7 +79,7 @@ def wrapper(*args, **kwargs):
# modified from appdirs: https://github.com/ActiveState/appdirs
# License: MIT
def user_cache_dir(
appname: Optional[str] = "magicgui", version: Optional[str] = None
appname: str | None = "magicgui", version: str | None = None
) -> Path:
r"""Return full path to the user-specific cache dir for this application.
Expand Down Expand Up @@ -120,9 +141,9 @@ def user_cache_dir(
return path


def safe_issubclass(obj, superclass):
def safe_issubclass(obj: object, superclass: object) -> bool:
"""Safely check if obj is a subclass of superclass."""
try:
return issubclass(obj, superclass)
return issubclass(obj, superclass) # type: ignore
except Exception:
return False
Loading

0 comments on commit 2d42d1e

Please sign in to comment.