From db386ca52a29fde1eedc46b19a28cd1017e882a5 Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Fri, 26 Dec 2025 22:33:40 +0000 Subject: [PATCH] chore: improve typing (#572) --- libs/pyTermTk/TermTk/TTkCore/constant.py | 2 +- .../TermTk/TTkWidgets/Fancy/tableview.py | 4 +- .../TermTk/TTkWidgets/TTkModelView/table.py | 12 ++-- .../TTkModelView/table_edit_proxy.py | 21 ++++--- .../TTkWidgets/TTkModelView/tablemodelcsv.py | 19 +++++-- .../TTkWidgets/TTkModelView/tablemodellist.py | 31 +++++----- .../TTkModelView/tablemodelsqlite3.py | 28 ++++++--- .../TTkWidgets/TTkModelView/tablewidget.py | 31 +++++++--- libs/pyTermTk/TermTk/TTkWidgets/combobox.py | 9 ++- ...rray.01.copy.py => 02.array.01.copy.01.py} | 0 tests/timeit/02.array.01.copy.02.py | 57 +++++++++++++++++++ 11 files changed, 160 insertions(+), 54 deletions(-) rename tests/timeit/{02.array.01.copy.py => 02.array.01.copy.01.py} (100%) create mode 100644 tests/timeit/02.array.01.copy.02.py diff --git a/libs/pyTermTk/TermTk/TTkCore/constant.py b/libs/pyTermTk/TermTk/TTkCore/constant.py index 4188f84b..34316bf5 100644 --- a/libs/pyTermTk/TermTk/TTkCore/constant.py +++ b/libs/pyTermTk/TermTk/TTkCore/constant.py @@ -60,7 +60,7 @@ class TTkConstant: ColorModifier = 0x08 '''The :py:class:`TTkColor` include a color modifier based on :py:class:`TTkColorModifier`''' - class FocusPolicy(int, Flag): + class FocusPolicy(Flag): ''' This Class type defines the various policies a widget can have with respect to acquiring keyboard focus. diff --git a/libs/pyTermTk/TermTk/TTkWidgets/Fancy/tableview.py b/libs/pyTermTk/TermTk/TTkWidgets/Fancy/tableview.py index e96dfae7..879875c6 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/Fancy/tableview.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/Fancy/tableview.py @@ -443,8 +443,8 @@ class TTkFancyTableView(TTkAbstractScrollView): self._viewOffsetY == y: # Nothong to do return self._excludeEvent = True - for widget in self.layout().iterWidgets(): - widget.viewMoveTo(x,y) + # for widget in self.layout().iterWidgets(): + # widget.viewMoveTo(x,y) self._excludeEvent = False self._viewOffsetX = x self._viewOffsetY = y diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table.py index a3ae1b00..b879763a 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table.py @@ -35,7 +35,7 @@ class TTkTable(TTkAbstractScrollArea): __doc__ = ''' :py:class:`TTkTable` is a container widget which place :py:class:`TTkTableWidget` in a scrolling area with on-demand scroll bars. - ''' + TTkTableWidget.__doc__ + ''' + str(TTkTableWidget.__doc__) classStyle = TTkTableWidget.classStyle @@ -216,18 +216,20 @@ class TTkTable(TTkAbstractScrollArea): ''' .. seealso:: this method is forwarded to :py:meth:`TTkTableWidget.isUndoAvailable` - isUndoAvailable + Returns True if undo is available, False otherwise. - :return: bool + :return: True if undo is available + :rtype: bool ''' return self._tableView.isUndoAvailable() def isRedoAvailable(self) -> bool: ''' .. seealso:: this method is forwarded to :py:meth:`TTkTableWidget.isRedoAvailable` - isRedoAvailable + Returns True if redo is available, False otherwise. - :return: bool + :return: True if redo is available + :rtype: bool ''' return self._tableView.isRedoAvailable() @pyTTkSlot() diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py index f406e7a1..ed01153c 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py @@ -158,7 +158,7 @@ class TTkCellListType(TTkCellListTypeBase): def value(self) -> Any: return self._value - def serValue(self, val: Any) -> None: + def setValue(self, val: Any) -> None: ''' Set the value @@ -166,7 +166,7 @@ class TTkCellListType(TTkCellListTypeBase): :type val: Any ''' if val not in self._items: - raise ValueError(f"{val} not included in {self._list}") + raise ValueError(f"{val} not included in {self._items}") self._value = val def factory(self, value:Any, items:List[Any]) -> TTkCellListTypeBase: @@ -253,6 +253,12 @@ class TTkTableProxyEditWidget(TTkWidget): self.close() def isModal(self) -> bool: + ''' + Check if the editor should be displayed modally. + + :return: True if the editor requires modal display, False otherwise + :rtype: bool + ''' return False @@ -323,7 +329,8 @@ class _ListBaseProxy(TTkResizableFrame, TTkTableProxyEditWidget): def setFocus(self) -> None: ''' Set focus to the internal list widget ''' - return self._list.viewport().setFocus() + if (viewport := self._list.viewport()) and isinstance(viewport, TTkWidget): + viewport.setFocus() def keyEvent(self, evt:TTkKeyEvent) -> bool: return self._list.keyEvent(evt=evt) @@ -343,15 +350,15 @@ class _ListBaseProxy(TTkResizableFrame, TTkTableProxyEditWidget): sb = _ListBaseProxy(items=data.items(), value=data) return sb - def getCellData(self) -> Union[float, int]: + def getCellData(self) -> TTkCellListTypeBase: ''' Get the current selected value from the list :return: The selected item value :rtype: Union[float, int] ''' - self.dataChanged.emit(self._value.factory( + return self._value.factory( value=self._list.selectedItems()[0].data(), - items=self._items)) + items=self._items) class _BoolListProxy(_ListBaseProxy): ''' Boolean editor for table cells @@ -385,7 +392,7 @@ class _BoolListProxy(_ListBaseProxy): sb = _BoolListProxy(value=value, items=[True,False]) return sb - def getCellData(self) -> Union[float, int]: + def getCellData(self) -> Any: ''' Get the current boolean value from the list :return: The selected boolean value (True or False) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelcsv.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelcsv.py index 41732c1c..25f3114d 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelcsv.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelcsv.py @@ -24,12 +24,15 @@ __all__=['TTkTableModelCSV'] import csv +from typing import Tuple,List,Optional,cast + from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.string import TTkStringType from TermTk.TTkWidgets.TTkModelView.tablemodellist import TTkTableModelList class TTkTableModelCSV(TTkTableModelList): ''' - :py:class:`TTkTableModelCSV` extends :py:class:`TTkTableModelList` with cvs loading helpers. + :py:class:`TTkTableModelCSV` extends :py:class:`TTkTableModelList` with csv loading helpers. You can address the csv file through the Filename (filename) or the FileDescriptor (fd). @@ -47,7 +50,7 @@ class TTkTableModelCSV(TTkTableModelList): ''' def __init__(self, *, - filename:str=None, + filename:Optional[str]=None, fd=None) -> None: ''' :param filename: the csv filename, if missing the file descriptor is used instead. @@ -56,7 +59,9 @@ class TTkTableModelCSV(TTkTableModelList): :param fd: the FileDescriptor :type fd: io, optional ''' - data, head, idx = [['']], [], [] + data:List[List[TTkStringType]] = [['']] + head:List[TTkStringType] = [] + idx:List[TTkStringType] = [] if filename: with open(filename, "r") as fd: data, head, idx = self._csvImport(fd) @@ -64,8 +69,10 @@ class TTkTableModelCSV(TTkTableModelList): data, head, idx = self._csvImport(fd) super().__init__(data=data,header=head,indexes=idx) - def _csvImport(self, fd) -> tuple[list,list,list[list]]: - data, head, idx = [], [], [] + def _csvImport(self, fd) -> Tuple[List[List[TTkStringType]],List[TTkStringType],List[TTkStringType]]: + data:List[List[TTkStringType]] = [] + head:List[TTkStringType] = [] + idx:List[TTkStringType] = [] sniffer = csv.Sniffer() try: has_header = sniffer.has_header(fd.read(2048)) @@ -74,7 +81,7 @@ class TTkTableModelCSV(TTkTableModelList): fd.seek(0) csvreader = csv.reader(fd) for row in csvreader: - data.append(row) + data.append(list(row)) if has_header: head = data.pop(0) # check if the first column include an index: diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodellist.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodellist.py index 19c22ac0..280b9389 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodellist.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodellist.py @@ -22,14 +22,15 @@ __all__=['TTkTableModelList'] -from typing import Any +from typing import Any,List,Optional +from TermTk.TTkCore.string import TTkString, TTkStringType from TermTk.TTkCore.constant import TTkK from TermTk.TTkAbstract.abstracttablemodel import TTkAbstractTableModel, TTkModelIndex class _TTkModelIndexList(TTkModelIndex): __slots__ = ('_col','_rowId','_rowCb') - def __init__(self, col:int, rowId:list, rowCb) -> None: + def __init__(self, col:int, rowId:List, rowCb) -> None: self._col = col self._rowId = rowId self._rowCb = rowCb @@ -50,35 +51,35 @@ class _TTkModelIndexList(TTkModelIndex): class TTkTableModelList(TTkAbstractTableModel): ''' :py:class:`TTkTableModelList` extends :py:class:`TTkAbstractTableModel`, - including a basic model with a 2d list data structure + including a basic model with a 2d List data structure ''' __slots__ = ('_data','_dataOriginal', '_hheader', '_vheader') def __init__(self, *, - data:list[list[object]]=None, - header:list[str]=None, - indexes:list[str]=None) -> None: + data:Optional[List[List[Any]]]=None, + header:Optional[List[TTkStringType]]=None, + indexes:Optional[List[TTkStringType]]=None) -> None: ''' :param data: the 2D List model for the view to present. - :type data: list[list] + :type data: List[List] :param header: the header labels, defaults to the column number. - :type header: list[str], optional + :type header: List[str], optional :param indexes: the index labels, defaults to the line number. - :type indexes: list[str], optional + :type indexes: List[str], optional ''' self._data = self._dataOriginal = data if data else [] self._hheader = header if header else [] self._vheader = indexes if indexes else [] super().__init__() - def modelList(self) -> list[list]: + def modelList(self) -> List[List]: return self._data - def setModelList(self, modelList:list[list]) -> None: + def setModelList(self, modelList:List[List]) -> None: if modelList == self._data: return self._data = modelList self.modelChanged.emit() @@ -102,18 +103,18 @@ class TTkTableModelList(TTkAbstractTableModel): return None return col_data[col] - def setData(self, row:int, col:int, data:object) -> None: + def setData(self, row:int, col:int, data:object) -> bool: self._data[row][col] = data self.dataChanged.emit((row,col),(1,1)) return True - def headerData(self, num:int, orientation:int): + def headerData(self, num:int, orientation:TTkK.Direction) -> TTkString: if orientation == TTkK.HORIZONTAL: if self._hheader: - return self._hheader[num] + return TTkString(self._hheader[num]) if orientation == TTkK.VERTICAL: if self._vheader: - return self._vheader[num] + return TTkString(self._vheader[num]) return super().headerData(num, orientation) def flags(self, row:int, col:int) -> TTkK.ItemFlag: diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelsqlite3.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelsqlite3.py index 698a7d73..cefe51a8 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelsqlite3.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablemodelsqlite3.py @@ -20,20 +20,28 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import annotations + __all__=['TTkTableModelSQLite3'] import sqlite3 import threading -from typing import Any +from typing import Any,Dict,List from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.constant import TTkK from TermTk.TTkAbstract.abstracttablemodel import TTkAbstractTableModel, TTkModelIndex class _TTkModelIndexSQLite3(TTkModelIndex): __slots__ = ('_col','_rowId','_sqModel') - def __init__(self, col:int, rowId:str, sqModel) -> None: + + _col:int + _rowId:str + _sqModel:TTkTableModelSQLite3 + + def __init__(self, col:int, rowId:str, sqModel:TTkTableModelSQLite3) -> None: self._col = col self._rowId = rowId self._sqModel = sqModel @@ -44,11 +52,11 @@ class _TTkModelIndexSQLite3(TTkModelIndex): def col(self) -> int: return self._col - def data(self) -> object: + def data(self) -> Any: return self._sqModel.data(row=self.row(),col=self.col()) - def setData(self, data: object) -> None: - return self._sqModel.setData(row=self.row(),col=self.col(),data=data) + def setData(self, data: Any) -> None: + self._sqModel.setData(row=self.row(),col=self.col(),data=data) class TTkTableModelSQLite3(TTkAbstractTableModel): ''' @@ -59,7 +67,7 @@ class TTkTableModelSQLite3(TTkAbstractTableModel): In This example i assume i have a database named **sqlite.database.db** which contain a table **users** - Please refer to `test.ui.032.table.10.sqlite.py `_ for working eample. + Please refer to `test.ui.032.table.10.sqlite.py `_ for working example. .. code-block:: python @@ -85,6 +93,8 @@ class TTkTableModelSQLite3(TTkAbstractTableModel): '_sqliteMutex', '_idMap') + _idMap:Dict[str,int] + def __init__(self, *, fileName:str, table:str, @@ -143,7 +153,7 @@ class TTkTableModelSQLite3(TTkAbstractTableModel): f"SELECT {self._key} FROM {self._table} " f"{self._sort} " f"LIMIT 1 OFFSET {row}") - key = None if not (_fetch:=res.fetchone()) else _fetch[0] + key:str = '' if not (_fetch:=res.fetchone()) else _fetch[0] return _TTkModelIndexSQLite3(col=col,rowId=key,sqModel=self) def data(self, row:int, col:int) -> Any: @@ -154,7 +164,7 @@ class TTkTableModelSQLite3(TTkAbstractTableModel): f"LIMIT 1 OFFSET {row}") return None if not (_fetch:=res.fetchone()) else _fetch[0] - def setData(self, row:int, col:int, data:object) -> None: + def setData(self, row:int, col:int, data:object) -> bool: with self._sqliteMutex: res = self._cur.execute( f"SELECT {self._key} FROM {self._table} " @@ -170,7 +180,7 @@ class TTkTableModelSQLite3(TTkAbstractTableModel): self._refreshIdMap() return True - def headerData(self, num:int, orientation:int): + def headerData(self, num:int, orientation:TTkK.Direction) -> TTkString: if orientation == TTkK.HORIZONTAL: if self._columns: return self._columns[num] diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py index ce723740..f7d357ac 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py @@ -58,22 +58,37 @@ class TTkHeaderView(): @pyTTkSlot(bool) def setVisible(self, visible: bool) -> None: - '''setVisible''' + ''' + Sets the visibility of the header. + + :param visible: True to show the header, False to hide it + :type visible: bool + ''' if self._visible == visible: return self._visible = visible self.visibilityUpdated.emit(visible) @pyTTkSlot() def show(self) -> None: - '''show''' + ''' + Shows the header by setting its visibility to True. + ''' self.setVisible(True) @pyTTkSlot() def hide(self) -> None: - '''hide''' + ''' + Hides the header by setting its visibility to False. + ''' self.setVisible(False) def isVisible(self) -> bool: + ''' + Returns True if the header is visible, False otherwise. + + :return: the visibility status + :rtype: bool + ''' return self._visible _ClipboardTableData = List[List[Tuple[int,int,Any]]] @@ -564,17 +579,19 @@ class TTkTableWidget(TTkAbstractScrollView): def isUndoAvailable(self) -> bool: ''' - isUndoAvailable + Returns True if undo is available, False otherwise. - :return: bool + :return: True if undo is available + :rtype: bool ''' return self._snapshotId > 0 def isRedoAvailable(self) -> bool: ''' - isRedoAvailable + Returns True if redo is available, False otherwise. - :return: bool + :return: True if redo is available + :rtype: bool ''' return self._snapshotId < len(self._snapshot) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/combobox.py b/libs/pyTermTk/TermTk/TTkWidgets/combobox.py index 9d8e4a1e..e9f7eb33 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/combobox.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/combobox.py @@ -33,6 +33,7 @@ from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkLayouts.gridlayout import TTkGridLayout +from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.container import TTkContainer from TermTk.TTkWidgets.list_ import TTkList from TermTk.TTkWidgets.lineedit import TTkLineEdit @@ -90,7 +91,7 @@ class _TTkComboBoxPopup(TTkResizableFrame): super().__init__(**kwargs|{'layout':TTkGridLayout()}) # Create internal list with search disabled - we'll render search text manually self._list:TTkList = TTkList(parent=self, showSearch=False) - self._list.addItems(items) + self._list.addItems(list(items)) # Trigger repaint when search changes to update our custom search overlay self._list.searchModified.connect(self.update) @@ -103,7 +104,9 @@ class _TTkComboBoxPopup(TTkResizableFrame): def keyEvent(self, evt:TTkKeyEvent) -> bool: '''Forward all key events to the list viewport for navigation and search''' - return self._list.viewport().keyEvent(evt) + if (viewport := self._list.viewport()) and isinstance(viewport, TTkWidget): + return viewport.keyEvent(evt) + return False def paintEvent(self, canvas:TTkCanvas) -> None: '''Custom paint event that overlays search text on top of the frame. @@ -180,7 +183,9 @@ class TTkComboBox(TTkContainer): ''' _list:List[str] + _lineEdit:Optional[TTkLineEdit] _popupFrame:Optional[_TTkComboBoxPopup] + def __init__(self, *, list:Optional[List[str]] = None, index:int = -1, diff --git a/tests/timeit/02.array.01.copy.py b/tests/timeit/02.array.01.copy.01.py similarity index 100% rename from tests/timeit/02.array.01.copy.py rename to tests/timeit/02.array.01.copy.01.py diff --git a/tests/timeit/02.array.01.copy.02.py b/tests/timeit/02.array.01.copy.02.py new file mode 100644 index 00000000..6b890fbc --- /dev/null +++ b/tests/timeit/02.array.01.copy.02.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# flake8: noqa +# This file uses match statements (Python 3.10+) which cause syntax errors in Python 3.9 + +from __future__ import annotations + +import sys, os + +from dataclasses import dataclass +from enum import Enum,Flag,auto +import timeit + +from typing import List, Tuple, Iterator + + +test_list = [_i for _i in range(10000)] + +def test_ti_01_01(): + return len(test_list.copy()) + +def test_ti_01_02(): + return len([_x for _x in test_list]) + +def test_ti_01_03(): + return len(list(test_list)) + +loop = 10000 + +a:dict = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")