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

Fix typechecking with latest PyQt5-stubs #279

Merged
merged 23 commits into from
May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e4c6f6b
towards typechecking with newest pyqtstubs
jenshnielsen Apr 29, 2022
28bbfe1
Fix Liskov warning
jenshnielsen Apr 29, 2022
db07825
handle that item may return None
jenshnielsen Apr 29, 2022
f7beeef
fix typo in method call
jenshnielsen Apr 29, 2022
1e20a17
use optional rather than empty dict
jenshnielsen Apr 29, 2022
e8593cd
improve runtime typecheck
jenshnielsen Apr 29, 2022
9a2d1ca
cast layout to the type used
jenshnielsen Apr 30, 2022
dea4e02
make nodewidget generic in embedded widget type
jenshnielsen Apr 30, 2022
f97bda0
remove unused import
jenshnielsen Apr 30, 2022
d2bf282
make FormLeyoutWrapper generic in Elementtype
jenshnielsen Apr 30, 2022
d385b52
fix type vs instance of
jenshnielsen Apr 30, 2022
607b020
Make DimensionReducerNodeWidget Generic in the type of embedded widget
jenshnielsen Apr 30, 2022
2d48c63
Make generic and revert isinstace checks
jenshnielsen Apr 30, 2022
0679d9c
work around typing issues in inspectr
jenshnielsen Apr 30, 2022
9c94bd2
handle inability to get correct type for self.layout()
jenshnielsen Apr 30, 2022
7712a73
make node class generic in nodewidget type
jenshnielsen Apr 30, 2022
72c3ee7
correctly type getRoles return value
jenshnielsen Apr 30, 2022
45201e3
Allow typing errors in autogenerated code
jenshnielsen Apr 30, 2022
2fc3a82
enforce types for ignore hints
jenshnielsen Apr 30, 2022
c8a078b
bump pyqt stubs to latest version
jenshnielsen Apr 30, 2022
b2d849a
Add missing error type
jenshnielsen Apr 30, 2022
7daf6a1
python 3.7 compat use TypedDict from typing_extensions
jenshnielsen Apr 30, 2022
8157946
Merge branch 'master' into pyqtstubs_fix
jenshnielsen May 3, 2022
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
20 changes: 12 additions & 8 deletions plottr/apps/inspectr.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ def updateDates(self, dates: Sequence[str]) -> None:

i = 0
while i < self.count():
if self.item(i).text() not in dates:
elem = self.item(i)
if elem is not None and elem.text() not in dates:
item = self.takeItem(i)
del item
else:
Expand Down Expand Up @@ -116,7 +117,7 @@ class SortableTreeWidgetItem(QtWidgets.QTreeWidgetItem):
def __init__(self, strings: Iterable[str]):
super().__init__(strings)

def __lt__(self, other: "SortableTreeWidgetItem") -> bool:
def __lt__(self, other: QtWidgets.QTreeWidgetItem) -> bool:
col = self.treeWidget().sortColumn()
text1 = self.text(col)
text2 = other.text(col)
Expand Down Expand Up @@ -151,20 +152,23 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None):
def showContextMenu(self, position: QtCore.QPoint) -> None:
model_index = self.indexAt(position)
item = self.itemFromIndex(model_index)
assert item is not None
current_tag_char = item.text(1)

menu = QtWidgets.QMenu()

copy_icon = self.style().standardIcon(QtWidgets.QStyle.SP_DialogSaveButton)
copy_action = menu.addAction(copy_icon, "Copy")

star_action = self.window().starAction
star_action.setText('Star' if current_tag_char != self.tag_dict['star'] else 'Unstar')
menu.addAction(star_action)
window = cast(QCodesDBInspector, self.window())
starAction: QtWidgets.QAction = window.starAction # type: ignore[has-type]

cross_action = self.window().crossAction
cross_action.setText('Cross' if current_tag_char != self.tag_dict['cross'] else 'Uncross')
menu.addAction(cross_action)
starAction.setText('Star' if current_tag_char != self.tag_dict['star'] else 'Unstar')
menu.addAction(starAction)

crossAction: QtWidgets.QAction = window.crossAction # type: ignore[has-type]
crossAction.setText('Cross' if current_tag_char != self.tag_dict['cross'] else 'Uncross')
menu.addAction(crossAction)

