Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Use parametrized generics for ListEdit and TupleEdit #500

Merged
merged 1 commit into from
Nov 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion magicgui/widgets/_bases/container_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
Any,
Callable,
Iterable,
Mapping,
MutableSequence,
Sequence,
TypeVar,
overload,
)

from psygnal import Signal
from pyparsing import Mapping

from magicgui._util import debounce
from magicgui.application import use_app
Expand Down
75 changes: 29 additions & 46 deletions magicgui/widgets/_concrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
All of these widgets should provide the `widget_type` argument to their
super().__init__ calls.
"""

from __future__ import annotations

import contextlib
import inspect
import math
import os
Expand Down Expand Up @@ -50,6 +52,9 @@
)

BUILDING_DOCS = sys.argv[-2:] == ["build", "docs"]
WidgetVar = TypeVar("WidgetVar", bound=Widget)
C = TypeVar("C", bound=type)
_V = TypeVar("_V")


def _param_list_to_str(param_list: list[DocstringParam]) -> str:
Expand All @@ -71,7 +76,9 @@ def _param_list_to_str(param_list: list[DocstringParam]) -> str:
return "\n".join(out)


def merge_super_sigs(cls, exclude=("widget_type", "kwargs", "args", "kwds", "extra")):
def merge_super_sigs(
cls: C, exclude=("widget_type", "kwargs", "args", "kwds", "extra")
) -> C:
"""Merge the signature and kwarg docs from all superclasses, for clearer docs.
Parameters
Expand Down Expand Up @@ -109,7 +116,7 @@ def merge_super_sigs(cls, exclude=("widget_type", "kwargs", "args", "kwds", "ext
k: v.replace(annotation=inspect.Parameter.empty) for k, v in params.items()
}

cls.__init__.__signature__ = inspect.Signature(
cls.__init__.__signature__ = inspect.Signature( # type: ignore
sorted(params.values(), key=lambda x: x.kind)
)
param_docs = [p for p in param_docs if p.arg_name not in exclude]
Expand All @@ -120,15 +127,12 @@ def merge_super_sigs(cls, exclude=("widget_type", "kwargs", "args", "kwds", "ext
return cls


C = TypeVar("C")


@overload
def backend_widget( # noqa
cls: type[C],
cls: C,
widget_name: str = None,
transform: Callable[[type], type] = None,
) -> type[C]:
) -> C:
...


Expand All @@ -137,15 +141,15 @@ def backend_widget( # noqa
cls: Literal[None] = None,
widget_name: str = None,
transform: Callable[[type], type] = None,
) -> Callable[..., type[C]]:
) -> Callable[..., C]:
...


def backend_widget(
cls: type[C] = None,
cls: C = None,
widget_name: str = None,
transform: Callable[[type], type] = None,
) -> Callable | type[C]:
) -> Callable | C:
"""Decorate cls to inject the backend widget of the same name.
The purpose of this decorator is to "inject" the appropriate backend
Expand Down Expand Up @@ -434,7 +438,7 @@ def __init__(self, choices=(), orientation="vertical", **kwargs):


@backend_widget
class Container(ContainerWidget):
class Container(ContainerWidget[WidgetVar]):
"""A Widget to contain other widgets."""


Expand Down Expand Up @@ -555,7 +559,7 @@ def __repr__(self) -> str:


@merge_super_sigs
class RangeEdit(Container):
class RangeEdit(Container[SpinBox]):
"""A widget to represent a python range object, with start/stop/step.
A range object produces a sequence of integers from start (inclusive)
Expand Down Expand Up @@ -652,11 +656,8 @@ def value(self, value: slice):
self.step.value = value.step


_V = TypeVar("_V")


@merge_super_sigs
class ListEdit(Container):
class ListEdit(Container[ValueWidget]):
"""A widget to represent a list of values.
A ListEdit container can create a list with multiple objects of same type. It
Expand Down Expand Up @@ -750,20 +751,12 @@ def annotation(self, value):
self._annotation = value
self._args_type = arg

def __iter__(self) -> Iterator[ValueWidget]:
"""Just for typing."""
return super().__iter__()

def __getitem__(self, key: int) -> ValueWidget: # type: ignore[override]
"""Just for typing."""
return super().__getitem__(key)

def __delitem__(self, key: int | slice):
def __delitem__(self, key: int | slice) -> None:
"""Delete child widget(s)."""
super().__delitem__(key)
self.changed.emit(self.value)

def _append_value(self, value=Undefined):
def _append_value(self, value: _V | _Undefined = Undefined) -> None:
"""Create a new child value widget and append it."""
i = len(self) - 2

Expand All @@ -787,12 +780,10 @@ def _append_value(self, value=Undefined):
widget.changed.connect(lambda: self.changed.emit(self.value))
self.changed.emit(self.value)

def _pop_value(self):
def _pop_value(self) -> None:
"""Delete last child value widget."""
try:
with contextlib.suppress(IndexError):
self.pop(-3)
except IndexError:
pass

@property
def value(self) -> list[_V]:
Expand Down Expand Up @@ -822,7 +813,7 @@ class ListDataView(Generic[_V]):

def __init__(self, obj: ListEdit):
self._obj = obj
self._widgets: list[ValueWidget] = list(obj[:-2]) # type: ignore
self._widgets = list(obj[:-2])

def __repr__(self):
"""Return list-like representation."""
Expand All @@ -837,11 +828,11 @@ def __eq__(self, other):
return list(self) == other

@overload
def __getitem__(self, i: int) -> _V: # noqa
def __getitem__(self, i: int) -> _V: # noqa: D105
...

@overload
def __getitem__(self, key: slice) -> list[_V]: # noqa
def __getitem__(self, key: slice) -> list[_V]: # noqa: D105
...

def __getitem__(self, key):
Expand All @@ -856,11 +847,11 @@ def __getitem__(self, key):
)

@overload
def __setitem__(self, key: int, value: _V) -> None: # noqa
def __setitem__(self, key: int, value: _V) -> None: # noqa: D105
...

@overload
def __setitem__(self, key: slice, value: _V | Iterable[_V]) -> None: # noqa
def __setitem__(self, key: slice, value: _V | Iterable[_V]) -> None: # noqa: D105
...

def __setitem__(self, key, value):
Expand All @@ -882,11 +873,11 @@ def __setitem__(self, key, value):
)

@overload
def __delitem__(self, key: int) -> None: # noqa
def __delitem__(self, key: int) -> None: # noqa: D105
...

@overload
def __delitem__(self, key: slice) -> None: # noqa
def __delitem__(self, key: slice) -> None: # noqa: D105
...

def __delitem__(self, key):
Expand All @@ -900,7 +891,7 @@ def __iter__(self) -> Iterator[_V]:


@merge_super_sigs
class TupleEdit(Container):
class TupleEdit(Container[ValueWidget]):
"""A widget to represent a tuple of values.
A TupleEdit container has several child widgets of different type. Their value is
Expand Down Expand Up @@ -952,14 +943,6 @@ def __init__(
widget.changed.disconnect()
widget.changed.connect(lambda: self.changed.emit(self.value))

def __iter__(self) -> Iterator[ValueWidget]:
"""Just for typing."""
return super().__iter__()

def __getitem__(self, key: int) -> ValueWidget: # type: ignore[override]
"""Just for typing."""
return super().__getitem__(key)

@property
def annotation(self):
"""Return type annotation for the parameter represented by the widget.
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ ignore_missing_imports = True
[mypy-.examples/,magicgui._mpl_image.*,magicgui.backends._qtpy.widgets.*]
ignore_errors = True

[mypy-.examples,numpy.*,_pytest.*,packaging.*,pyparsing.*,importlib_metadata.*,docstring_parser.*,psygnal.*,qtpy.*,imageio.*,ipywidgets.*]
[mypy-.examples,numpy.*,_pytest.*,packaging.*,importlib_metadata.*,docstring_parser.*,psygnal.*,qtpy.*,imageio.*,ipywidgets.*]
ignore_errors = True

[mypy-tomli.*]
Expand Down