From 4599602e75e0682e09fa40ca69bfbb3791ddb606 Mon Sep 17 00:00:00 2001 From: Saeed Rasooli Date: Wed, 15 Jan 2025 20:45:38 +0330 Subject: [PATCH] ui: add missing type annotations --- pyglossary/ui/argparse_main.py | 2 +- pyglossary/ui/gtk3_utils/__init__.py | 2 + pyglossary/ui/gtk3_utils/about.py | 6 +- pyglossary/ui/gtk3_utils/dialog.py | 8 +- pyglossary/ui/gtk3_utils/resize_button.py | 10 +- pyglossary/ui/gtk3_utils/utils.py | 76 ++++---- pyglossary/ui/gtk4_utils/about.py | 6 +- pyglossary/ui/gtk4_utils/dialog.py | 8 +- pyglossary/ui/gtk4_utils/resize_button.py | 8 +- pyglossary/ui/gtk4_utils/utils.py | 82 ++++---- pyglossary/ui/pbar_legacy.py | 2 +- pyglossary/ui/pbar_tqdm.py | 9 +- pyglossary/ui/tools/diff_glossary.py | 6 +- pyglossary/ui/ui_cmd_interactive.py | 91 ++++----- pyglossary/ui/ui_gtk.py | 144 +++++++-------- pyglossary/ui/ui_gtk4.py | 216 ++++++++++++---------- pyglossary/ui/ui_tk.py | 96 +++++----- pyglossary/ui/ui_web/ui_controller.py | 2 +- pyglossary/ui/ui_web/weblog.py | 9 +- pyglossary/ui/ui_web/websocket_handler.py | 61 +++--- pyglossary/ui/ui_web/websocket_main.py | 27 ++- pyglossary/ui/ui_web/websocket_server.py | 140 +++++++++----- pyproject.toml | 5 +- 23 files changed, 563 insertions(+), 453 deletions(-) diff --git a/pyglossary/ui/argparse_main.py b/pyglossary/ui/argparse_main.py index 318b7a7f3..7168616ad 100644 --- a/pyglossary/ui/argparse_main.py +++ b/pyglossary/ui/argparse_main.py @@ -11,7 +11,7 @@ import logging -def defineFlags(parser: argparse.ArgumentParser, config: dict[str, Any]): +def defineFlags(parser: argparse.ArgumentParser, config: dict[str, Any]) -> None: defaultHasColor = config.get( "color.enable.cmd.windows" if os.sep == "\\" else "color.enable.cmd.unix", True, diff --git a/pyglossary/ui/gtk3_utils/__init__.py b/pyglossary/ui/gtk3_utils/__init__.py index 50941c857..c1a2a7a11 100644 --- a/pyglossary/ui/gtk3_utils/__init__.py +++ b/pyglossary/ui/gtk3_utils/__init__.py @@ -2,3 +2,5 @@ # do not sort these imports! from gi.repository import Gtk as gtk # noqa: I001 from gi.repository import Gdk as gdk # noqa: I001 + +__all__ = ["gdk", "gtk"] diff --git a/pyglossary/ui/gtk3_utils/about.py b/pyglossary/ui/gtk3_utils/about.py index 3071e1218..6eb00e31b 100644 --- a/pyglossary/ui/gtk3_utils/about.py +++ b/pyglossary/ui/gtk3_utils/about.py @@ -78,7 +78,7 @@ def newTabWidgetTextView( text: str, wrap: bool = False, justification: gtk.Justification | None = None, - ): + ) -> gtk.ScrolledWindow: tv = gtk.TextView() tv.set_editable(False) tv.set_can_focus(False) @@ -104,7 +104,7 @@ def newTabLabelWidget( text: str, # wrap: bool = False, # justification: "gtk.Justification | None" = None, - ): + ) -> gtk.ScrolledWindow: box = VBox() box.set_border_width(10) label = gtk.Label() @@ -127,7 +127,7 @@ def newTabLabelWidget( return swin @staticmethod - def newTabTitle(title: str, icon: str): + def newTabTitle(title: str, icon: str) -> gtk.Box: box = gtk.Box(orientation=gtk.Orientation.VERTICAL) if icon: box.pack_start(imageFromFile(icon), False, False, 5) diff --git a/pyglossary/ui/gtk3_utils/dialog.py b/pyglossary/ui/gtk3_utils/dialog.py index 310a85a01..db1dbf35f 100644 --- a/pyglossary/ui/gtk3_utils/dialog.py +++ b/pyglossary/ui/gtk3_utils/dialog.py @@ -17,6 +17,8 @@ # GNU General Public License for more details. +from collections.abc import Callable + from gi.repository import Gdk as gdk from gi.repository import Gtk as gtk @@ -24,18 +26,18 @@ class MyDialog: - def startWaiting(self): + def startWaiting(self) -> None: self.queue_draw() self.vbox.set_sensitive(False) self.get_window().set_cursor(gdk.Cursor.new(gdk.CursorType.WATCH)) while gtk.events_pending(): gtk.main_iteration_do(False) - def endWaiting(self): + def endWaiting(self) -> None: self.get_window().set_cursor(gdk.Cursor.new(gdk.CursorType.LEFT_PTR)) self.vbox.set_sensitive(True) - def waitingDo(self, func, *args, **kwargs): + def waitingDo(self, func: Callable, *args, **kwargs) -> None: # noqa: ANN002 self.startWaiting() try: func(*args, **kwargs) diff --git a/pyglossary/ui/gtk3_utils/resize_button.py b/pyglossary/ui/gtk3_utils/resize_button.py index 5a71448f4..cdefca750 100644 --- a/pyglossary/ui/gtk3_utils/resize_button.py +++ b/pyglossary/ui/gtk3_utils/resize_button.py @@ -19,6 +19,8 @@ from __future__ import annotations +from typing import Any + from . import gdk, gtk from .utils import imageFromFile @@ -26,7 +28,11 @@ class ResizeButton(gtk.EventBox): - def __init__(self, win, edge=gdk.WindowEdge.SOUTH_EAST) -> None: + def __init__( + self, + win: gtk.Window, + edge: gdk.WindowEdge = gdk.WindowEdge.SOUTH_EAST, + ) -> None: gtk.EventBox.__init__(self) self.win = win self.edge = edge @@ -35,7 +41,7 @@ def __init__(self, win, edge=gdk.WindowEdge.SOUTH_EAST) -> None: self.add(self.image) self.connect("button-press-event", self.buttonPress) - def buttonPress(self, _obj, gevent): + def buttonPress(self, _obj: Any, gevent: gdk.ButtonEvent) -> None: self.win.begin_resize_drag( self.edge, gevent.button, diff --git a/pyglossary/ui/gtk3_utils/utils.py b/pyglossary/ui/gtk3_utils/utils.py index 3bfddd728..85fb82c31 100644 --- a/pyglossary/ui/gtk3_utils/utils.py +++ b/pyglossary/ui/gtk3_utils/utils.py @@ -20,6 +20,7 @@ import logging from os.path import isabs, join +from typing import TYPE_CHECKING from gi.repository import Pango as pango @@ -27,6 +28,9 @@ from . import gdk, gtk +if TYPE_CHECKING: + from collections.abc import Callable + __all__ = [ "HBox", "VBox", @@ -41,25 +45,20 @@ log = logging.getLogger("pyglossary") -def VBox(**kwargs): +def VBox(**kwargs) -> gtk.Box: return gtk.Box(orientation=gtk.Orientation.VERTICAL, **kwargs) -def HBox(**kwargs): +def HBox(**kwargs) -> gtk.Box: return gtk.Box(orientation=gtk.Orientation.HORIZONTAL, **kwargs) -def set_tooltip(widget, text): - try: - widget.set_tooltip_text(text) # PyGTK 2.12 or above - except AttributeError: - try: - widget.set_tooltip(gtk.Tooltips(), text) - except Exception: - log.exception("") +# TODO: remove +def set_tooltip(widget, text) -> None: # noqa: ANN001 + widget.set_tooltip_text(text) -def imageFromFile(path): # the file must exist +def imageFromFile(path: str) -> gtk.Image: # the file must exist if not isabs(path): path = join(appResDir, path) im = gtk.Image() @@ -70,7 +69,7 @@ def imageFromFile(path): # the file must exist return im -def imageFromIconName(iconName: str, size: int, nonStock=False) -> gtk.Image: +def imageFromIconName(iconName: str, size: int, nonStock: bool = False) -> gtk.Image: # So gtk.Image.new_from_stock is deprecated # And the doc says we should use gtk.Image.new_from_icon_name # which does NOT have the same functionality! @@ -87,18 +86,20 @@ def imageFromIconName(iconName: str, size: int, nonStock=False) -> gtk.Image: return gtk.Image.new_from_icon_name(iconName, size) -def rgba_parse(colorStr): +def rgba_parse(colorStr: str) -> gdk.RGBA: rgba = gdk.RGBA() if not rgba.parse(colorStr): raise ValueError(f"bad color string {colorStr!r}") return rgba -def color_parse(colorStr): - return rgba_parse(colorStr).to_color() - - -def pack(box, child, expand=False, fill=False, padding=0): +def pack( + box: gtk.Box | gtk.CellLayout, + child: gtk.Widget | gtk.CellRenderer, + expand: bool = False, + fill: bool = False, + padding: int = 0, +) -> None: if isinstance(box, gtk.Box): box.pack_start(child, expand, fill, padding) elif isinstance(box, gtk.CellLayout): @@ -108,31 +109,30 @@ def pack(box, child, expand=False, fill=False, padding=0): def dialog_add_button( - dialog, - _iconName, - label, - resId, - onClicked=None, - tooltip="", -): + dialog: gtk.Dialog, + _iconName: str, # TODO: remove + label: str, + resId: int, + onClicked: Callable | None = None, + tooltip: str = "", +) -> None: b = dialog.add_button(label, resId) if onClicked: b.connect("clicked", onClicked) if tooltip: set_tooltip(b, tooltip) - return b def showMsg( # noqa: PLR0913 - msg, - iconName="", - parent=None, - transient_for=None, - title="", - borderWidth=10, - iconSize=gtk.IconSize.DIALOG, - selectable=False, -): + msg: str, + iconName: str = "", + parent: gtk.Widget | None = None, + transient_for: gtk.Widget | None = None, + title: str = "", + borderWidth: int = 10, + iconSize: gtk.IconSize = gtk.IconSize.DIALOG, + selectable: bool = False, +) -> None: win = gtk.Dialog( parent=parent, transient_for=transient_for, @@ -167,19 +167,19 @@ def showMsg( # noqa: PLR0913 win.destroy() -def showError(msg, **kwargs): +def showError(msg, **kwargs) -> None: # noqa: ANN001 # gtk-dialog-error is deprecated since version 3.10: # Use named icon “dialog-error”. showMsg(msg, iconName="gtk-dialog-error", **kwargs) -def showWarning(msg, **kwargs): +def showWarning(msg, **kwargs) -> None: # noqa: ANN001 # gtk-dialog-warning is deprecated since version 3.10: # Use named icon “dialog-warning”. showMsg(msg, iconName="gtk-dialog-warning", **kwargs) -def showInfo(msg, **kwargs): +def showInfo(msg, **kwargs) -> None: # noqa: ANN001 # gtk-dialog-info is deprecated since version 3.10: # Use named icon “dialog-information”. showMsg(msg, iconName="gtk-dialog-info", **kwargs) diff --git a/pyglossary/ui/gtk4_utils/about.py b/pyglossary/ui/gtk4_utils/about.py index 93e390850..38eec43f4 100644 --- a/pyglossary/ui/gtk4_utils/about.py +++ b/pyglossary/ui/gtk4_utils/about.py @@ -104,7 +104,7 @@ def newTabWidgetTextView( text: str, wrap: bool = False, justification: gtk.Justification | None = None, - ): + ) -> gtk.ScrolledWindow: tv = gtk.TextView() if wrap: tv.set_wrap_mode(gtk.WrapMode.WORD) @@ -128,7 +128,7 @@ def newTabLabelWidget( text: str, # wrap: bool = False, # justification: "gtk.Justification | None" = None, - ): + ) -> gtk.ScrolledWindow: box = VBox() # box.set_border_width(10) label = gtk.Label() @@ -151,5 +151,5 @@ def newTabLabelWidget( return swin @staticmethod - def newTabTitle(title: str, icon: str): + def newTabTitle(title: str, icon: str) -> AboutTabTitleBox: return AboutTabTitleBox(title, icon) diff --git a/pyglossary/ui/gtk4_utils/dialog.py b/pyglossary/ui/gtk4_utils/dialog.py index 381d5baee..2f4808d32 100644 --- a/pyglossary/ui/gtk4_utils/dialog.py +++ b/pyglossary/ui/gtk4_utils/dialog.py @@ -17,23 +17,25 @@ # GNU General Public License for more details. +from collections.abc import Callable + from gi.repository import Gdk as gdk from .utils import gtk_event_iteration_loop class MyDialog: - def startWaiting(self): + def startWaiting(self) -> None: self.queue_draw() self.vbox.set_sensitive(False) self.get_window().set_cursor(gdk.Cursor.new(gdk.CursorType.WATCH)) gtk_event_iteration_loop() - def endWaiting(self): + def endWaiting(self) -> None: self.get_window().set_cursor(gdk.Cursor.new(gdk.CursorType.LEFT_PTR)) self.vbox.set_sensitive(True) - def waitingDo(self, func, *args, **kwargs): + def waitingDo(self, func: Callable, *args, **kwargs) -> None: # noqa: ANN002 self.startWaiting() try: func(*args, **kwargs) diff --git a/pyglossary/ui/gtk4_utils/resize_button.py b/pyglossary/ui/gtk4_utils/resize_button.py index 39be183e7..7af1f12d6 100644 --- a/pyglossary/ui/gtk4_utils/resize_button.py +++ b/pyglossary/ui/gtk4_utils/resize_button.py @@ -24,7 +24,11 @@ class ResizeButton(gtk.Box): - def __init__(self, win, edge=gdk.SurfaceEdge.SOUTH_EAST) -> None: + def __init__( + self, + win: gtk.Window, + edge: gdk.SurfaceEdge = gdk.SurfaceEdge.SOUTH_EAST, + ) -> None: gtk.Box.__init__(self) self.win = win self.edge = edge @@ -35,7 +39,7 @@ def __init__(self, win, edge=gdk.SurfaceEdge.SOUTH_EAST) -> None: gesture.connect("pressed", self.buttonPress) self.add_controller(gesture) - def buttonPress(self, gesture, button, x, y): + def buttonPress(self, gesture: gtk.EventController, button, x, y) -> None: # noqa: ANN001 # Gesture is subclass of EventController pass # FIXME # self.win.begin_resize( diff --git a/pyglossary/ui/gtk4_utils/utils.py b/pyglossary/ui/gtk4_utils/utils.py index be29e2f96..8e8d39d2e 100644 --- a/pyglossary/ui/gtk4_utils/utils.py +++ b/pyglossary/ui/gtk4_utils/utils.py @@ -20,11 +20,15 @@ import logging from os.path import isabs, join +from typing import TYPE_CHECKING, Any from pyglossary.core import appResDir from . import gdk, glib, gtk +if TYPE_CHECKING: + from collections.abc import Callable + __all__ = [ "HBox", "VBox", @@ -41,7 +45,7 @@ log = logging.getLogger("pyglossary") -def gtk_window_iteration_loop(): +def gtk_window_iteration_loop() -> None: try: while gtk.Window.get_toplevels(): glib.MainContext.default().iteration(True) @@ -49,7 +53,7 @@ def gtk_window_iteration_loop(): pass -def gtk_event_iteration_loop(): +def gtk_event_iteration_loop() -> None: ctx = glib.MainContext.default() try: while ctx.pending(): @@ -58,25 +62,20 @@ def gtk_event_iteration_loop(): pass -def VBox(**kwargs): +def VBox(**kwargs) -> gtk.Box: return gtk.Box(orientation=gtk.Orientation.VERTICAL, **kwargs) -def HBox(**kwargs): +def HBox(**kwargs) -> gtk.Box: return gtk.Box(orientation=gtk.Orientation.HORIZONTAL, **kwargs) -def set_tooltip(widget, text): - try: - widget.set_tooltip_text(text) # PyGTK 2.12 or above - except AttributeError: - try: - widget.set_tooltip(gtk.Tooltips(), text) - except Exception: - log.exception("") +# TODO: remove +def set_tooltip(widget, text) -> None: # noqa: ANN001 + widget.set_tooltip_text(text) -def imageFromFile(path): # the file must exist +def imageFromFile(path: str) -> gtk.Image: # the file must exist if not isabs(path): path = join(appResDir, path) im = gtk.Image() @@ -87,7 +86,7 @@ def imageFromFile(path): # the file must exist return im -def imageFromIconName(iconName: str, size: int, nonStock=False) -> gtk.Image: +def imageFromIconName(iconName: str, size: int, nonStock: bool = False) -> gtk.Image: # So gtk.Image.new_from_stock is deprecated # And the doc says we should use gtk.Image.new_from_icon_name # which does NOT have the same functionality! @@ -104,18 +103,20 @@ def imageFromIconName(iconName: str, size: int, nonStock=False) -> gtk.Image: return gtk.Image.new_from_icon_name(iconName) -def rgba_parse(colorStr): +def rgba_parse(colorStr: str) -> gdk.RGBA: rgba = gdk.RGBA() if not rgba.parse(colorStr): raise ValueError(f"bad color string {colorStr!r}") return rgba -def color_parse(colorStr): - return rgba_parse(colorStr).to_color() - - -def pack(box, child, expand=False, fill=False, padding=0): # noqa: ARG001 +def pack( + box: gtk.Box | gtk.CellLayout, + child: gtk.Widget | gtk.CellRenderer, + expand: bool = False, + fill: bool = False, # noqa: ARG001 + padding: int = 0, +) -> None: # noqa: ARG001 if padding > 0: print(f"pack: padding={padding} ignored") if isinstance(box, gtk.Box): @@ -133,13 +134,13 @@ def pack(box, child, expand=False, fill=False, padding=0): # noqa: ARG001 def dialog_add_button( - dialog, - _iconName, - label, - resId, - onClicked=None, - tooltip="", -): + dialog: gtk.Dialog, + _iconName: str, # TODO: remove + label: str, + resId: int, + onClicked: Callable | None = None, + tooltip: str = "", +) -> None: button = gtk.Button( label=label, use_underline=True, @@ -154,19 +155,18 @@ def dialog_add_button( label.connect("clicked", onClicked) if tooltip: set_tooltip(label, tooltip) - return label def showMsg( # noqa: PLR0913 - msg, - iconName="", - parent=None, - transient_for=None, - title="", - borderWidth=10, # noqa: ARG001 - iconSize=gtk.IconSize.LARGE, - selectable=False, -): + msg: str, + iconName: str = "", + parent: gtk.Widget | None = None, + transient_for: gtk.Widget | None = None, + title: str = "", + borderWidth: int = 10, # noqa: ARG001 + iconSize: gtk.IconSize = gtk.IconSize.LARGE, + selectable: bool = False, +) -> None: win = gtk.Dialog( parent=parent, transient_for=transient_for, @@ -198,7 +198,7 @@ def showMsg( # noqa: PLR0913 gtk.ResponseType.OK, ) - def onResponse(_w, _response_id): + def onResponse(_w: Any, _response_id: int) -> None: win.destroy() win.connect("response", onResponse) @@ -207,19 +207,19 @@ def onResponse(_w, _response_id): win.show() -def showError(msg, **kwargs): +def showError(msg, **kwargs) -> None: # noqa: ANN001 # gtk-dialog-error is deprecated since version 3.10: # Use named icon “dialog-error”. showMsg(msg, iconName="gtk-dialog-error", **kwargs) -def showWarning(msg, **kwargs): +def showWarning(msg, **kwargs) -> None: # noqa: ANN001 # gtk-dialog-warning is deprecated since version 3.10: # Use named icon “dialog-warning”. showMsg(msg, iconName="gtk-dialog-warning", **kwargs) -def showInfo(msg, **kwargs): +def showInfo(msg, **kwargs) -> None: # noqa: ANN001 # gtk-dialog-info is deprecated since version 3.10: # Use named icon “dialog-information”. showMsg(msg, iconName="gtk-dialog-info", **kwargs) diff --git a/pyglossary/ui/pbar_legacy.py b/pyglossary/ui/pbar_legacy.py index e71e23004..a60170b0c 100644 --- a/pyglossary/ui/pbar_legacy.py +++ b/pyglossary/ui/pbar_legacy.py @@ -5,7 +5,7 @@ __all__ = ["createProgressBar"] -def createProgressBar(title: str): +def createProgressBar(title: str) -> pb.ProgressBar: rot = pb.RotatingMarker() pbar = pb.ProgressBar( maxval=1.0, diff --git a/pyglossary/ui/pbar_tqdm.py b/pyglossary/ui/pbar_tqdm.py index 3f559185b..782ef2df6 100644 --- a/pyglossary/ui/pbar_tqdm.py +++ b/pyglossary/ui/pbar_tqdm.py @@ -2,12 +2,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING, Any + from tqdm import tqdm +if TYPE_CHECKING: + from collections.abc import MutableMapping + __all__ = ["createProgressBar"] -def createProgressBar(title: str): +def createProgressBar(title: str) -> MyTqdm: return MyTqdm( total=1.0, desc=title, @@ -16,7 +21,7 @@ def createProgressBar(title: str): class MyTqdm(tqdm): @property - def format_dict(self): + def format_dict(self) -> MutableMapping[str, Any]: d = super().format_dict # return dict( # n=self.n, total=self.total, diff --git a/pyglossary/ui/tools/diff_glossary.py b/pyglossary/ui/tools/diff_glossary.py index b86b9ac9a..48c3698a7 100755 --- a/pyglossary/ui/tools/diff_glossary.py +++ b/pyglossary/ui/tools/diff_glossary.py @@ -84,13 +84,13 @@ def diffGlossary( # noqa: PLR0912, PLR0913 stdin=PIPE, ) - def write(msg: str): + def write(msg: str) -> None: proc.stdin.write(msg.encode("utf-8")) else: proc = None - def write(msg: str): + def write(msg: str) -> None: print(msg, end="") if header: @@ -246,7 +246,7 @@ def step() -> None: sys.stdout.flush() count += 1 - def run(): # noqa: PLR0912 + def run() -> None: # noqa: PLR0912 nonlocal index1, index2 while True: diff --git a/pyglossary/ui/ui_cmd_interactive.py b/pyglossary/ui/ui_cmd_interactive.py index 05250fb02..8ff6d7db9 100644 --- a/pyglossary/ui/ui_cmd_interactive.py +++ b/pyglossary/ui/ui_cmd_interactive.py @@ -103,7 +103,7 @@ def __init__( self.fmt = fmt self.value = value - def formatMessage(self): + def formatMessage(self) -> str: msg = self.fmt.format( check="[x]" if self.value else "[ ]", message=self.message, @@ -178,7 +178,11 @@ def _(_event: KeyPressEvent) -> None: } -def dataToPrettyJson(data, ensure_ascii=False, sort_keys=False): +def dataToPrettyJson( + data: dict[str, Any] | list[Any], + ensure_ascii: bool = False, + sort_keys: bool = False, +) -> str: return json.dumps( data, sort_keys=sort_keys, @@ -191,7 +195,7 @@ def prompt( message: ANSI | str, multiline: bool = False, **kwargs, -): +) -> str: if kwargs.get("default", "") is None: kwargs["default"] = "" text = promptLow(message=message, **kwargs) @@ -212,7 +216,7 @@ class MyPathCompleter(PathCompleter): def __init__( self, reading: bool, # noqa: ARG002 - fs_action_names=None, + fs_action_names: list[str] | None = None, **kwargs, ) -> None: PathCompleter.__init__( @@ -220,9 +224,7 @@ def __init__( file_filter=self.file_filter, **kwargs, ) - if fs_action_names is None: - fs_action_names = [] - self.fs_action_names = fs_action_names + self.fs_action_names = fs_action_names or [] @staticmethod def file_filter(_filename: str) -> bool: @@ -326,7 +328,7 @@ def __init__( } @staticmethod - def fs_pwd(args: list[str]): + def fs_pwd(args: list[str]) -> None: if args: print(f"extra arguments: {args}") print(os.getcwd()) @@ -361,7 +363,7 @@ def get_ls_l( details.append(f"-> {os.readlink(argPath)}") return " ".join(details) - def fs_ls(self, args: list[str]): + def fs_ls(self, args: list[str]) -> None: opts, args = self.ls_parser.parse_known_args(args=args) if opts.help: @@ -408,7 +410,7 @@ def fs_ls(self, args: list[str]): ) @staticmethod - def fs_cd_parent(args: list[str]): + def fs_cd_parent(args: list[str]) -> None: if args: log.error("This command does not take arguments") return @@ -417,7 +419,7 @@ def fs_cd_parent(args: list[str]): print(f"Changed current directory to: {newDir}") @staticmethod - def fs_cd(args: list[str]): + def fs_cd(args: list[str]) -> None: if len(args) != 1: log.error("This command takes exactly one argument") return @@ -427,7 +429,12 @@ def fs_cd(args: list[str]): os.chdir(newDir) print(f"Changed current directory to: {newDir}") - def formatPromptMsg(self, level, msg, colon=":"): + def formatPromptMsg( + self, + level: int, + msg: str, + colon: str = ":", + ) -> tuple[str, bool]: indent_ = self.promptIndentStr * level if core.noColor: @@ -441,13 +448,13 @@ def formatPromptMsg(self, level, msg, colon=":"): return f"{indent_} {msg}{colon} ", True - def prompt(self, level, msg, colon=":", **kwargs): - msg, colored = self.formatPromptMsg(level, msg, colon) + def prompt(self, level: int, msg: str, colon: str = ":", **kwargs) -> str: + msg2, colored = self.formatPromptMsg(level, msg, colon) if colored: - msg = ANSI(msg) - return prompt(msg, **kwargs) + msg2 = ANSI(msg) + return prompt(msg2, **kwargs) - def checkbox_prompt(self, level, msg, colon=":", **kwargs): + def checkbox_prompt(self, level: int, msg: str, colon: str = ":", **kwargs) -> bool: # FIXME: colors are not working, they are being escaped msg = f"{self.promptIndentStr * level} {msg}{colon} " # msg, colored = self.formatPromptMsg(level, msg, colon) @@ -459,7 +466,7 @@ def askFile( histName: str, varName: str, reading: bool, - ): + ) -> str: from shlex import split as shlex_split history = AbsolutePathHistory(join(histDir, histName)) @@ -500,7 +507,7 @@ def askFile( return filename raise ValueError(f"{kind} is not given") - def askInputFile(self): + def askInputFile(self) -> str: return self.askFile( "Input file", "filename-input", @@ -508,7 +515,7 @@ def askInputFile(self): True, ) - def askOutputFile(self): + def askOutputFile(self) -> str: return self.askFile( "Output file", "filename-output", @@ -577,20 +584,20 @@ def askOutputFormat(self) -> str: return plugin.name raise ValueError("output format is not given") - def finish(self): + def finish(self) -> None: pass # TODO: how to handle \r and \n in NewlineOption.values? @staticmethod - def getOptionValueSuggestValues(option: Option): + def getOptionValueSuggestValues(option: Option) -> list[str] | None: if option.values: return [str(x) for x in option.values] if option.typ == "bool": return ["True", "False"] return None - def getOptionValueCompleter(self, option: Option): + def getOptionValueCompleter(self, option: Option) -> WordCompleter | None: values = self.getOptionValueSuggestValues(option) if values: return WordCompleter( @@ -602,7 +609,7 @@ def getOptionValueCompleter(self, option: Option): return None # PLR0912 Too many branches (15 > 12) - def askReadOptions(self): # noqa: PLR0912 + def askReadOptions(self) -> None: # noqa: PLR0912 options = Glossary.formatsReadOptions.get(self._inputFormat) if options is None: log.error(f"internal error: invalid format {self._inputFormat!r}") @@ -678,7 +685,7 @@ def askReadOptions(self): # noqa: PLR0912 break # PLR0912 Too many branches (15 > 12) - def askWriteOptions(self): # noqa: PLR0912 + def askWriteOptions(self) -> None: # noqa: PLR0912 options = Glossary.formatsWriteOptions.get(self._outputFormat) if options is None: log.error(f"internal error: invalid format {self._outputFormat!r}") @@ -753,13 +760,13 @@ def askWriteOptions(self): # noqa: PLR0912 self._writeOptions[optName] = valueNew break - def resetReadOptions(self): + def resetReadOptions(self) -> None: self._readOptions = {} - def resetWriteOptions(self): + def resetWriteOptions(self) -> None: self._writeOptions = {} - def askConfigValue(self, configKey, option): + def askConfigValue(self, configKey: str, option: Option) -> str: default = self.config.get(configKey, "") if option.typ == "bool": return str( @@ -779,7 +786,7 @@ def askConfigValue(self, configKey, option): completer=self.getOptionValueCompleter(option), ) - def askConfig(self): + def askConfig(self) -> None: configKeys = sorted(self.configDefDict) history = FileHistory(join(histDir, "config-key")) auto_suggest = AutoSuggestFromHistory() @@ -826,28 +833,28 @@ def askConfig(self): self.config[configKey] = valueNew break - def showOptions(self): + def showOptions(self) -> None: print(f"readOptions = {self._readOptions}") print(f"writeOptions = {self._writeOptions}") print(f"convertOptions = {self._convertOptions}") print(f"config = {self.config}") print() - def setIndirect(self): + def setIndirect(self) -> None: self._convertOptions["direct"] = False self._convertOptions["sqlite"] = None print("Switched to indirect mode") - def setSQLite(self): + def setSQLite(self) -> None: self._convertOptions["direct"] = None self._convertOptions["sqlite"] = True print("Switched to SQLite mode") - def setNoProgressbar(self): + def setNoProgressbar(self) -> None: self._glossarySetAttrs["progressbar"] = False print("Disabled progress bar") - def setSort(self): + def setSort(self) -> None: try: value = self.checkbox_prompt( 2, @@ -858,7 +865,7 @@ def setSort(self): return self._convertOptions["sort"] = value - def setSortKey(self): + def setSortKey(self) -> None: completer = WordCompleter( [_sk.name for _sk in namedSortKeyList], ignore_case=False, @@ -945,7 +952,7 @@ def getRunKeywordArgs(self) -> dict: "glossarySetAttrs": self._glossarySetAttrs, } - def checkInputFormat(self, forceAsk: bool = False): + def checkInputFormat(self, forceAsk: bool = False) -> None: if not forceAsk: try: inputArgs = Glossary.detectInputFormat(self._inputFilename) @@ -957,7 +964,7 @@ def checkInputFormat(self, forceAsk: bool = False): return self._inputFormat = self.askInputFormat() - def checkOutputFormat(self, forceAsk: bool = False): + def checkOutputFormat(self, forceAsk: bool = False) -> None: if not forceAsk: try: outputArgs = Glossary.detectOutputFormat( @@ -971,17 +978,17 @@ def checkOutputFormat(self, forceAsk: bool = False): return self._outputFormat = self.askOutputFormat() - def askFormats(self): + def askFormats(self) -> None: self.checkInputFormat(forceAsk=True) self.checkOutputFormat(forceAsk=True) - def askInputOutputAgain(self): + def askInputOutputAgain(self) -> None: self.askInputFile() self.checkInputFormat(forceAsk=True) self.askOutputFile() self.checkOutputFormat(forceAsk=True) - def printNonInteractiveCommand(self): # noqa: PLR0912 + def printNonInteractiveCommand(self) -> None: # noqa: PLR0912 cmd = [ ui_cmd.COMMAND, self._inputFilename, @@ -1063,7 +1070,7 @@ def printNonInteractiveCommand(self): # noqa: PLR0912 # shlex.join is added in Python 3.8 print(shlex.join(cmd)) - def setConfigAttrs(self): + def setConfigAttrs(self) -> None: config = self.config self.promptIndentStr = config.get("cmdi.prompt.indent.str", ">") self.promptIndentColor = config.get("cmdi.prompt.indent.color", 2) @@ -1071,7 +1078,7 @@ def setConfigAttrs(self): self.msgColor = config.get("cmdi.msg.color", -1) # PLR0912 Too many branches (19 > 12) - def main(self, again=False): # noqa: PLR0912 + def main(self, again: bool = False) -> None: # noqa: PLR0912 if again or not self._inputFilename: try: self.askInputFile() diff --git a/pyglossary/ui/ui_gtk.py b/pyglossary/ui/ui_gtk.py index d7c965fc8..500372930 100644 --- a/pyglossary/ui/ui_gtk.py +++ b/pyglossary/ui/ui_gtk.py @@ -78,7 +78,7 @@ ] -def getMonitor(): +def getMonitor() -> gdk.Monitor | None: display = gdk.Display.get_default() monitor = display.get_monitor_at_point(1, 1) @@ -107,19 +107,11 @@ def getWorkAreaSize() -> tuple[int, int] | None: return rect.width, rect.height -def buffer_get_text(b): - return b.get_text( - b.get_start_iter(), - b.get_end_iter(), - True, - ) - - class FormatDialog(gtk.Dialog): def __init__( self, descList: list[str], - parent=None, + parent: gtk.Widget | None = None, **kwargs, ) -> None: gtk.Dialog.__init__(self, parent=parent, **kwargs) @@ -187,13 +179,13 @@ def __init__( self.resize(400, 400) self.connect("realize", self.onRealize) - def onRealize(self, _widget=None): + def onRealize(self, _widget: Any = None) -> None: if self.activeDesc: self.treev.grab_focus() else: self.entry.grab_focus() - def onEntryChange(self, entry): + def onEntryChange(self, entry: gtk.Entry) -> None: text = entry.get_text().strip() if not text: self.items = self.descList @@ -214,7 +206,7 @@ def onEntryChange(self, entry): self.items = items1 + items2 self.updateTree() - def setCursor(self, desc: str): + def setCursor(self, desc: str) -> None: model = self.treev.get_model() iter_ = model.iter_children(None) while iter_ is not None: @@ -225,7 +217,7 @@ def setCursor(self, desc: str): return iter_ = model.iter_next(iter_) - def updateTree(self): + def updateTree(self) -> None: model = self.treev.get_model() model.clear() for desc in self.items: @@ -242,7 +234,7 @@ def getActive(self) -> PluginProp | None: desc = model.get_value(iter_, 0) return pluginByDesc[desc] - def setActive(self, plugin): + def setActive(self, plugin: PluginProp) -> None: if plugin is None: self.activeDesc = "" return @@ -250,7 +242,12 @@ def setActive(self, plugin): self.activeDesc = desc self.setCursor(desc) - def rowActivated(self, treev, path, _col): + def rowActivated( + self, + treev: gtk.TreeView, + path: gtk.GtkTreePath, + _col: Any, + ) -> None: model = treev.get_model() iter_ = model.get_iter(path) desc = model.get_value(iter_, 0) @@ -264,7 +261,7 @@ class FormatButton(gtk.Button): noneLabel = "[Select Format]" dialogTitle = "Select Format" - def __init__(self, descList: list[str], parent=None) -> None: + def __init__(self, descList: list[str], parent: gtk.Widget | None = None) -> None: gtk.Button.__init__(self) self.set_label(self.noneLabel) ### @@ -274,10 +271,10 @@ def __init__(self, descList: list[str], parent=None) -> None: ### self.connect("clicked", self.onClick) - def onChanged(self, obj=None): + def onChanged(self, _obj: Any = None) -> None: pass - def onClick(self, _button=None): + def onClick(self, _button: Any = None) -> None: dialog = FormatDialog( descList=self.descList, parent=self._parent, @@ -295,13 +292,13 @@ def onClick(self, _button=None): self.set_label(self.noneLabel) self.onChanged() - def getActive(self): + def getActive(self) -> str: if self.activePlugin is None: return "" return self.activePlugin.name - def setActive(self, format_): - plugin = Glossary.plugins[format_] + def setActive(self, formatName: str) -> None: + plugin = Glossary.plugins[formatName] self.activePlugin = plugin self.set_label(plugin.description) self.onChanged() @@ -315,7 +312,7 @@ def __init__( formatName: str, options: list[str], optionsValues: dict[str, Any], - parent=None, + parent: gtk.Widget | None = None, ) -> None: gtk.Dialog.__init__(self, parent=parent) optionsProp = Glossary.plugins[formatName].optionsProp @@ -410,14 +407,14 @@ def __init__( pack(self.vbox, treev, 1, 1) self.vbox.show_all() - def enableToggled(self, cell, path): + def enableToggled(self, cell: gtk.CellRenderer, path: gtk.TreePath) -> None: # enable is column 0 model = self.treev.get_model() active = not cell.get_active() itr = model.get_iter(path) model.set_value(itr, 0, active) - def valueEdited(self, _cell, path, rawValue): + def valueEdited(self, _cell: Any, path: gtk.TreePath, rawValue: str) -> None: # value is column 3 model = self.treev.get_model() itr = model.get_iter(path) @@ -434,12 +431,12 @@ def valueEdited(self, _cell, path, rawValue): model.set_value(itr, self.valueCol, rawValue) model.set_value(itr, 0, enable) - def rowActivated(self, _treev, path, _col): + def rowActivated(self, _treev: Any, path: gtk.TreePath, _col: Any) -> bool: # forceMenu=True because we can not enter edit mode # if double-clicked on a cell other than Value return self.valueCellClicked(path, forceMenu=True) - def treeviewButtonPress(self, treev, gevent): + def treeviewButtonPress(self, treev: gtk.TreeView, gevent: gdk.ButtonEvent) -> bool: if gevent.button != 1: return False pos_t = treev.get_path_at_pos(int(gevent.x), int(gevent.y)) @@ -453,14 +450,14 @@ def treeviewButtonPress(self, treev, gevent): return self.valueCellClicked(path) return False - def valueItemActivate(self, item: gtk.MenuItem, itr: gtk.TreeIter): + def valueItemActivate(self, item: gtk.MenuItem, itr: gtk.TreeIter) -> None: # value is column 3 value = item.get_label() model = self.treev.get_model() model.set_value(itr, self.valueCol, value) model.set_value(itr, 0, True) # enable it - def valueCustomOpenDialog(self, itr: gtk.TreeIter, optName: str): + def valueCustomOpenDialog(self, itr: gtk.TreeIter, optName: str) -> None: model = self.treev.get_model() prop = self.optionsProp[optName] currentValue = model.get_value(itr, self.valueCol) @@ -498,12 +495,12 @@ def valueItemCustomActivate( self, _item: gtk.MenuItem, itr: gtk.TreeIter, - ): + ) -> None: model = self.treev.get_model() optName = model.get_value(itr, 1) self.valueCustomOpenDialog(itr, optName) - def valueCellClicked(self, path, forceMenu=False) -> bool: + def valueCellClicked(self, path: gtk.TreePath, forceMenu: bool = False) -> bool: """ Returns True if event is handled, False if not handled (need to enter edit mode). @@ -601,17 +598,17 @@ def __init__(self, descList: list[str], parent=None) -> None: self.dependsButton.pkgNames = [] self.dependsButton.connect("clicked", self.dependsButtonClicked) - def setOptionsValues(self, optionsValues: dict[str, Any]): + def setOptionsValues(self, optionsValues: dict[str, Any]) -> None: self.optionsValues = optionsValues - def kind(self): + def kind(self) -> str: """Return 'r' or 'w'.""" raise NotImplementedError - def getActiveOptions(self): + def getActiveOptions(self) -> list[str] | None: raise NotImplementedError - def optionsButtonClicked(self, _button): + def optionsButtonClicked(self, _button) -> None: formatName = self.getActive() options = self.getActiveOptions() dialog = FormatOptionsDialog( @@ -627,7 +624,7 @@ def optionsButtonClicked(self, _button): self.optionsValues = dialog.getOptionsValues() dialog.destroy() - def dependsButtonClicked(self, button): + def dependsButtonClicked(self, button) -> None: formatName = self.getActive() pkgNames = button.pkgNames if not pkgNames: @@ -643,15 +640,14 @@ def dependsButtonClicked(self, button): ) self.onChanged(self) - def onChanged(self, _obj=None): + def onChanged(self, _obj=None) -> None: name = self.getActive() if not name: self.optionsButton.set_visible(False) return self.optionsValues.clear() - options = self.getActiveOptions() - self.optionsButton.set_visible(bool(options)) + self.optionsButton.set_visible(bool(self.getActiveOptions())) kind = self.kind() plugin = Glossary.plugins[name] @@ -673,11 +669,11 @@ class InputFormatBox(FormatBox): def __init__(self, **kwargs) -> None: FormatBox.__init__(self, readDesc, **kwargs) - def kind(self): + def kind(self) -> str: """Return 'r' or 'w'.""" return "r" - def getActiveOptions(self): + def getActiveOptions(self) -> list[str] | None: formatName = self.getActive() if not formatName: return None @@ -690,11 +686,11 @@ class OutputFormatBox(FormatBox): def __init__(self, **kwargs) -> None: FormatBox.__init__(self, writeDesc, **kwargs) - def kind(self): + def kind(self) -> str: """Return 'r' or 'w'.""" return "w" - def getActiveOptions(self): + def getActiveOptions(self) -> list[str] | None: return list(Glossary.formatsWriteOptions[self.getActive()]) @@ -721,7 +717,7 @@ def setColor(self, levelname: str, rgba: gdk.RGBA) -> None: self.getTag(levelname).set_property("foreground-rgba", rgba) # foreground-gdk is deprecated since Gtk 3.4 - def emit(self, record): + def emit(self, record) -> None: msg = "" if record.getMessage(): msg = self.format(record) @@ -788,7 +784,7 @@ def __init__( self.connect("clicked", self.onClick) - def onClick(self, _widget): + def onClick(self, _widget) -> None: fcd = gtk.FileChooserNative( transient_for=( self.get_root() if hasattr(self, "get_root") else self.get_toplevel() @@ -864,13 +860,13 @@ def __init__(self, ui) -> None: ### self.show_all() - def onSortCheckClicked(self, check): + def onSortCheckClicked(self, check) -> None: sort = check.get_active() self.sortKeyCombo.set_sensitive(sort) self.encodingHBox.set_sensitive(sort) self.localeHBox.set_sensitive(sort) - def updateWidgets(self): + def updateWidgets(self) -> None: convertOptions = self.ui.convertOptions sort = convertOptions.get("sort") self.sortCheck.set_active(sort) @@ -889,7 +885,7 @@ def updateWidgets(self): self.encodingCheck.set_active(True) self.encodingEntry.set_text(convertOptions["sortEncoding"]) - def applyChanges(self): + def applyChanges(self) -> None: convertOptions = self.ui.convertOptions sort = self.sortCheck.get_active() if not sort: @@ -911,11 +907,11 @@ def applyChanges(self): class GeneralOptionsDialog(gtk.Dialog): - def onDeleteEvent(self, _widget, _event): + def onDeleteEvent(self, _widget, _event) -> bool: self.hide() return True - def onResponse(self, _widget, _event): + def onResponse(self, _widget, _event) -> bool: self.applyChanges() self.hide() return True @@ -983,7 +979,7 @@ def getSQLite(self) -> bool: return sqlite return self.ui.config.get("auto_sqlite", True) - def updateWidgets(self): + def updateWidgets(self) -> None: config = self.ui.config self.sortOptionsBox.updateWidgets() self.sqliteCheck.set_active(self.getSQLite()) @@ -991,7 +987,7 @@ def updateWidgets(self): default = self.configParams[param] check.set_active(config.get(param, default)) - def applyChanges(self): + def applyChanges(self) -> None: # print("applyChanges") self.sortOptionsBox.applyChanges() @@ -1011,14 +1007,14 @@ def __init__(self, ui) -> None: self.connect("clicked", self.onClick) self.dialog = None - def onClick(self, _widget): + def onClick(self, _widget) -> None: if self.dialog is None: self.dialog = GeneralOptionsDialog(self.ui) self.dialog.present() class UI(gtk.Dialog, MyDialog, UIBase): - def status(self, msg): + def status(self, msg) -> None: # try: # _id = self.statusMsgDict[msg] # except KeyError: @@ -1386,7 +1382,7 @@ def run( # noqa: PLR0913 writeOptions: dict[str, Any] | None = None, convertOptions: dict[str, Any] | None = None, glossarySetAttrs: dict[str, Any] | None = None, - ): + ) -> None: self.config = config if inputFilename: @@ -1417,7 +1413,7 @@ def run( # noqa: PLR0913 gtk.Dialog.present(self) gtk.main() - def onDeleteEvent(self, _widget, _event): + def onDeleteEvent(self, _widget, _event) -> None: self.destroy() # gtk.main_quit() # if called while converting, main_quit does not exit program, @@ -1425,10 +1421,10 @@ def onDeleteEvent(self, _widget, _event): # and makes you close the terminal or force kill the process gtk.main_quit() - def consoleClearButtonClicked(self, _widget=None): + def consoleClearButtonClicked(self, _widget=None) -> None: self.convertConsoleTextview.get_buffer().set_text("") - def verbosityComboChanged(self, _widget=None): + def verbosityComboChanged(self, _widget=None) -> None: verbosity = self.verbosityCombo.get_active() # or int(self.verbosityCombo.get_active_text()) log.setVerbosity(verbosity) @@ -1494,7 +1490,7 @@ def convertClicked(self, _widget=None): return True - def convertInputEntryChanged(self, _widget=None): + def convertInputEntryChanged(self, _widget=None) -> None: inPath = self.convertInputEntry.get_text() inFormat = self.convertInputFormatCombo.getActive() if inPath.startswith("file://"): @@ -1514,7 +1510,7 @@ def convertInputEntryChanged(self, _widget=None): self.status("Select output file") - def convertOutputEntryChanged(self, _widget=None): + def convertOutputEntryChanged(self, _widget=None) -> None: outPath = self.convertOutputEntry.get_text() outFormat = self.convertOutputFormatCombo.getActive() if not outPath: @@ -1540,13 +1536,13 @@ def convertOutputEntryChanged(self, _widget=None): else: self.status("Select output format") - def reverseLoad(self): + def reverseLoad(self) -> None: pass - def reverseStartLoop(self): + def reverseStartLoop(self) -> None: pass - def reverseStart(self): + def reverseStart(self) -> None: if not self.reverseLoad(): return ### @@ -1558,10 +1554,10 @@ def reverseStart(self): self.reverseResumeButton.set_sensitive(False) self.reverseStopButton.set_sensitive(True) - def reverseStartClicked(self, _widget=None): + def reverseStartClicked(self, _widget=None) -> None: self.waitingDo(self.reverseStart) - def reversePause(self): + def reversePause(self) -> None: self.reverseStatus = "pause" ### self.reverseStartButton.set_sensitive(False) @@ -1569,10 +1565,10 @@ def reversePause(self): self.reverseResumeButton.set_sensitive(True) self.reverseStopButton.set_sensitive(True) - def reversePauseClicked(self, _widget=None): + def reversePauseClicked(self, _widget=None) -> None: self.waitingDo(self.reversePause) - def reverseResume(self): + def reverseResume(self) -> None: self.reverseStatus = "doing" ### self.reverseStartButton.set_sensitive(False) @@ -1580,10 +1576,10 @@ def reverseResume(self): self.reverseResumeButton.set_sensitive(False) self.reverseStopButton.set_sensitive(True) - def reverseResumeClicked(self, _widget=None): + def reverseResumeClicked(self, _widget=None) -> None: self.waitingDo(self.reverseResume) - def reverseStop(self): + def reverseStop(self) -> None: self.reverseStatus = "stop" ### self.reverseStartButton.set_sensitive(True) @@ -1591,10 +1587,10 @@ def reverseStop(self): self.reverseResumeButton.set_sensitive(False) self.reverseStopButton.set_sensitive(False) - def reverseStopClicked(self, _widget=None): + def reverseStopClicked(self, _widget=None) -> None: self.waitingDo(self.reverseStop) - def reverseInputEntryChanged(self, _widget=None): + def reverseInputEntryChanged(self, _widget=None) -> None: inPath = self.reverseInputEntry.get_text() if inPath.startswith("file://"): inPath = urlToPath(inPath) @@ -1612,13 +1608,13 @@ def reverseInputEntryChanged(self, _widget=None): inFormat = inputArgs[1] self.reverseInputFormatCombo.setActive(inFormat) - def reverseOutputEntryChanged(self, widget=None): + def reverseOutputEntryChanged(self, widget=None) -> None: pass - def progressInit(self, title): + def progressInit(self, title) -> None: self.progressTitle = title - def progress(self, ratio, text=None): + def progress(self, ratio, text=None) -> None: if not text: text = "%" + str(int(ratio * 100)) text += " - " + self.progressTitle diff --git a/pyglossary/ui/ui_gtk4.py b/pyglossary/ui/ui_gtk4.py index a443aa8f4..5adf4f350 100644 --- a/pyglossary/ui/ui_gtk4.py +++ b/pyglossary/ui/ui_gtk4.py @@ -59,6 +59,8 @@ ) if TYPE_CHECKING: + from collections.abc import Callable + from pyglossary.plugin_prop import PluginProp # from gi.repository import GdkPixbuf @@ -78,7 +80,7 @@ ] -def getWorkAreaSize(_w): +def getWorkAreaSize(_w: Any) -> tuple[int, int]: display = gdk.Display.get_default() # monitor = display.get_monitor_at_surface(w.get_surface()) # if monitor is None: @@ -87,14 +89,6 @@ def getWorkAreaSize(_w): return rect.width, rect.height -def buffer_get_text(b): - return b.get_text( - b.get_start_iter(), - b.get_end_iter(), - True, - ) - - # GTK 4 has removed the GtkContainer::border-width property # (together with the rest of GtkContainer). # Use other means to influence the spacing of your containers, @@ -106,7 +100,7 @@ class FormatDialog(gtk.Dialog): def __init__( self, descList: list[str], - parent=None, + parent: gtk.Widget | None = None, **kwargs, ) -> None: gtk.Dialog.__init__(self, transient_for=parent, **kwargs) @@ -176,13 +170,13 @@ def __init__( self.updateTree() self.connect("realize", self.onRealize) - def onRealize(self, _widget=None): + def onRealize(self, _widget: Any = None) -> None: if self.activeDesc: self.treev.grab_focus() else: self.entry.grab_focus() - def onEntryChange(self, entry): + def onEntryChange(self, entry: gtk.Entry) -> None: text = entry.get_text().strip() if not text: self.items = self.descList @@ -203,7 +197,7 @@ def onEntryChange(self, entry): self.items = items1 + items2 self.updateTree() - def setCursor(self, desc: str): + def setCursor(self, desc: str) -> None: model = self.treev.get_model() iter_ = model.iter_children(None) while iter_ is not None: @@ -214,7 +208,7 @@ def setCursor(self, desc: str): return iter_ = model.iter_next(iter_) - def updateTree(self): + def updateTree(self) -> None: model = self.treev.get_model() model.clear() for desc in self.items: @@ -231,7 +225,7 @@ def getActive(self) -> PluginProp | None: desc = model.get_value(iter_, 0) return pluginByDesc[desc] - def setActive(self, plugin): + def setActive(self, plugin: PluginProp) -> None: if plugin is None: self.activeDesc = "" return @@ -239,7 +233,12 @@ def setActive(self, plugin): self.activeDesc = desc self.setCursor(desc) - def rowActivated(self, treev, path, _col): + def rowActivated( + self, + treev: gtk.TreeView, + path: gtk.GtkTreePath, + _col: Any, + ) -> None: model = treev.get_model() iter_ = model.get_iter(path) desc = model.get_value(iter_, 0) @@ -253,7 +252,7 @@ class FormatButton(gtk.Button): noneLabel = "[Select Format]" dialogTitle = "Select Format" - def __init__(self, descList: list[str], parent=None) -> None: + def __init__(self, descList: list[str], parent: gtk.Widget | None = None) -> None: gtk.Button.__init__(self) self.set_label(self.noneLabel) ### @@ -263,10 +262,10 @@ def __init__(self, descList: list[str], parent=None) -> None: ### self.connect("clicked", self.onClick) - def onChanged(self, obj=None): + def onChanged(self, _obj: Any = None) -> None: pass - def onDialogResponse(self, dialog, response_id): + def onDialogResponse(self, dialog: gtk.Dialog, response_id: int) -> None: print(f"onDialogResponse: {dialog}, {response_id}") if response_id != gtk.ResponseType.OK: return @@ -279,7 +278,7 @@ def onDialogResponse(self, dialog, response_id): self.set_label(self.noneLabel) self.onChanged() - def onClick(self, _button=None): + def onClick(self, _button: Any = None) -> None: dialog = FormatDialog( descList=self.descList, parent=self._parent, @@ -289,13 +288,13 @@ def onClick(self, _button=None): dialog.connect("response", self.onDialogResponse) dialog.present() - def getActive(self): + def getActive(self) -> str: if self.activePlugin is None: return "" return self.activePlugin.name - def setActive(self, format_): - plugin = Glossary.plugins[format_] + def setActive(self, formatName: str) -> None: + plugin = Glossary.plugins[formatName] self.activePlugin = plugin self.set_label(plugin.description) self.onChanged() @@ -306,7 +305,7 @@ class FormatOptionsDialog(gtk.Dialog): def __init__( self, - app, + app: gtk.Application, formatName: str, options: list[str], optionsValues: dict[str, Any], @@ -413,14 +412,14 @@ def __init__( pack(self.vbox, treev, 1, 1) self.vbox.show() - def enableToggled(self, cell, path): + def enableToggled(self, cell: gtk.CellRenderer, path: gtk.TreePath) -> None: # enable is column 0 model = self.treev.get_model() active = not cell.get_active() itr = model.get_iter(path) model.set_value(itr, 0, active) - def valueEdited(self, _cell, path, rawValue): + def valueEdited(self, _cell: Any, path: gtk.TreePath, rawValue: str) -> None: # value is column 3 model = self.treev.get_model() itr = model.get_iter(path) @@ -437,12 +436,12 @@ def valueEdited(self, _cell, path, rawValue): model.set_value(itr, self.valueCol, rawValue) model.set_value(itr, 0, enable) - def rowActivated(self, _treev, path, _col): + def rowActivated(self, _treev: Any, path: gtk.TreePath, _col: Any) -> bool: # forceMenu=True because we can not enter edit mode # if double-clicked on a cell other than Value return self.valueCellClicked(path, forceMenu=True) - def treeviewButtonPress(self, _gesture, _n_press, x, y): + def treeviewButtonPress(self, _gesture: Any, _n_press: Any, x: int, y: int) -> bool: # if gevent.button != 1: # return False pos_t = self.treev.get_path_at_pos(int(x), int(y)) @@ -456,14 +455,14 @@ def treeviewButtonPress(self, _gesture, _n_press, x, y): return self.valueCellClicked(path) return False - def valueItemActivate(self, item: gio.MenuItem, itr: gtk.TreeIter): + def valueItemActivate(self, item: gio.MenuItem, itr: gtk.TreeIter) -> None: # value is column 3 value = item.get_label() model = self.treev.get_model() model.set_value(itr, self.valueCol, value) model.set_value(itr, 0, True) # enable it - def valueCustomOpenDialog(self, itr: gtk.TreeIter, optName: str): + def valueCustomOpenDialog(self, itr: gtk.TreeIter, optName: str) -> None: model = self.treev.get_model() prop = self.optionsProp[optName] currentValue = model.get_value(itr, self.valueCol) @@ -494,7 +493,12 @@ def valueCustomOpenDialog(self, itr: gtk.TreeIter, optName: str): dialog.connect("response", self.valueCustomDialogResponse, entry) dialog.present() - def valueCustomDialogResponse(self, _dialog, response_id, entry): + def valueCustomDialogResponse( + self, + _dialog: Any, + response_id: int, + entry: gtk.Entry, + ) -> None: if response_id != gtk.ResponseType.OK: return model = self.treev.get_model() @@ -508,12 +512,18 @@ def valueItemCustomActivate( self, _item: gtk.MenuItem, itr: gtk.TreeIter, - ): + ) -> None: model = self.treev.get_model() optName = model.get_value(itr, 1) self.valueCustomOpenDialog(itr, optName) - def addAction(self, path, name, callback, *args) -> str: + def addAction( + self, + path: gtk.TreePath, + name: str, + callback: Callable, + *args # noqa: ANN002 + ) -> str: actionId = self.formatName + "_" + str(path[0]) + "_" + name if actionId not in self.actionIds: action = gio.SimpleAction(name=actionId) @@ -522,7 +532,7 @@ def addAction(self, path, name, callback, *args) -> str: return "app." + actionId - def valueCellClicked(self, path, forceMenu=False) -> bool: + def valueCellClicked(self, path: gtk.TreePath, forceMenu: bool = False) -> bool: """ Returns True if event is handled, False if not handled (need to enter edit mode). @@ -608,7 +618,7 @@ def valueCellClicked(self, path, forceMenu=False) -> bool: menu.popup() return True - def getOptionsValues(self): + def getOptionsValues(self) -> dict[str, Any]: model = self.treev.get_model() optionsValues: dict[str, Any] = {} for row in model: @@ -626,7 +636,12 @@ def getOptionsValues(self): class FormatBox(FormatButton): - def __init__(self, app, descList: list[str], parent=None) -> None: + def __init__( + self, + app: gtk.Application, + descList: list[str], + parent: gtk.Widget | None = None, + ) -> None: self.app = app FormatButton.__init__(self, descList, parent=parent) @@ -644,19 +659,21 @@ def __init__(self, app, descList: list[str], parent=None) -> None: self.dependsButton.pkgNames = [] self.dependsButton.connect("clicked", self.dependsButtonClicked) - def setOptionsValues(self, optionsValues: dict[str, Any]): + def setOptionsValues(self, optionsValues: dict[str, Any]) -> None: self.optionsValues = optionsValues - def kind(self): + def kind(self) -> str: """Return 'r' or 'w'.""" raise NotImplementedError - def getActiveOptions(self): + def getActiveOptions(self) -> list[str] | None: raise NotImplementedError - def optionsButtonClicked(self, _button): + def optionsButtonClicked(self, _button: Any) -> None: formatName = self.getActive() options = self.getActiveOptions() + if options is None: + return dialog = FormatOptionsDialog( self.app, formatName, @@ -668,12 +685,16 @@ def optionsButtonClicked(self, _button): dialog.connect("response", self.optionsDialogResponse) dialog.present() - def optionsDialogResponse(self, dialog, response_id): + def optionsDialogResponse( + self, + dialog: FormatOptionsDialog, + response_id: gtk.ResponseType, + ) -> None: if response_id == gtk.ResponseType.OK: self.optionsValues = dialog.getOptionsValues() dialog.destroy() - def dependsButtonClicked(self, button): + def dependsButtonClicked(self, button: gtk.Button) -> None: formatName = self.getActive() pkgNames = button.pkgNames if not pkgNames: @@ -689,15 +710,14 @@ def dependsButtonClicked(self, button): ) self.onChanged(self) - def onChanged(self, _obj=None): + def onChanged(self, _obj: Any = None) -> None: name = self.getActive() if not name: self.optionsButton.set_visible(False) return self.optionsValues.clear() - options = self.getActiveOptions() - self.optionsButton.set_visible(bool(options)) + self.optionsButton.set_visible(bool(self.getActiveOptions())) kind = self.kind() plugin = Glossary.plugins[name] @@ -716,14 +736,14 @@ def onChanged(self, _obj=None): class InputFormatBox(FormatBox): dialogTitle = "Select Input Format" - def __init__(self, app, **kwargs) -> None: + def __init__(self, app: gtk.Application, **kwargs) -> None: FormatBox.__init__(self, app, readDesc, **kwargs) - def kind(self): + def kind(self) -> str: """Return 'r' or 'w'.""" return "r" - def getActiveOptions(self): + def getActiveOptions(self) -> list[str] | None: formatName = self.getActive() if not formatName: return None @@ -733,26 +753,30 @@ def getActiveOptions(self): class OutputFormatBox(FormatBox): dialogTitle = "Select Output Format" - def __init__(self, app, **kwargs) -> None: + def __init__(self, app: gtk.Application, **kwargs) -> None: FormatBox.__init__(self, app, writeDesc, **kwargs) - def kind(self): + def kind(self) -> str: """Return 'r' or 'w'.""" return "w" - def getActiveOptions(self): + def getActiveOptions(self) -> list[str] | None: return list(Glossary.formatsWriteOptions[self.getActive()]) class GtkTextviewLogHandler(logging.Handler): - def __init__(self, mainWin, treeview_dict) -> None: + def __init__( + self, + mainWin: gtk.Window, + textview_dict: dict[str, gtk.TextView], + ) -> None: logging.Handler.__init__(self) self.mainWin = mainWin self.buffers = {} for levelNameCap in log.levelNamesCap[:-1]: levelName = levelNameCap.upper() - textview = treeview_dict[levelName] + textview = textview_dict[levelName] buff = textview.get_buffer() tag = gtk.TextTag.new(levelName) @@ -760,14 +784,14 @@ def __init__(self, mainWin, treeview_dict) -> None: self.buffers[levelName] = buff - def getTag(self, levelname): + def getTag(self, levelname: str) -> gtk.TextTag: return self.buffers[levelname].get_tag_table().lookup(levelname) def setColor(self, levelname: str, rgba: gdk.RGBA) -> None: self.getTag(levelname).set_property("foreground-rgba", rgba) # foreground-gdk is deprecated since Gtk 3.4 - def emit(self, record): + def emit(self, record: logging.LogRecord) -> None: msg = "" if record.getMessage(): msg = self.format(record) @@ -795,7 +819,7 @@ def emit(self, record): class GtkSingleTextviewLogHandler(GtkTextviewLogHandler): - def __init__(self, ui, textview) -> None: + def __init__(self, ui: UI, textview: gtk.TextView) -> None: GtkTextviewLogHandler.__init__( self, ui, @@ -813,10 +837,10 @@ def __init__(self, ui, textview) -> None: class BrowseButton(gtk.Button): def __init__( self, - setFilePathFunc, - label="Browse", - actionSave=False, - title="Select File", + setFilePathFunc: Callable[[str], None], + label: str = "Browse", + actionSave: bool = False, + title: str = "Select File", ) -> None: gtk.Button.__init__(self) @@ -833,14 +857,14 @@ def __init__( self.connect("clicked", self.onClick) - def onResponse(self, fcd, response_id): + def onResponse(self, fcd, response_id) -> None: if response_id == gtk.ResponseType.OK: gfile = fcd.get_file() if gfile is not None: self.setFilePathFunc(gfile.get_path()) fcd.destroy() - def onClick(self, _widget): + def onClick(self, _widget) -> None: fcd = gtk.FileChooserNative( transient_for=self.get_root(), action=gtk.FileChooserAction.SAVE @@ -901,12 +925,12 @@ def __init__(self, mainWin) -> None: ### self.show() - def onSortCheckToggled(self, *_args): + def onSortCheckToggled(self, *_args) -> None: sort = self.sortCheck.get_active() self.sortKeyCombo.set_sensitive(sort) self.encodingHBox.set_sensitive(sort) - def updateWidgets(self): + def updateWidgets(self) -> None: convertOptions = self.mainWin.convertOptions sort = convertOptions.get("sort") self.sortCheck.set_active(sort) @@ -920,7 +944,7 @@ def updateWidgets(self): sortEncoding = convertOptions.get("sortEncoding", "utf-8") self.encodingEntry.set_text(sortEncoding) - def applyChanges(self): + def applyChanges(self) -> None: convertOptions = self.mainWin.convertOptions sort = self.sortCheck.get_active() if not sort: @@ -937,11 +961,11 @@ def applyChanges(self): class GeneralOptionsDialog(gtk.Dialog): - def onCloseRequest(self, _widget): + def onCloseRequest(self, _widget) -> bool: self.hide() return True - def onResponse(self, _widget, _event): + def onResponse(self, _widget, _event) -> bool: self.applyChanges() self.hide() return True @@ -1010,7 +1034,7 @@ def getSQLite(self) -> bool: return sqlite return self.mainWin.config.get("auto_sqlite", True) - def updateWidgets(self): + def updateWidgets(self) -> None: config = self.mainWin.config self.sortOptionsBox.updateWidgets() self.sqliteCheck.set_active(self.getSQLite()) @@ -1018,7 +1042,7 @@ def updateWidgets(self): default = self.configParams[param] check.set_active(config.get(param, default)) - def applyChanges(self): + def applyChanges(self) -> None: # print("applyChanges") self.sortOptionsBox.applyChanges() @@ -1038,7 +1062,7 @@ def __init__(self, mainWin) -> None: self.connect("clicked", self.onClick) self.dialog = None - def onClick(self, _widget): + def onClick(self, _widget) -> None: if self.dialog is None: self.dialog = GeneralOptionsDialog(self.mainWin) self.dialog.present() @@ -1081,7 +1105,7 @@ class MainWindow(gtk.Window): } """ - def status(self, msg): + def status(self, msg) -> None: # try: # _id = self.statusMsgDict[msg] # except KeyError: @@ -1462,7 +1486,7 @@ def run( # noqa: PLR0913 writeOptions: dict[str, Any] | None = None, convertOptions: dict[str, Any] | None = None, glossarySetAttrs: dict[str, Any] | None = None, - ): + ) -> None: self.config = config if inputFilename: @@ -1490,11 +1514,11 @@ def run( # noqa: PLR0913 self._glossarySetAttrs = glossarySetAttrs or {} self.present() - def exitApp(self): + def exitApp(self) -> None: self.destroy() # unlike Gtk3, no need for sys.exit or gtk.main_quit (which does not exist) - def onCloseRequest(self, _widget): + def onCloseRequest(self, _widget) -> None: self.exitApp() def onKeyPress( @@ -1503,17 +1527,17 @@ def onKeyPress( keyval: int, _keycode: int, _state: gdk.ModifierType, - ): + ) -> None: if keyval == gdk.KEY_Escape: self.exitApp() - def onButtonPress(self, gesture, _n_press, _x, _y): + def onButtonPress(self, gesture, _n_press, _x, _y) -> None: print(f"MainWindow.onButtonPress: {gesture}") - def consoleClearButtonClicked(self, _widget=None): + def consoleClearButtonClicked(self, _widget=None) -> None: self.convertConsoleTextview.get_buffer().set_text("") - def verbosityComboChanged(self, _widget=None): + def verbosityComboChanged(self, _widget=None) -> None: verbosity = self.verbosityCombo.get_active() # or int(self.verbosityCombo.get_active_text()) log.setVerbosity(verbosity) @@ -1578,7 +1602,7 @@ def convertClicked(self, _widget=None): return True - def convertInputEntryChanged(self, _widget=None): + def convertInputEntryChanged(self, _widget=None) -> None: inPath = self.convertInputEntry.get_text() inFormat = self.convertInputFormatCombo.getActive() if inPath.startswith("file://"): @@ -1598,7 +1622,7 @@ def convertInputEntryChanged(self, _widget=None): self.status("Select output file") - def convertOutputEntryChanged(self, _widget=None): + def convertOutputEntryChanged(self, _widget=None) -> None: outPath = self.convertOutputEntry.get_text() outFormat = self.convertOutputFormatCombo.getActive() if not outPath: @@ -1624,13 +1648,13 @@ def convertOutputEntryChanged(self, _widget=None): else: self.status("Select output format") - def reverseLoad(self): + def reverseLoad(self) -> None: pass - def reverseStartLoop(self): + def reverseStartLoop(self) -> None: pass - def reverseStart(self): + def reverseStart(self) -> None: if not self.reverseLoad(): return ### @@ -1642,10 +1666,10 @@ def reverseStart(self): self.reverseResumeButton.set_sensitive(False) self.reverseStopButton.set_sensitive(True) - def reverseStartClicked(self, _widget=None): + def reverseStartClicked(self, _widget=None) -> None: self.waitingDo(self.reverseStart) - def reversePause(self): + def reversePause(self) -> None: self.reverseStatus = "pause" ### self.reverseStartButton.set_sensitive(False) @@ -1653,10 +1677,10 @@ def reversePause(self): self.reverseResumeButton.set_sensitive(True) self.reverseStopButton.set_sensitive(True) - def reversePauseClicked(self, _widget=None): + def reversePauseClicked(self, _widget=None) -> None: self.waitingDo(self.reversePause) - def reverseResume(self): + def reverseResume(self) -> None: self.reverseStatus = "doing" ### self.reverseStartButton.set_sensitive(False) @@ -1664,10 +1688,10 @@ def reverseResume(self): self.reverseResumeButton.set_sensitive(False) self.reverseStopButton.set_sensitive(True) - def reverseResumeClicked(self, _widget=None): + def reverseResumeClicked(self, _widget=None) -> None: self.waitingDo(self.reverseResume) - def reverseStop(self): + def reverseStop(self) -> None: self.reverseStatus = "stop" ### self.reverseStartButton.set_sensitive(True) @@ -1675,10 +1699,10 @@ def reverseStop(self): self.reverseResumeButton.set_sensitive(False) self.reverseStopButton.set_sensitive(False) - def reverseStopClicked(self, _widget=None): + def reverseStopClicked(self, _widget=None) -> None: self.waitingDo(self.reverseStop) - def reverseInputEntryChanged(self, _widget=None): + def reverseInputEntryChanged(self, _widget=None) -> None: inPath = self.reverseInputEntry.get_text() if inPath.startswith("file://"): inPath = urlToPath(inPath) @@ -1695,13 +1719,13 @@ def reverseInputEntryChanged(self, _widget=None): else: self.reverseInputFormatCombo.setActive(inputArgs.formatName) - def reverseOutputEntryChanged(self, widget=None): + def reverseOutputEntryChanged(self, widget=None) -> None: pass - def progressInit(self, title): + def progressInit(self, title) -> None: self.progressTitle = title - def progress(self, ratio, text=None): + def progress(self, ratio, text=None) -> None: if not text: text = "%" + str(int(ratio * 100)) text += " - " + self.progressTitle @@ -1720,7 +1744,7 @@ def __init__(self) -> None: ) self.win = None - def do_activate(self): + def do_activate(self) -> None: win = self.props.active_window if not win: win = self.win @@ -1743,7 +1767,7 @@ def __init__( ) self.app.win = self.win - def run(self, **kwargs): + def run(self, **kwargs) -> None: self.win.run(**kwargs) self.app.run(None) gtk_window_iteration_loop() diff --git a/pyglossary/ui/ui_tk.py b/pyglossary/ui/ui_tk.py index 6b0019a55..59427c1ad 100644 --- a/pyglossary/ui/ui_tk.py +++ b/pyglossary/ui/ui_tk.py @@ -55,7 +55,7 @@ ] -def set_window_icon(window): +def set_window_icon(window) -> None: window.iconphoto( True, tk.PhotoImage(file=logo), @@ -72,15 +72,15 @@ def decodeGeometry(gs): return (int(p[1]), int(p[2]), int(w), int(h)) -def encodeGeometry(x, y, w, h): +def encodeGeometry(x, y, w, h) -> str: return f"{w}x{h}+{x}+{y}" -def encodeLocation(x, y): +def encodeLocation(x, y) -> str: return f"+{x}+{y}" -def centerWindow(win): +def centerWindow(win) -> None: """ Centers a tkinter window :param win: the root or Toplevel window to center. @@ -101,7 +101,7 @@ def centerWindow(win): def newButton(*args, **kwargs): button = ttk.Button(*args, **kwargs) - def onEnter(_event): + def onEnter(_event) -> None: button.invoke() button.bind("", onEnter) @@ -151,7 +151,7 @@ def __init__(self, tktext) -> None: ### self.tktext = tktext - def emit(self, record): + def emit(self, record) -> None: msg = "" if record.getMessage(): msg = self.format(record) @@ -251,13 +251,13 @@ def __init__( # noqa: PLR0913 self.bind("", self.update) self.canvas.pack(side="top", fill="x", expand="no") - def updateProgress(self, value, max_=None, text=""): + def updateProgress(self, value, max_=None, text="") -> None: if max_: self.max = max_ self.value = value self.update(None, text) - def update(self, event=None, labelText=""): # noqa: ARG002 + def update(self, event=None, labelText="") -> None: # noqa: ARG002 # Trim the values to be between min and max value = self.value value = min(value, self.max) @@ -399,11 +399,11 @@ def __init__( # noqa: PLR0913 # self.bind("", self.onKeyPress) - def setActiveRow(self, desc): + def setActiveRow(self, desc) -> None: self.treev.selection_set(desc) self.treev.see(desc) - def updateTree(self): + def updateTree(self) -> None: treev = self.treev current = treev.get_children() if current: @@ -414,7 +414,7 @@ def updateTree(self): if self.activeDesc in self.items: self.setActiveRow(self.activeDesc) - def onEntryKeyRelease(self, _event): + def onEntryKeyRelease(self, _event) -> None: text = self.entry.get().strip() if text == self.lastSearch: return @@ -440,16 +440,16 @@ def onEntryKeyRelease(self, _event): self.updateTree() self.lastSearch = text - def onTreeDoubleClick(self, _event): + def onTreeDoubleClick(self, _event) -> None: self.okClicked() - def cancelClicked(self): + def cancelClicked(self) -> None: self.destroy() - def onReturnPress(self, _event): + def onReturnPress(self, _event) -> None: self.okClicked() - def onDownPress(self, _event): + def onDownPress(self, _event) -> None: treev = self.treev selection = treev.selection() if selection: @@ -460,7 +460,7 @@ def onDownPress(self, _event): self.setActiveRow(self.items[0]) treev.focus() - def onUpPress(self, _event): + def onUpPress(self, _event) -> None: treev = self.treev treev.focus() selection = treev.selection() @@ -472,10 +472,10 @@ def onUpPress(self, _event): if nextDesc: self.setActiveRow(nextDesc) - def onKeyPress(self, event): + def onKeyPress(self, event) -> None: print(f"FormatDialog: onKeyPress: {event}") - def okClicked(self): + def okClicked(self) -> None: treev = self.treev selectedList = treev.selection() desc = selectedList[0] if selectedList else "" @@ -508,24 +508,24 @@ def __init__( self.bind("", self.onEnter) self.bind("", self.onEnter) - def onEnter(self, _event=None): + def onEnter(self, _event=None) -> None: self.invoke() - def onChange(self, desc): + def onChange(self, desc) -> None: self.setValue(desc) self._onChange(desc) def get(self): return self.activeDesc - def setValue(self, desc): + def setValue(self, desc) -> None: if desc: self.var.set(desc) else: self.var.set(self.noneLabel) self.activeDesc = desc - def onClick(self): + def onClick(self) -> None: dialog = FormatDialog( descList=self.descList, title=self.dialogTitle, @@ -577,7 +577,7 @@ def __init__( okButton.pack(side="right") buttonBox.pack(fill="x") - def createOptionsList(self): + def createOptionsList(self) -> None: values = self.values self.valueCol = "#3" cols = [ @@ -638,7 +638,7 @@ def valueMenuItemCustomSelected( formatName: str, optName: str, menu=None, - ): + ) -> None: if menu: menu.destroy() self.menu = None @@ -674,7 +674,7 @@ def valueMenuItemCustomSelected( prop = Glossary.plugins[formatName].optionsProp[optName] - def customOkClicked(_event=None): + def customOkClicked(_event=None) -> None: rawValue = entry.get() if not prop.validateRaw(rawValue): log.error(f"invalid {prop.typ} value: {optName} = {rawValue!r}") @@ -703,7 +703,7 @@ def customOkClicked(_event=None): frame.pack(fill="x") dialog.focus() - def valueMenuItemSelected(self, optName, menu, value): + def valueMenuItemSelected(self, optName, menu, value) -> None: treev = self.treev treev.set(optName, self.valueCol, value) treev.set(optName, "#1", "1") # enable it @@ -713,7 +713,7 @@ def valueMenuItemSelected(self, optName, menu, value): menu.destroy() self.menu = None - def valueCellClicked(self, event, optName): + def valueCellClicked(self, event, optName) -> None: if not optName: return treev = self.treev @@ -762,7 +762,7 @@ def valueCellClicked(self, event, optName): maxItemW = 0 def valueMenuItemSelectedCommand(value): - def callback(): + def callback() -> None: self.valueMenuItemSelected(optName, menu, value) return callback @@ -792,7 +792,7 @@ def callback(): command=valueMenuItemSelectedCommand(value), ) - def close(): + def close() -> None: menu.destroy() self.menu = None @@ -811,7 +811,7 @@ def close(): # make sure to release the grab (Tk 8.0a1 only) menu.grab_release() - def treeClicked(self, event): + def treeClicked(self, event) -> None: treev = self.treev if self.menu: self.menu.destroy() @@ -828,7 +828,7 @@ def treeClicked(self, event): if col == self.valueCol: self.valueCellClicked(event, optName) - def okClicked(self): + def okClicked(self) -> None: treev = self.treev for optName in self.options: enable = bool(int(treev.set(optName, "#1"))) @@ -866,10 +866,10 @@ def __init__( self.values = values self.formatInput = formatInput - def setOptionsValues(self, values): + def setOptionsValues(self, values) -> None: self.values = values - def buttonClicked(self): + def buttonClicked(self) -> None: formatD = self.formatInput.get() if not formatD: return @@ -1261,12 +1261,12 @@ def __init__( else: # Linux rootWin.deiconify() - def textSelectAll(self, tktext): + def textSelectAll(self, tktext) -> None: tktext.tag_add(tk.SEL, "1.0", tk.END) tktext.mark_set(tk.INSERT, "1.0") tktext.see(tk.INSERT) - def consoleKeyPress(self, e): + def consoleKeyPress(self, e) -> str | None: # print(e.state, e.keysym) if e.state > 0: if e.keysym == "c": @@ -1278,7 +1278,7 @@ def consoleKeyPress(self, e): return None return "break" - def verbosityChanged(self, _index, _value, _op): + def verbosityChanged(self, _index, _value, _op) -> None: log.setVerbosity( int(self.verbosityCombo.get()), ) @@ -1295,7 +1295,7 @@ def verbosityChanged(self, _index, _value, _op): # if "info" in x: # log.debug(x) - def inputFormatChanged(self, *_args): + def inputFormatChanged(self, *_args) -> None: formatDesc = self.formatButtonInputConvert.get() if not formatDesc: return @@ -1311,7 +1311,7 @@ def inputFormatChanged(self, *_args): else: self.readOptionsButton.grid_forget() - def outputFormatChanged(self, *_args): + def outputFormatChanged(self, *_args) -> None: formatDesc = self.formatButtonOutputConvert.get() if not formatDesc: return @@ -1347,11 +1347,11 @@ def outputFormatChanged(self, *_args): pathNoExt + plugin.extensionCreate, ) - def anyEntryChanged(self, _event=None): + def anyEntryChanged(self, _event=None) -> None: self.inputEntryChanged() self.outputEntryChanged() - def inputEntryChanged(self, _event=None): + def inputEntryChanged(self, _event=None) -> None: # char = event.keysym pathI = self.entryInputConvert.get() if self.pathI == pathI: @@ -1375,7 +1375,7 @@ def inputEntryChanged(self, _event=None): self.inputFormatChanged() self.pathI = pathI - def outputEntryChanged(self, _event=None): + def outputEntryChanged(self, _event=None) -> None: pathO = self.entryOutputConvert.get() if self.pathO == pathO: return @@ -1401,13 +1401,13 @@ def outputEntryChanged(self, _event=None): self.outputFormatChanged() self.pathO = pathO - def save_fcd_dir(self): + def save_fcd_dir(self) -> None: if not self.fcd_dir: return with open(self.fcd_dir_save_path, mode="w", encoding="utf-8") as fp: fp.write(self.fcd_dir) - def browseInputConvert(self): + def browseInputConvert(self) -> None: path = filedialog.askopenfilename(initialdir=self.fcd_dir) if path: self.entryInputConvert.delete(0, "end") @@ -1416,7 +1416,7 @@ def browseInputConvert(self): self.fcd_dir = os.path.dirname(path) self.save_fcd_dir() - def browseOutputConvert(self): + def browseOutputConvert(self) -> None: path = filedialog.asksaveasfilename() if path: self.entryOutputConvert.delete(0, "end") @@ -1482,7 +1482,7 @@ def run( # noqa: PLR0913 writeOptions: dict[str, Any] | None = None, convertOptions: dict[str, Any] | None = None, glossarySetAttrs: dict[str, Any] | None = None, - ): + ) -> None: self.config = config if inputFilename: @@ -1527,10 +1527,10 @@ def run( # noqa: PLR0913 # which is not implemented self.mainloop() - def progressInit(self, title): + def progressInit(self, title) -> None: self.progressTitle = title - def progress(self, ratio, text=""): + def progress(self, ratio, text="") -> None: if not text: text = "%" + str(int(ratio * 100)) text += " - " + self.progressTitle @@ -1539,7 +1539,7 @@ def progress(self, ratio, text=""): # self.pbar.update() self.rootWin.update() - def console_clear(self, _event=None): + def console_clear(self, _event=None) -> None: self.console.delete("1.0", "end") self.console.insert("end", "Console:\n") diff --git a/pyglossary/ui/ui_web/ui_controller.py b/pyglossary/ui/ui_web/ui_controller.py index b5f8fe5e7..e10273ea3 100644 --- a/pyglossary/ui/ui_web/ui_controller.py +++ b/pyglossary/ui/ui_web/ui_controller.py @@ -29,7 +29,7 @@ def progressInit(self, title: str) -> None: json.dumps({"type": "progress", "text": title or "", "ratio": 0}) ) - def progress(self, ratio: float, text=None) -> None: + def progress(self, ratio: float, text: str = "") -> None: if not text: text = f"{int(ratio * 100)!s}%" diff --git a/pyglossary/ui/ui_web/weblog.py b/pyglossary/ui/ui_web/weblog.py index 48ae36ded..a27fa61fc 100644 --- a/pyglossary/ui/ui_web/weblog.py +++ b/pyglossary/ui/ui_web/weblog.py @@ -25,14 +25,19 @@ import logging import traceback +from typing import TYPE_CHECKING, Protocol + +if TYPE_CHECKING: + class ServerType(Protocol): + def send_message_to_all(self, msg: str | dict) -> None: ... class WebLogHandler(logging.Handler): - def __init__(self, server) -> None: + def __init__(self, server: ServerType) -> None: logging.Handler.__init__(self) self.srv = server - def emit(self, record: logging.LogRecord): + def emit(self, record: logging.LogRecord) -> None: msg = "" if record.getMessage(): msg = self.format(record) diff --git a/pyglossary/ui/ui_web/websocket_handler.py b/pyglossary/ui/ui_web/websocket_handler.py index 5919b7ce1..c00a5fb55 100644 --- a/pyglossary/ui/ui_web/websocket_handler.py +++ b/pyglossary/ui/ui_web/websocket_handler.py @@ -70,7 +70,7 @@ class HTTPWebSocketHandler(SimpleHTTPRequestHandler): browse_roots = [] @classmethod - def add_browse_root(cls, path): + def add_browse_root(cls, path: str) -> None: """Additional browse roots for css/js/etc resources.""" cls.browse_roots.append(path) @@ -79,9 +79,9 @@ def __init__( socket: socketlib.socket, addr: tuple[str, int], # (ip: str, port: int) server: socketserver.BaseServer, - *args, + *args, # noqa: ANN001, ANN002 **kwargs, - ): + ) -> None: if hasattr(self, "_send_lock"): raise RuntimeError("_send_lock already exists") @@ -100,7 +100,7 @@ def __init__( directory=webroot, ) - def translate_path(self, path): + def translate_path(self, path: str) -> str: """ Overlay of https://github.com/python/cpython/blob/47c5a0f307cff3ed477528536e8de095c0752efa/Lib/http/server.py#L841 patched to support multiple browse roots @@ -141,13 +141,13 @@ def translate_path(self, path): # fallback to super for other methods return super().translate_path(path) - def do_GET(self): + def do_GET(self) -> None: if self.path == "/config": self.send_config() else: super().do_GET() - def send_config(self): + def send_config(self) -> None: self.send_response(HTTPStatus.OK) self.send_header("Content-Type", "application/json") self.end_headers() @@ -163,7 +163,7 @@ def send_config(self): } self.wfile.write(json.dumps(conversion_config).encode()) - def do_POST(self): + def do_POST(self) -> None: # custom ajax action for /convert POST if self.path == "/convert": self.handle_convert_job() @@ -179,13 +179,16 @@ def do_POST(self): self.wfile, ) - def setup(self): + def setup(self) -> None: SimpleHTTPRequestHandler.setup(self) self.keep_alive = True self.handshake_done = False self.valid_client = False - def handle(self): + def set_keep_alive(self, keep_alive: bool) -> None: + self.keep_alive = keep_alive + + def handle(self) -> None: self.close_connection = True try: @@ -195,14 +198,14 @@ def handle(self): except Exception as e: self.log_error(str(e)) - def handle_ws(self): + def handle_ws(self) -> None: while self.keep_alive: if not self.handshake_done: self.handshake() elif self.valid_client: self.read_next_message() - def handle_convert_job(self): + def handle_convert_job(self) -> None: try: payload: dict[str, Any] = json.loads( self.rfile.read(int(self.headers.get("Content-Length", 0))) @@ -234,7 +237,7 @@ def validation_exception(self, e: Exception) -> None: self.end_headers() json.dump({"error": str(e)}, self.wfile) - def json_decode_error(self): + def json_decode_error(self) -> None: self.send_response(HTTPStatus.BAD_REQUEST) self.send_header("Content-type", "application/json") self.end_headers() @@ -247,7 +250,7 @@ def internal_exception(self, e: Exception) -> None: self.end_headers() self.wfile.write(f"Error: {e!s}".encode()) - def _handle_one_request(self): + def _handle_one_request(self) -> None: self.raw_requestline = self.rfile.readline(65537) if len(self.raw_requestline) > 65536: @@ -277,7 +280,7 @@ def _handle_one_request(self): method() self.wfile.flush() # actually send the response if not already done. - def handle_one_request(self): + def handle_one_request(self) -> None: """ Handle a single HTTP/WS request. Override ootb method to delegate to WebSockets handler based @@ -290,10 +293,10 @@ def handle_one_request(self): self.log_error("Request timed out: %r", e) self.close_connection = True - def read_bytes(self, num): + def read_bytes(self, num: int) -> bytes: return self.rfile.read(num) - def read_next_message(self): + def read_next_message(self) -> None: try: b1, b2 = self.read_bytes(2) except OSError as e: # to be replaced with ConnectionResetError for py3 @@ -346,13 +349,17 @@ def read_next_message(self): message_bytes.append(message_byte) opcode_handler(self, message_bytes.decode("utf8")) - def send_message(self, message): + def send_message(self, message: str | bytes) -> None: self.send_text(message) - def send_pong(self, message): + def send_pong(self, message: str | bytes) -> None: self.send_text(message, OPCODE_PONG) - def send_close(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): + def send_close( + self, + status: int = CLOSE_STATUS_NORMAL, + reason: bytes = DEFAULT_CLOSE_REASON, + ) -> None: """ Send CLOSE to client. @@ -380,16 +387,15 @@ def send_close(self, status=CLOSE_STATUS_NORMAL, reason=DEFAULT_CLOSE_REASON): except Exception as e: self.log_error(f"ws: CLOSE not sent - client disconnected! {e!s}") - def send_text(self, message, opcode=OPCODE_TEXT): + def send_text(self, message: str | bytes, opcode: int = OPCODE_TEXT) -> bool | None: """ Important: Fragmented(=continuation) messages are not supported since their usage cases are limited - when we don't know the payload length. """ # Validate message if isinstance(message, bytes): - message = try_decode_UTF8( - message - ) # this is slower but ensures we have UTF-8 + # this is slower but ensures we have UTF-8 + message = try_decode_UTF8(message) if not message: log.warning("Can't send message, message is not valid UTF-8") return False @@ -428,7 +434,7 @@ def send_text(self, message, opcode=OPCODE_TEXT): self.request.send(header + payload) # type: ignore return None - def handshake(self): + def handshake(self) -> None: try: key = self.headers.get("sec-websocket-key") except KeyError: @@ -443,7 +449,7 @@ def handshake(self): self.server.new_client_handler(self) @classmethod - def make_handshake_response(cls, key): + def make_handshake_response(cls, key: str) -> str: return ( "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" @@ -453,13 +459,14 @@ def make_handshake_response(cls, key): ) @classmethod - def calculate_response_key(cls, key): + def calculate_response_key(cls, key: str) -> str: seed = sha1(key.encode() + b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11") response_key = b64encode(seed.digest()).strip() return response_key.decode("ASCII") - def finish(self): + def finish(self) -> None: self.server.client_left_handler(self) + self.connection.close() def encode_to_UTF8(data: str) -> bytes: diff --git a/pyglossary/ui/ui_web/websocket_main.py b/pyglossary/ui/ui_web/websocket_main.py index af98f3073..165c6ed5a 100644 --- a/pyglossary/ui/ui_web/websocket_main.py +++ b/pyglossary/ui/ui_web/websocket_main.py @@ -29,16 +29,21 @@ import logging import os.path from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from pyglossary.glossary_types import EntryType +from typing import TYPE_CHECKING, Any, Protocol from pyglossary.glossary_v2 import Glossary from pyglossary.ui.ui_web.weblog import WebLogHandler from pyglossary.ui.ui_web.websocket_handler import HTTPWebSocketHandler from pyglossary.ui.ui_web.websocket_server import HttpWebsocketServer +if TYPE_CHECKING: + from pyglossary.glossary_types import EntryType + + class ServerType(Protocol): + def send_message_to_all(self, msg: str | dict) -> None: ... + def shutdown(self) -> None: ... + + MAX_IMAGE_SIZE = 512000 DEFAULT_MAX_BROWSE_ENTRIES = 42 @@ -56,7 +61,7 @@ # ======================= IMPLEMENTATION SECTION ========================= -def new_client(client, server): +def new_client(client: dict[str, Any], server: ServerType) -> None: client_id = client.get("id", "n/a") print(f"New client connected and was given id {client_id}") server.send_message_to_all( @@ -65,12 +70,12 @@ def new_client(client, server): # Called on client disconnecting -def client_left(client, server): +def client_left(client: dict[str, Any], server: ServerType) -> None: log.info(f"{server}: Client({(client and client.get('id')) or -1}) disconnected") # Callback invoked when client sends a message -def message_received(client, server, message): +def message_received(client: dict[str, Any], server: ServerType, message: str) -> None: if message == "ping": print(f"Client({client.get('id')}) said: {message}") server.send_message_to_all({"type": "info", "text": "ws: pong ✔️"}) @@ -114,7 +119,11 @@ def browse_check_entry(entry: EntryType, wordQuery: str) -> str | None: return html_entry -def handle_browse_request(client, server, message): +def handle_browse_request( + client: dict[str, Any], + server: ServerType, + message: str, +) -> None: log.debug(f"processing client #{client} message") params = json.loads(message) wordQuery = params.get("word") @@ -177,7 +186,7 @@ def handle_browse_request(client, server, message): break -def create_server(host: str, port: int): +def create_server(host: str, port: int) -> HttpWebsocketServer: server = HttpWebsocketServer( HTTPWebSocketHandler, log, diff --git a/pyglossary/ui/ui_web/websocket_server.py b/pyglossary/ui/ui_web/websocket_server.py index 855997a53..b6af33c37 100644 --- a/pyglossary/ui/ui_web/websocket_server.py +++ b/pyglossary/ui/ui_web/websocket_server.py @@ -27,8 +27,29 @@ import logging import sys import threading +from collections.abc import Callable from http.server import HTTPServer from socketserver import ThreadingMixIn +from typing import TYPE_CHECKING, Any, Protocol + +if TYPE_CHECKING: + class ServerType(Protocol): + def send_message_to_all(self, msg: str | dict) -> None: ... + def shutdown(self) -> None: ... + + class HandlerType(Protocol): + def send_pong(self, message: str | bytes) -> None: ... + + def send_close( + self, + status: int, + reason: bytes, + ) -> None: ... + + def set_keep_alive(self, keep_alive: bool) -> None: ... + + def finish(self) -> None: ... + """ +-+-+-+-+-------+-+-------------+-------------------------------+ @@ -65,31 +86,42 @@ class API: - def run_forever(self, threaded=False): - return self._run_forever(threaded) + def run_forever(self, threaded: bool = False) -> None: + raise NotImplementedError - def new_client(self, client, server): + def new_client(self, client: dict[str, Any], server: ServerType) -> None: pass - def client_left(self, client, server): + def client_left(self, client: dict[str, Any], server: ServerType) -> None: pass - def message_received(self, client, server, message): + def message_received( + self, + client: dict[str, Any], + server: ServerType, + message: str, + ) -> None: pass - def set_fn_new_client(self, fn): + def set_fn_new_client( + self, + fn: Callable[[dict[str, Any], ServerType], None], + ) -> None: self.new_client = fn - def set_fn_client_left(self, fn): + def set_fn_client_left( + self, + fn: Callable[[dict[str, Any], ServerType], None], + ) -> None: self.client_left = fn - def set_fn_message_received(self, fn): + def set_fn_message_received(self, fn) -> None: # noqa: ANN001 self.message_received = fn - def send_message(self, client, msg): + def send_message(self, client: dict[str, Any], msg: str | bytes) -> None: self._unicast(client, msg) - def send_message_to_all(self, msg): + def send_message_to_all(self, msg: str | dict) -> None: if isinstance(msg, str): self._multicast(msg) else: @@ -97,32 +129,32 @@ def send_message_to_all(self, msg): def deny_new_connections( self, - status=CLOSE_STATUS_NORMAL, - reason=DEFAULT_CLOSE_REASON, - ): + status: int = CLOSE_STATUS_NORMAL, + reason: int = DEFAULT_CLOSE_REASON, + ) -> None: self._deny_new_connections(status, reason) - def allow_new_connections(self): + def allow_new_connections(self) -> None: self._allow_new_connections() def shutdown_gracefully( self, - status=CLOSE_STATUS_NORMAL, - reason=DEFAULT_CLOSE_REASON, - ): + status: int =CLOSE_STATUS_NORMAL, + reason: int = DEFAULT_CLOSE_REASON, + ) -> None: self._shutdown_gracefully(status, reason) - def shutdown_abruptly(self): + def shutdown_abruptly(self) -> None: self._shutdown_abruptly() def disconnect_clients_gracefully( self, - status=CLOSE_STATUS_NORMAL, - reason=DEFAULT_CLOSE_REASON, - ): + status: int = CLOSE_STATUS_NORMAL, + reason: int = DEFAULT_CLOSE_REASON, + ) -> None: self._disconnect_clients_gracefully(status, reason) - def disconnect_clients_abruptly(self): + def disconnect_clients_abruptly(self) -> None: self._disconnect_clients_abruptly() @@ -154,9 +186,9 @@ def __init__( self, handlerClass: type, logger: logging.Logger, - host="127.0.0.1", - port=0, - ): + host: str = "127.0.0.1", + port: int = 0, + ) -> None: # server's own logger HTTPServer.__init__(self, (host, port), handlerClass) self.host = host @@ -174,16 +206,16 @@ def __init__( def url(self) -> str: return f"http://{self.host}:{self.port}/" - def info(self, *args, **kwargs) -> None: + def info(self, *args, **kwargs) -> None: # noqa: ANN002 self.logger.info(*args, **kwargs) - def error(self, *args, **kwargs) -> None: + def error(self, *args, **kwargs) -> None: # noqa: ANN002 self.logger.error(*args, **kwargs) - def exception(self, *args, **kwargs) -> None: + def exception(self, *args, **kwargs) -> None: # noqa: ANN002 self.logger.error(*args, **kwargs) - def _run_forever(self, threaded): + def run_forever(self, threaded: bool = False) -> None: cls_name = self.__class__.__name__ try: self.info(f"Listening on http://{self.host}:{self.port}/") @@ -207,16 +239,16 @@ def _run_forever(self, threaded): self.exception(str(e), exc_info=True) sys.exit(1) - def message_received_handler(self, handler, msg): + def message_received_handler(self, handler: HandlerType, msg: str) -> None: self.message_received(self.handler_to_client(handler), self, msg) - def ping_received_handler(self, handler, msg): + def ping_received_handler(self, handler: HandlerType, msg: str) -> None: handler.send_pong(msg) - def pong_received_handler(self, handler, msg): + def pong_received_handler(self, handler: HandlerType, msg: str) -> None: pass - def new_client_handler(self, handler): + def new_client_handler(self, handler: HandlerType) -> None: if self._deny_clients: status = self._deny_clients["status"] reason = self._deny_clients["reason"] @@ -233,50 +265,52 @@ def new_client_handler(self, handler): self.clients.append(client) self.new_client(client, self) - def client_left_handler(self, handler): + def client_left_handler(self, handler: HandlerType) -> None: client = self.handler_to_client(handler) + if not client: + self.logger.warning("client handler was not found") + return self.client_left(client, self) if client in self.clients: self.clients.remove(client) - def _unicast(self, receiver_client, msg): + def _unicast(self, receiver_client: dict[str, Any], msg: str | bytes) -> None: receiver_client["handler"].send_message(msg) - def _multicast(self, msg): + def _multicast(self, msg: str | bytes) -> None: for client in self.clients: try: self._unicast(client, msg) except Exception as e: print(str(e)) - def handler_to_client(self, handler): + def handler_to_client(self, handler: HandlerType) -> dict[str, Any] | None: for client in self.clients: if client["handler"] == handler: return client return None - def _terminate_client_handler(self, handler): - handler.keep_alive = False + def _terminate_client_handler(self, handler: HandlerType) -> None: + handler.set_keep_alive(False) handler.finish() - handler.connection.close() - def _terminate_client_handlers(self): + def _terminate_client_handlers(self) -> None: """Ensures request handler for each client is terminated correctly.""" for client in self.clients: self._terminate_client_handler(client["handler"]) def _shutdown_gracefully( self, - status=CLOSE_STATUS_NORMAL, - reason=DEFAULT_CLOSE_REASON, - ): + status: int = CLOSE_STATUS_NORMAL, + reason: bytes = DEFAULT_CLOSE_REASON, + ) -> None: """Send a CLOSE handshake to all connected clients before terminating server.""" self.keep_alive = False self._disconnect_clients_gracefully(status, reason) self.server_close() self.shutdown() - def _shutdown_abruptly(self): + def _shutdown_abruptly(self) -> None: """Terminate server without sending a CLOSE handshake.""" self.keep_alive = False self._disconnect_clients_abruptly() @@ -285,26 +319,30 @@ def _shutdown_abruptly(self): def _disconnect_clients_gracefully( self, - status=CLOSE_STATUS_NORMAL, - reason=DEFAULT_CLOSE_REASON, - ): + status: int = CLOSE_STATUS_NORMAL, + reason: bytes = DEFAULT_CLOSE_REASON, + ) -> None: """Terminate clients gracefully without shutting down the server.""" for client in self.clients: client["handler"].send_close(status, reason) self._terminate_client_handlers() - def _disconnect_clients_abruptly(self): + def _disconnect_clients_abruptly(self) -> None: """ Terminate clients abruptly (no CLOSE handshake) without shutting down the server. """ self._terminate_client_handlers() - def _deny_new_connections(self, status, reason): + def _deny_new_connections( + self, + status: int, + reason: bytes, + ) -> None: self._deny_clients = { "status": status, "reason": reason, } - def _allow_new_connections(self): + def _allow_new_connections(self) -> None: self._deny_clients = False diff --git a/pyproject.toml b/pyproject.toml index c94e2d6d1..1732ff23e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -208,8 +208,11 @@ mccabe.max-complexity = 13 # Unlike Flake8, default to a complexity level of 10. "scripts/wiki-formats.py" = ["E501"] "pyglossary/io_utils.py" = ["ANN"] "pyglossary/plugins/babylon_bgl/reader_debug.py" = ["ANN", "FURB"] +#"pyglossary/ui/ui_gtk4.py" = ["ANN"] +"pyglossary/ui/ui_gtk.py" = ["ANN"] +"pyglossary/ui/ui_tk.py" = ["ANN"] "pyglossary/ui/**/*.py" = [ - "ANN", + #"ANN", "T201", "PERF203", "PLR0904", # Too many public methods