action = menu.exec_(self.mapToGlobal(position))
if action == copy_action:
Expand Down
7 changes: 4 additions & 3 deletions plottr/apps/ui/monitr.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@ def finditem(parent: Union["DataFileList", QtWidgets.QTreeWidgetItem], name: str

item = None
for item_ in existingItems:
if item_.text(0) == name:
item = item_
break
if item_ is not None:
if item_.text(0) == name:
item = item_
break
return item

def itemPath(self, item: QtWidgets.QTreeWidgetItem) -> str:
Expand Down
2 changes: 1 addition & 1 deletion plottr/data/qcodes_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def pathAndId(self) -> Tuple[Optional[str], Optional[int]]:
return self._pathAndId

# see https://github.com/python/mypy/issues/1362
@pathAndId.setter # type: ignore
@pathAndId.setter # type: ignore[misc]
@updateOption('pathAndId')
def pathAndId(self, val: Tuple[Optional[str], Optional[int]]) -> None:
if val != self.pathAndId:
Expand Down
7 changes: 4 additions & 3 deletions plottr/gui/data_display.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ def setShape(self, shape: Dict[str, Tuple[int, ...]]) -> None:
"""Set shapes of given elements"""
for i in range(self.topLevelItemCount()):
item = self.topLevelItem(i)
name = item.text(0)
if name in shape:
item.setText(2, str(shape[name]))
if item is not None:
name = item.text(0)
if name in shape:
item.setText(2, str(shape[name]))

def clear(self) -> None:
"""Clear the tree, and make sure all selections are cleared."""
Expand Down
19 changes: 11 additions & 8 deletions plottr/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

Common GUI widgets that are re-used across plottr.
"""
from typing import Union, List, Tuple, Optional, Type, Sequence, Dict, Any, Type
from typing import Union, List, Tuple, Optional, Sequence, Dict, Any, Type, Generic, TypeVar

from .tools import dictToTreeWidgetItems, dpiScalingFactor
from plottr import QtGui, QtCore, Flowchart, QtWidgets, Signal, Slot
Expand All @@ -15,20 +15,22 @@
__author__ = 'Wolfgang Pfaff'
__license__ = 'MIT'

ElementType = TypeVar("ElementType", bound=QtWidgets.QWidget)

class FormLayoutWrapper(QtWidgets.QWidget):

class FormLayoutWrapper(QtWidgets.QWidget, Generic[ElementType]):
"""
Simple wrapper widget for forms.
Expects a list of tuples of the form (label, widget),
creates a widget that contains these using a form layout.
Labels have to be unique.
"""

def __init__(self, elements: List[Tuple[str, QtWidgets.QWidget]],
def __init__(self, elements: List[Tuple[str, ElementType]],
parent: Union[None, QtWidgets.QWidget] = None):
super().__init__(parent)

self.elements = {}
self.elements: Dict[str, ElementType] = {}

layout = QtWidgets.QFormLayout()
for lbl, widget in elements:
Expand Down Expand Up @@ -462,10 +464,11 @@ def setSelected(self, selected: List[str]) -> None:
"""
for i in range(self.count()):
item = self.item(i)
if item.text() in selected:
item.setSelected(True)
else:
item.setSelected(False)
if item is not None:
if item.text() in selected:
item.setSelected(True)
else:
item.setSelected(False)

def emitSelection(self) -> None:
self.dimensionSelectionMade.emit(self.getSelected())
Expand Down
16 changes: 10 additions & 6 deletions plottr/node/autonode.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Any, Callable, Optional, Type
from typing import Dict, Any, Callable, Optional, Type, cast

from .. import QtGui, QtWidgets
from .node import Node, NodeWidget, updateOption
Expand Down Expand Up @@ -62,13 +62,15 @@ def addOption(self, name: str, specs: Dict[str, Any], confirm: bool) -> None:
func = self.widgetConnection.get(optionType, None)
if func is not None:
widget = func(self, name, specs, confirm)

self.layout().addRow(name, widget)
layout = cast(QtWidgets.QFormLayout, self.layout())
if widget is not None:
layout.addRow(name, widget)

def addConfirm(self) -> None:
widget = QtWidgets.QPushButton('Confirm')
widget.pressed.connect(self.signalAllOptions)
self.layout().addRow('', widget)
layout = cast(QtWidgets.QFormLayout, self.layout())
layout.addRow('', widget)


class AutoNode(Node):
Expand Down Expand Up @@ -101,6 +103,10 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None,
self.addConfirm()

class AutoNode_(AutoNode):

_uiClass = AutoNodeGui_
useUi = True

def __init__(self, name: str):
super().__init__(name)
for optName, optSpecs in options.items():
Expand All @@ -110,8 +116,6 @@ def __init__(self, name: str):
AutoNode_.nodeName = nodeName
AutoNode_.nodeOptions = options
AutoNode_.process = func # type: ignore[assignment]
AutoNode_.useUi = True
AutoNode_.uiClass = AutoNodeGui_

return AutoNode_

Expand Down
1 change: 0 additions & 1 deletion plottr/node/data_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,3 @@ def setupUi(self) -> None:
assert self.ui is not None
self.newDataStructure.connect(self.ui.setData)
self.dataShapesChanged.connect(self.ui.setShape)

15 changes: 8 additions & 7 deletions plottr/node/dim_reducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ def setDimInfo(self, dim: str, info: str = '') -> None:
@Slot(dict)
def setDimInfos(self, infos: Dict[str, str]) -> None:
for ax, info in infos.items():
self.setInfo(ax, info)
self.setDimInfo(ax, info)


class DimensionReductionAssignmentWidget(DimensionAssignmentWidget):
Expand Down Expand Up @@ -367,18 +367,19 @@ def setRole(self, dim: str, role: Optional[str] = None, **kw: Any) -> None:
self.setRole(d, 'None')


class DimensionReducerNodeWidget(NodeWidget):
class DimensionReducerNodeWidget(NodeWidget[DimensionReductionAssignmentWidget]):

def __init__(self, node: Optional[Node] = None):
super().__init__(embedWidgetClass=DimensionReductionAssignmentWidget)
# Not clear how to type that self.widget is not None
# iff embedWidgetClass is not None
assert self.widget is not None
self.optSetters = {
'reductions': self.setReductions,
}
self.optGetters = {
'reductions': self.getReductions,
}

self.widget.rolesChanged.connect(
lambda x: self.signalOption('reductions'))

Expand Down Expand Up @@ -640,7 +641,7 @@ def setupUi(self) -> None:
self.newDataStructure.connect(self.ui.setData)


class XYSelectorNodeWidget(NodeWidget):
class XYSelectorNodeWidget(NodeWidget[XYSelectionWidget]):

def __init__(self, node: Optional[Node] = None):
self.icon = get_xySelectIcon()
Expand All @@ -658,10 +659,10 @@ def __init__(self, node: Optional[Node] = None):
lambda x: self.signalOption('dimensionRoles')
)

