diff --git a/guidata/dataset/qtitemwidgets.py b/guidata/dataset/qtitemwidgets.py index e100ea8..539a40c 100644 --- a/guidata/dataset/qtitemwidgets.py +++ b/guidata/dataset/qtitemwidgets.py @@ -160,11 +160,7 @@ def set(self) -> None: self.item.set_from_string(self.value()) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" pass def value(self) -> Any: @@ -218,11 +214,7 @@ def __init__( self.group.setLayout(self.layout) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" self.edit.update_widgets() def set(self) -> None: @@ -298,11 +290,7 @@ def __init__( self.widgets.append(widget) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" for widget in self.widgets: widget.get() @@ -367,11 +355,7 @@ def __init__( self.edit.textChanged.connect(self.line_edit_changed) # type:ignore def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" value = self.item.get() old_value = str(self.value()) if value is not None: @@ -438,11 +422,7 @@ def __get_text(self) -> str: return str(self.edit.toPlainText()).replace("\u2029", os.linesep) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" value = self.item.get() if value is not None: self.edit.setPlainText(value) @@ -496,11 +476,7 @@ def __init__( self.checkbox.stateChanged.connect(self.state_changed) # type:ignore def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" value = self.item.get() if value is not None: self.checkbox.setChecked(value) @@ -576,11 +552,7 @@ def date_changed(self, value): self.notify_value_change() def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" value = self.item.get() if value: if not isinstance(value, datetime.date): @@ -629,11 +601,7 @@ def date_changed(self, value): self.notify_value_change() def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" value = self.item.get() if value: if not isinstance(value, datetime.datetime): @@ -1004,11 +972,7 @@ def get_widget_value(self) -> Optional[int]: return self.combobox.currentIndex() def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" self.initialize_widget() value = self.item.get() if value is not None: @@ -1074,11 +1038,7 @@ def __init__( self.groupbox.setLayout(layout) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" value = self.item.get() _choices = self.item.get_prop_value("data", "choices") for (i, _choice, _img), checkbox in zip(_choices, self.boxes): @@ -1172,11 +1132,7 @@ def edit_array(self) -> None: self.update(self.arr) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" self.arr = numpy.array(self.item.get(), copy=False) if self.item.get_prop_value("display", "transpose"): self.arr = self.arr.T @@ -1276,11 +1232,7 @@ def __init__( self.cb_value = None def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" self.cb_value = self.item.get() def set(self) -> None: @@ -1355,11 +1307,7 @@ def __init__( ) def get(self) -> None: - """Update widget contents from data item value - - Returns: - Widget value - """ + """Update widget contents from data item value""" self.get_dataset() for widget in self.edit.widgets: widget.get() diff --git a/guidata/dataset/qtwidgets.py b/guidata/dataset/qtwidgets.py index 0a104cc..a3b4397 100644 --- a/guidata/dataset/qtwidgets.py +++ b/guidata/dataset/qtwidgets.py @@ -34,7 +34,10 @@ :members: """ -from typing import * +from __future__ import annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING, Any, Type from qtpy.compat import getopenfilename, getopenfilenames, getsavefilename from qtpy.QtCore import QRect, QSize, Qt, Signal @@ -73,20 +76,27 @@ class DataSetEditDialog(QDialog): - """ - Dialog box for DataSet editing + """Dialog box for DataSet editing + + Args: + instance: DataSet instance to edit + icon: icon name (default: "guidata.svg") + parent: parent widget + apply: function called when Apply button is clicked + wordwrap: if True, comment text is wordwrapped + size: dialog size (default: None) """ def __init__( self, - instance: Union["DataSet", "DataSetGroup"], - icon: Union[str, QIcon] = "", - parent: Optional[QWidget] = None, - apply: Optional[Callable] = None, + instance: DataSet | DataSetGroup, + icon: str | QIcon = "", + parent: QWidget | None = None, + apply: Callable | None = None, wordwrap: bool = True, - size: Optional[Union[QSize, Tuple[int, int]]] = None, + size: QSize | tuple[int, int] | None = None, ) -> None: - QDialog.__init__(self, parent) + super().__init__(parent) win32_fix_title_bar_background(self) self.wordwrap = wordwrap self.apply_func = apply @@ -96,7 +106,7 @@ def __init__( label.setWordWrap(wordwrap) self._layout.addWidget(label) self.instance = instance - self.edit_layout: List["DataSetEditLayout"] = [] + self.edit_layout: list[DataSetEditLayout] = [] self.setup_instance(instance) @@ -129,7 +139,12 @@ def __init__( else: self.resize(*size) - def button_clicked(self, button: "QAbstractButton") -> None: + def button_clicked(self, button: QAbstractButton) -> None: + """Handle button click + + Args: + button: button that was clicked + """ role = self.bbox.buttonRole(button) if ( role == QDialogButtonBox.ApplyRole # type:ignore @@ -141,29 +156,49 @@ def button_clicked(self, button: "QAbstractButton") -> None: self.apply_func(self.instance) def setup_instance(self, instance: Any) -> None: - """Construct main layout""" + """Construct main layout + + Args: + instance: DataSet instance to edit + """ grid = QGridLayout() grid.setAlignment(Qt.AlignTop) # type:ignore self._layout.addLayout(grid) self.edit_layout.append(self.layout_factory(instance, grid)) - def layout_factory( - self, instance: "DataSet", grid: "QGridLayout" - ) -> "DataSetEditLayout": + def layout_factory(self, instance: DataSet, grid: QGridLayout) -> DataSetEditLayout: """A factory method that produces instances of DataSetEditLayout - or derived classes (see DataSetShowDialog) + + Args: + instance: DataSet instance to edit + grid: grid layout + + Returns: + DataSetEditLayout instance """ return DataSetEditLayout(self, instance, grid) - def child_title(self, item: "DataItemVariable") -> str: - """Return data item title combined with QApplication title""" + def child_title(self, item: DataItemVariable) -> str: + """Return data item title combined with QApplication title + + Args: + item: data item + + Returns: + title + """ app_name = QApplication.applicationName() if not app_name: app_name = self.instance.get_title() return "%s - %s" % (app_name, item.label()) def check(self) -> bool: + """Check input of all widgets + + Returns: + True if all widgets are valid + """ is_ok = True for edl in self.edit_layout: if not edl.check_all_values(): @@ -188,12 +223,23 @@ def accept(self) -> None: class DataSetGroupEditDialog(DataSetEditDialog): + """Tabbed dialog box for DataSet editing + + Args: + instance: DataSetGroup instance to edit + icon: icon name (default: "guidata.svg") + parent: parent widget + apply: function called when Apply button is clicked + wordwrap: if True, comment text is wordwrapped + size: dialog size (default: None) """ - Tabbed dialog box for DataSet editing - """ - def setup_instance(self, instance: "DataSetGroup") -> None: - """Override DataSetEditDialog method""" + def setup_instance(self, instance: DataSetGroup) -> None: + """Construct main layout + + Args: + instance: DataSetGroup instance to edit + """ from guidata.dataset.datatypes import DataSetGroup assert isinstance(instance, DataSetGroup) @@ -220,43 +266,60 @@ def setup_instance(self, instance: "DataSetGroup") -> None: class DataSetEditLayout: - """ - Layout in which data item widgets are placed + """Layout in which data item widgets are placed + + Args: + parent: parent widget + instance: DataSet instance to edit + layout: grid layout + items: list of data items + first_line: first line of grid layout + change_callback: function called when any widget's value has changed """ - _widget_factory: Dict[Any, Any] = {} + _widget_factory: dict[Any, Any] = {} @classmethod def register(cls: Type, item_type: Type, factory: Any) -> None: - """Register a factory for a new item_type""" + """Register a factory for a new item_type + + Args: + item_type: item type + factory: factory function + """ cls._widget_factory[item_type] = factory def __init__( self, - parent: Optional[QWidget], - instance: "DataSet", + parent: QWidget | None, + instance: DataSet, layout: QGridLayout, - items: Optional[List["DataItem"]] = None, + items: list[DataItem] | None = None, first_line: int = 0, - change_callback: Optional[Callable] = None, + change_callback: Callable | None = None, ) -> None: self.parent = parent self.instance = instance self.layout = layout self.first_line = first_line self.change_callback = change_callback - self.widgets: List["AbstractDataSetWidget"] = [] + self.widgets: list[AbstractDataSetWidget] = [] # self.linenos = {} # prochaine ligne à remplir par colonne - self.items_pos: Dict["DataItem", List[int]] = {} + self.items_pos: dict[DataItem, list[int]] = {} if not items: items = self.instance._items items = self.transform_items(items) # type:ignore self.setup_layout(items) - def transform_items(self, items: List["DataItem"]) -> List["DataItem"]: - """ - Handle group of items: transform items into a GroupItem instance + def transform_items(self, items: list[DataItem]) -> list[DataItem]: + """Handle group of items: transform items into a GroupItem instance if they are located between BeginGroup and EndGroup + + Args: + items: list of data items + + Returns: + list of data items """ item_lists: Any = [[]] for item in items: @@ -272,7 +335,11 @@ def transform_items(self, items: List["DataItem"]) -> List["DataItem"]: return item_lists[-1] def check_all_values(self) -> bool: - """Check input of all widgets""" + """Check input of all widgets + + Returns: + True if all widgets are valid + """ for widget in self.widgets: if widget.is_active() and not widget.check(): return False @@ -282,8 +349,12 @@ def accept_changes(self) -> None: """Accept changes made to widget inputs""" self.update_dataitems() - def setup_layout(self, items: List["DataItem"]) -> None: - """Place items on layout""" + def setup_layout(self, items: list[DataItem]) -> None: + """Place items on layout + + Args: + items: list of data items + """ def last_col(col, span): """Return last column (which depends on column span)""" @@ -302,7 +373,7 @@ def last_col(col, span): ) # Check if specified rows are consistent - sorted_items: List[Optional[DataItem]] = [None] * len(items) + sorted_items: list[DataItem | None] = [None] * len(items) rows = [] other_items = [] for item in items: @@ -350,14 +421,26 @@ def last_col(col, span): self.refresh_widgets() - def build_widget(self, item: "DataItem") -> "DataSetShowWidget": + def build_widget(self, item: DataItem) -> DataSetShowWidget: + """Build widget for item + + Args: + item: data item + + Returns: + widget + """ factory = self._widget_factory[type(item)] widget = factory(item.bind(self.instance), self) self.widgets.append(widget) return widget - def add_row(self, widget: "DataSetShowWidget") -> None: - """Add widget to row""" + def add_row(self, widget: DataSetShowWidget) -> None: + """Add widget to row + + Args: + widget: widget to add + """ item = widget.item line, col, span = self.items_pos[item.item] if col > 0: @@ -382,9 +465,13 @@ def update_dataitems(self) -> None: widget.set() def update_widgets( - self, except_this_one: Optional[Union[QWidget, "AbstractDataSetWidget"]] = None + self, except_this_one: QWidget | AbstractDataSetWidget | None = None ) -> None: - """Refresh the content of all widgets""" + """Refresh the content of all widgets + + Args: + except_this_one: widget to skip + """ for widget in self.widgets: if widget is not except_this_one: widget.get() @@ -395,8 +482,6 @@ def widget_value_changed(self) -> None: self.change_callback() -from typing import Any - from guidata.dataset.dataitems import ( BoolItem, ButtonItem, @@ -475,12 +560,17 @@ def widget_value_changed(self) -> None: class DataSetShowWidget(AbstractDataSetWidget): - """Read-only base widget""" + """Read-only base widget + + Args: + item: data item variable (``DataItemVariable``) + parent_layout: parent layout (``DataSetEditLayout``) + """ READ_ONLY = True def __init__( - self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" + self, item: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: AbstractDataSetWidget.__init__(self, item, parent_layout) self.group = QLabel() @@ -492,27 +582,33 @@ def __init__( # self.group.setEnabled(False) def get(self) -> None: - """Override AbstractDataSetWidget method""" + """Update widget contents from data item value""" self.set_state() text = self.item.get_string_value() self.group.setText(text) - def set(self) -> Any: - """Read only...""" + def set(self) -> None: + """Update data item value from widget contents""" + # Do nothing: read-only widget pass class ShowColorWidget(DataSetShowWidget): - """Read-only color item widget""" + """Read-only color item widget + + Args: + item: data item variable (``DataItemVariable``) + parent_layout: parent layout (``DataSetEditLayout``) + """ def __init__( - self, item: "DataItemVariable", parent_layout: "DataSetEditLayout" + self, item: DataItemVariable, parent_layout: DataSetEditLayout ) -> None: DataSetShowWidget.__init__(self, item, parent_layout) - self.picture: Optional[QPicture] = None + self.picture: QPicture | None = None def get(self) -> None: - """Override AbstractDataSetWidget method""" + """Update widget contents from data item value""" value = self.item.get() if value is not None: color = QColor(value) @@ -525,7 +621,12 @@ def get(self) -> None: class ShowBooleanWidget(DataSetShowWidget): - """Read-only bool item widget""" + """Read-only bool item widget + + Args: + item: data item variable (``DataItemVariable``) + parent_layout: parent layout (``DataSetEditLayout``) + """ def place_on_grid( self, @@ -536,7 +637,16 @@ def place_on_grid( row_span: int = 1, column_span: int = 1, ): - """Override AbstractDataSetWidget method""" + """Place widget on layout at specified position + + Args: + layout: parent layout + row: row index + label_column: column index for label + widget_column: column index for widget + row_span: number of rows to span + column_span: number of columns to span + """ if not self.item.get_prop_value("display", "label"): widget_column = label_column column_span += 1 @@ -545,7 +655,7 @@ def place_on_grid( layout.addWidget(self.group, row, widget_column, row_span, column_span) def get(self) -> None: - """Override AbstractDataSetWidget method""" + """Update widget contents from data item value""" DataSetShowWidget.get(self) text = self.item.get_prop_value("display", "text") self.group.setText(text) @@ -558,16 +668,43 @@ def get(self) -> None: class DataSetShowLayout(DataSetEditLayout): - """Read-only layout""" + """Read-only layout + + Args: + parent: parent widget + instance: DataSet instance to edit + layout: grid layout + items: list of data items + first_line: first line of grid layout + change_callback: function called when any widget's value has changed + """ _widget_factory = {} class DataSetShowDialog(DataSetEditDialog): - """Read-only dialog box""" + """Read-only dialog box + + Args: + instance: DataSet instance to edit + icon: icon name (default: "guidata.svg") + parent: parent widget + apply: function called when Apply button is clicked + wordwrap: if True, comment text is wordwrapped + size: dialog size (default: None) + """ def layout_factory(self, instance: DataSet, grid: QGridLayout) -> DataSetShowLayout: - """Override DataSetEditDialog method""" + """A factory method that produces instances of DataSetEditLayout + or derived classes (see DataSetShowDialog) + + Args: + instance: DataSet instance to edit + grid: grid layout + + Returns: + DataSetEditLayout instance + """ return DataSetShowLayout(self, instance, grid) @@ -593,13 +730,20 @@ def layout_factory(self, instance: DataSet, grid: QGridLayout) -> DataSetShowLay class DataSetShowGroupBox(QGroupBox): - """Group box widget showing a read-only DataSet""" + """Group box widget showing a read-only DataSet + + Args: + label: group box label (string) + klass: guidata.DataSet class + wordwrap: if True, comment text is wordwrapped + kwargs: keyword arguments passed to DataSet constructor + """ def __init__( self, label: QLabel, klass: Type, wordwrap: bool = False, **kwargs ) -> None: QGroupBox.__init__(self, label) - self.apply_button: Optional[QPushButton] = None + self.apply_button: QPushButton | None = None self.klass = klass self.dataset = klass(**kwargs) self._layout = QVBoxLayout() @@ -613,7 +757,11 @@ def __init__( self.edit = self.get_edit_layout() def get_edit_layout(self) -> DataSetEditLayout: - """Return edit layout""" + """Return edit layout + + Returns: + edit layout + """ return DataSetShowLayout(self, self.dataset, self.grid_layout) def get(self) -> None: @@ -625,13 +773,16 @@ def get(self) -> None: class DataSetEditGroupBox(DataSetShowGroupBox): - """ - Group box widget including a DataSet - - label: group box label (string) - klass: guidata.DataSet class - button_text: action button text (default: "Apply") - button_icon: QIcon object or string (default "apply.png") + """Group box widget including a DataSet + + Args: + label: group box label (string) + klass: guidata.DataSet class + button_text: text of apply button (default: "Apply") + button_icon: icon of apply button (default: "apply.png") + show_button: if True, show apply button (default: True) + wordwrap: if True, comment text is wordwrapped + kwargs: keyword arguments passed to DataSet constructor """ #: Signal emitted when Apply button is clicked @@ -641,11 +792,11 @@ def __init__( self, label: QLabel, klass: Type, - button_text: Optional[str] = None, - button_icon: Optional[Union[QIcon, str]] = None, + button_text: str | None = None, + button_icon: QIcon | str | None = None, show_button: bool = True, wordwrap: bool = False, - **kwargs + **kwargs, ): DataSetShowGroupBox.__init__(self, label, klass, wordwrap=wordwrap, **kwargs) if show_button: @@ -663,7 +814,11 @@ def __init__( ) def get_edit_layout(self) -> DataSetEditLayout: - """Return edit layout""" + """Return edit layout + + Returns: + edit layout + """ return DataSetEditLayout( self, self.dataset, self.grid_layout, change_callback=self.change_callback ) @@ -672,8 +827,12 @@ def change_callback(self) -> None: """Method called when any widget's value has changed""" self.set_apply_button_state(True) - def set(self, check=True) -> None: - """Update data item values from layout contents""" + def set(self, check:bool=True) -> None: + """Update data item values from layout contents + + Args: + check: if True, check input of all widgets + """ for widget in self.edit.widgets: if widget.is_active(): if not check or widget.check(): @@ -682,12 +841,23 @@ def set(self, check=True) -> None: self.set_apply_button_state(False) def set_apply_button_state(self, state: bool) -> None: - """Set apply button enable/disable state""" + """Set apply button enable/disable state + + Args: + state: if True, enable apply button + """ if self.apply_button is not None: self.apply_button.setEnabled(state) - def child_title(self, item: "DataItemVariable") -> str: - """Return data item title combined with QApplication title""" + def child_title(self, item: DataItemVariable) -> str: + """Return data item title combined with QApplication title + + Args: + item: data item + + Returns: + title + """ app_name = QApplication.applicationName() if not app_name: app_name = str(self.title()) diff --git a/guidata/utils/encoding.py b/guidata/utils/encoding.py index b4d5f4f..6f97f07 100644 --- a/guidata/utils/encoding.py +++ b/guidata/utils/encoding.py @@ -11,6 +11,7 @@ source code (Utilities/__init___.py) Copyright © 2003-2009 Detlev Offenbach """ +from __future__ import annotations import os import re @@ -47,7 +48,7 @@ ] -def get_coding(text): +def get_coding(text: str) -> str | None: """ Function to get the coding of a text. @param text text to inspect (string) @@ -70,7 +71,7 @@ def get_coding(text): return codec -def decode(text): +def decode(text: bytes) -> tuple[str, str] | tuple[str, str] | tuple[str, str]: """ Function to decode a text. @param text text to decode (bytes) @@ -100,7 +101,7 @@ def decode(text): return str(text, "latin-1"), "latin-1-guessed" -def encode(text, orig_coding): +def encode(text: str, orig_coding: str) -> tuple[bytes, str] | tuple[bytes, str]: """ Function to encode a text. @param text text to encode (string) @@ -146,7 +147,7 @@ def encode(text, orig_coding): return text.encode("utf-8"), "utf-8" -def write(text, filename, encoding="utf-8", mode="wb"): +def write(text: str, filename: str, encoding: str = "utf-8", mode: str = "wb") -> str: """ Write 'text' to file ('filename') assuming 'encoding' Return (eventually new) encoding @@ -157,7 +158,9 @@ def write(text, filename, encoding="utf-8", mode="wb"): return encoding -def writelines(lines, filename, encoding="utf-8", mode="wb"): +def writelines( + lines: list[str], filename: str, encoding: str = "utf-8", mode: str = "wb" +) -> str: """ Write 'lines' to file ('filename') assuming 'encoding' Return (eventually new) encoding @@ -165,7 +168,7 @@ def writelines(lines, filename, encoding="utf-8", mode="wb"): return write(os.linesep.join(lines), filename, encoding, mode) -def read(filename, encoding="utf-8"): +def read(filename: str, encoding: str = "utf-8") -> tuple[str, str]: """ Read text from file ('filename') Return text and encoding @@ -174,7 +177,7 @@ def read(filename, encoding="utf-8"): return text, encoding -def readlines(filename, encoding="utf-8"): +def readlines(filename: str, encoding: str = "utf-8") -> tuple[list[str], str]: """ Read lines from file ('filename') Return lines and encoding diff --git a/guidata/utils/misc.py b/guidata/utils/misc.py index caa25f6..5ede9e2 100644 --- a/guidata/utils/misc.py +++ b/guidata/utils/misc.py @@ -34,7 +34,7 @@ import os.path as osp import subprocess import sys -from typing import Any +from typing import Any, Type # Local imports from guidata.userconfig import get_home_dir @@ -84,8 +84,16 @@ def decode_fs_string(string: str) -> str: # ============================================================================== -def assert_interface_supported(klass, iface): - """Makes sure a class supports an interface""" +def assert_interface_supported(klass: Type, iface: Type) -> None: + """Makes sure a class supports an interface + + Args: + klass (Type): The class. + iface (Type): The interface. + + Raises: + AssertionError: If the class does not support the interface. + """ for name, func in list(iface.__dict__.items()): if name == "__inherits__": continue @@ -107,9 +115,15 @@ def assert_interface_supported(klass, iface): pass # should check class attributes for consistency -def assert_interfaces_valid(klass): - """Makes sure a class supports the interfaces - it declares""" +def assert_interfaces_valid(klass: Type) -> None: + """Makes sure a class supports the interfaces it declares + + Args: + klass (Type): The class. + + Raises: + AssertionError: If the class does not support the interfaces it declares. + """ assert hasattr(klass, "__implements__"), "Class doesn't implements anything" for iface in klass.__implements__: assert_interface_supported(klass, iface)