def getRoles(self) -> Dict[str, str]:
def getRoles(self) -> Dict[str, Union[str, Tuple[ReductionMethod, List[Any], Dict[str, Any]]]]:
assert self.widget is not None
widgetRoles = self.widget.getRoles()
roles = {}
roles: Dict[str, Union[str, Tuple[ReductionMethod, List[Any], Dict[str, Any]]]] = {}
for dimName, rolesOptions in widgetRoles.items():
role = rolesOptions['role']
opts = rolesOptions['options']
Expand All @@ -677,7 +678,7 @@ def getRoles(self) -> Dict[str, str]:
return roles

def setRoles(self, roles: Dict[str, str]) -> None:
assert isinstance(self.widget, XYSelectionWidget)
assert self.widget is not None
# when this is called, we do not want the UI to signal changes.
self.widget.emitRoleChangeSignal = False

Expand Down
38 changes: 22 additions & 16 deletions plottr/node/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

from enum import Enum, unique

from typing import Tuple, Dict, Any, List, Union, Optional, Sequence
from typing import Tuple, Dict, Any, List, Optional, Sequence, cast

from typing_extensions import TypedDict

from plottr import QtGui, Signal, Slot, QtWidgets
from .node import Node, NodeWidget, updateOption, updateGuiFromNode
Expand All @@ -18,10 +20,6 @@
__license__ = 'MIT'


#: Type for additional options when specifying the shape
SpecShapeType = Dict[str, Tuple[Union[str, int], ...]]


@unique
class GridOption(Enum):
"""Options for how to grid data."""
Expand All @@ -38,6 +36,14 @@ class GridOption(Enum):
#: read the shape from DataSet Metadata (if available)
metadataShape = 3

class _WidgetDict(TypedDict):
name: QtWidgets.QComboBox
shape: QtWidgets.QSpinBox

#: Type for additional options when specifying the shape
class SpecShapeType(TypedDict):
order: Tuple[str, ...]
shape: Tuple[int, ...]

class ShapeSpecificationWidget(QtWidgets.QWidget):
"""A widget that allows the user to specify a grid shape.
Expand All @@ -55,7 +61,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None):
super().__init__(parent)

self._axes: List[str] = []
self._widgets: Dict[int, Dict[str, QtWidgets.QWidget]] = {}
self._widgets: Dict[int, _WidgetDict] = {}
self._processChanges = True

layout = QtWidgets.QFormLayout()
Expand All @@ -82,7 +88,7 @@ def _addAxis(self, idx: int, name: str) -> None:
'name': nameWidget,
'shape': dimLenWidget,
}
self.layout().insertRow(idx, nameWidget, dimLenWidget)
cast(QtWidgets.QFormLayout, self.layout()).insertRow(idx, nameWidget, dimLenWidget)

nameWidget.currentTextChanged.connect(
lambda x: self._processAxisChange(idx, x)
Expand All @@ -96,11 +102,11 @@ def setAxes(self, axes: List[str]) -> None:
"""
if axes != self._axes:
self._axes = axes

for i in range(self.layout().rowCount() - 1):
layout = cast(QtWidgets.QFormLayout, self.layout())
for i in range(layout.rowCount() - 1):
self._widgets[i]['name'].deleteLater()
self._widgets[i]['shape'].deleteLater()
self.layout().removeRow(0)
layout.removeRow(0)

self._widgets = {}

Expand Down Expand Up @@ -133,7 +139,7 @@ def _processAxisChange(self, idx: int, newName: str) -> None:
self._widgets[prevIdx]['name'].setCurrentText(unused[0])
self._processChanges = True

def setShape(self, shape: Dict[str, Tuple[Union[str, int], ...]]) -> None:
def setShape(self, shape: SpecShapeType) -> None:
""" Set the shape, will be reflected in the values set in the widgets.

:param shape: A dictionary with keys `order` and `shape`. The value
Expand All @@ -148,7 +154,7 @@ def setShape(self, shape: Dict[str, Tuple[Union[str, int], ...]]) -> None:
self._widgets[i]['shape'].setValue(s)
self._processChanges = True

def getShape(self) -> Dict[str, Tuple[Union[str, int], ...]]:
def getShape(self) -> SpecShapeType:
"""get the currently specified shape.

:returns: a dictionary with keys `order` and `shape`.
Expand Down Expand Up @@ -220,7 +226,7 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None):
self.buttons[GridOption.noGrid].setChecked(True)
self.enableShapeEdit(False)

def getGrid(self) -> Tuple[GridOption, Dict[str, Any]]:
def getGrid(self) -> Tuple[GridOption, Optional[SpecShapeType]]:
"""Get grid option from the current widget selections

:returns: the grid specification, and the options that go with it.
Expand All @@ -230,7 +236,7 @@ def getGrid(self) -> Tuple[GridOption, Dict[str, Any]]:
"""
activeBtn = self.btnGroup.checkedButton()
activeId = self.btnGroup.id(activeBtn)
opts = {}
opts: Optional[SpecShapeType] = None

if GridOption(activeId) == GridOption.specifyShape:
opts = self.shapeSpec.getShape()
Expand Down Expand Up @@ -281,7 +287,7 @@ def gridButtonSelected(self, btn: QtWidgets.QAbstractButton, checked: bool) -> N
def shapeSpecified(self) -> None:
self.signalGridOption(self.getGrid())

def signalGridOption(self, grid: Tuple[GridOption, Dict[str, Any]]) -> None:
def signalGridOption(self, grid: Tuple[GridOption, Optional[SpecShapeType]]) -> None:
self.optionSelected.emit(grid)

def setAxes(self, axes: List[str]) -> None:
Expand Down Expand Up @@ -338,7 +344,7 @@ def setShape(self, shape: Dict[str, Tuple[int, ...]]) -> None:
self.widget.setShape(shape)


class DataGridder(Node):
class DataGridder(Node[DataGridderNodeWidget]):
"""
A node that can put data onto or off a grid.
Has one property: :attr:`grid`. Its possible values are governed by a main option,
Expand Down
Loading