From 067c233b4b0d53c909f5d7babc158388101cbb85 Mon Sep 17 00:00:00 2001 From: wurstsalat Date: Tue, 29 Nov 2022 19:36:05 +0100 Subject: [triggers] Type annotations, linting --- triggers/__init__.py | 2 +- triggers/gtk/config.py | 146 ++++++++++++++++++++++++++++++++----------------- triggers/triggers.py | 110 +++++++++++++++++++++++++++---------- triggers/util.py | 21 +++++-- 4 files changed, 193 insertions(+), 86 deletions(-) diff --git a/triggers/__init__.py b/triggers/__init__.py index 10a5a09..fc31554 100644 --- a/triggers/__init__.py +++ b/triggers/__init__.py @@ -1 +1 @@ -from .triggers import Triggers +from .triggers import Triggers # type: ignore diff --git a/triggers/gtk/config.py b/triggers/gtk/config.py index 3c2409f..df0ad50 100644 --- a/triggers/gtk/config.py +++ b/triggers/gtk/config.py @@ -14,6 +14,11 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . +from __future__ import annotations + +from typing import Any +from typing import TYPE_CHECKING + from pathlib import Path from gi.repository import Gtk @@ -26,7 +31,10 @@ from gajim.common.helpers import play_sound_file from gajim.plugins.plugins_i18n import _ from gajim.plugins.helpers import get_builder -EVENTS = { +if TYPE_CHECKING: + from ..triggers import Triggers + +EVENTS: dict[str, Any] = { 'message_received': [], } @@ -39,7 +47,7 @@ RECIPIENT_TYPES = [ class ConfigDialog(Gtk.ApplicationWindow): - def __init__(self, plugin, transient): + def __init__(self, plugin: Triggers, transient: Gtk.Window) -> None: Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_show_menubar(False) @@ -51,41 +59,61 @@ class ConfigDialog(Gtk.ApplicationWindow): self.set_destroy_with_parent(True) ui_path = Path(__file__).parent - self._ui = get_builder(ui_path.resolve() / 'config.ui') + self._ui = get_builder(str(ui_path.resolve() / 'config.ui')) self._plugin = plugin self.add(self._ui.box) self.show_all() - self._active_num = '' + self._active_num = -1 + self._config: dict[int, Any] = {} + self._initialize() self._ui.connect_signals(self) self.connect('destroy', self._on_destroy) - def _on_destroy(self, *args): + def _on_destroy(self, *args: Any) -> None: for num in list(self._plugin.config.keys()): del self._plugin.config[num] for num in self._config: self._plugin.config[str(num)] = self._config[num] - def _initialize(self): + def _initialize(self) -> None: # Fill window - for widget in ( - 'conditions_treeview', 'config_box', 'event_combobox', - 'recipient_type_combobox', 'recipient_list_entry', - 'delete_button', 'online_cb', 'away_cb', 'xa_cb', 'dnd_cb', - 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb', - 'disable_popup_cb', - 'tab_opened_cb', 'not_tab_opened_cb', 'has_focus_cb', - 'not_has_focus_cb', 'filechooser', 'sound_file_box', - 'up_button', 'down_button', 'run_command_cb', - 'command_entry', 'one_shot_cb'): + widgets = [ + 'conditions_treeview', + 'config_box', + 'event_combobox', + 'recipient_type_combobox', + 'recipient_list_entry', + 'delete_button', + 'online_cb', + 'away_cb', + 'xa_cb', + 'dnd_cb', + 'use_sound_cb', + 'disable_sound_cb', + 'use_popup_cb', + 'disable_popup_cb', + 'tab_opened_cb', + 'not_tab_opened_cb', + 'has_focus_cb', + 'not_has_focus_cb', + 'filechooser', + 'sound_file_box', + 'up_button', + 'down_button', + 'run_command_cb', + 'command_entry', + 'one_shot_cb' + ] + for widget in widgets: self._ui.__dict__[widget] = self._ui.get_object(widget) self._config = {} - for num in self._plugin.config: + for num in self._plugin.config.keys(): self._config[int(num)] = self._plugin.config[num] if not self._ui.conditions_treeview.get_column(0): @@ -141,7 +169,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.filechooser.add_filter(filter_) self._ui.filechooser.set_filter(filter_) - def _initiate_rule_state(self): + def _initiate_rule_state(self) -> None: """ Set values for all widgets """ @@ -243,7 +271,7 @@ class ConfigDialog(Gtk.ApplicationWindow): value = False self._ui.one_shot_cb.set_active(value) - def _set_treeview_string(self): + def _set_treeview_string(self) -> None: selection = self._ui.conditions_treeview.get_selection() (model, iter_) = selection.get_selected() if not iter_: @@ -274,17 +302,22 @@ class ConfigDialog(Gtk.ApplicationWindow): 'recipient': recipient, 'status': status} - def _on_conditions_treeview_cursor_changed(self, widget): + def _on_conditions_treeview_cursor_changed(self, + widget: Gtk.TreeView + ) -> None: + (model, iter_) = widget.get_selection().get_selected() if not iter_: - self._active_num = '' + self._active_num = -1 return self._active_num = model[iter_][0] if self._active_num == 0: self._ui.up_button.set_sensitive(False) else: self._ui.up_button.set_sensitive(True) - _max = widget.get_model().iter_n_children(None) + model = widget.get_model() + assert model is not None + _max = model.iter_n_children(None) if self._active_num == _max - 1: self._ui.down_button.set_sensitive(False) else: @@ -293,7 +326,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.config_box.set_sensitive(True) self._ui.delete_button.set_sensitive(True) - def _on_new_button_clicked(self, _widget): + def _on_new_button_clicked(self, _button: Gtk.Button) -> None: model = self._ui.conditions_treeview.get_model() num = self._ui.conditions_treeview.get_model().iter_n_children(None) self._config[num] = { @@ -317,7 +350,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._set_treeview_string() self._ui.config_box.set_sensitive(True) - def _on_delete_button_clicked(self, widget): + def _on_delete_button_clicked(self, button: Gtk.Button) -> None: selection = self._ui.conditions_treeview.get_selection() (model, iter_) = selection.get_selected() if not iter_: @@ -332,13 +365,13 @@ class ConfigDialog(Gtk.ApplicationWindow): iter2 = model.iter_next(iter2) model.remove(iter_) del self._config[num] - self._active_num = '' - widget.set_sensitive(False) + self._active_num = -1 + button.set_sensitive(False) self._ui.up_button.set_sensitive(False) self._ui.down_button.set_sensitive(False) self._ui.config_box.set_sensitive(False) - def _on_up_button_clicked(self, _widget): + def _on_up_button_clicked(self, _button: Gtk.Button) -> None: selection = self._ui.conditions_treeview.get_selection() (model, iter_) = selection.get_selected() if not iter_: @@ -355,7 +388,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._on_conditions_treeview_cursor_changed( self._ui.conditions_treeview) - def _on_down_button_clicked(self, _widget): + def _on_down_button_clicked(self, _button: Gtk.Button) -> None: selection = self._ui.conditions_treeview.get_selection() (model, iter_) = selection.get_selected() if not iter_: @@ -370,10 +403,10 @@ class ConfigDialog(Gtk.ApplicationWindow): self._on_conditions_treeview_cursor_changed( self._ui.conditions_treeview) - def _on_event_combobox_changed(self, widget): + def _on_event_combobox_changed(self, combo: Gtk.ComboBox) -> None: if self._active_num < 0: return - active = widget.get_active() + active = combo.get_active() if active == -1: return event = list(EVENTS.keys())[active] @@ -383,7 +416,10 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.__dict__[widget].set_state(False) self._set_treeview_string() - def _on_recipient_type_combobox_changed(self, widget): + def _on_recipient_type_combobox_changed(self, + widget: Gtk.ComboBox + ) -> None: + if self._active_num < 0: return recipient_type = RECIPIENT_TYPES[widget.get_active()] @@ -394,7 +430,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.recipient_list_entry.set_sensitive(True) self._set_treeview_string() - def _on_recipient_list_entry_changed(self, widget): + def _on_recipient_list_entry_changed(self, widget: Gtk.Entry) -> None: if self._active_num < 0: return recipients = widget.get_text() @@ -402,7 +438,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._config[self._active_num]['recipients'] = recipients self._set_treeview_string() - def _set_status_config(self): + def _set_status_config(self) -> None: if self._active_num < 0: return status = '' @@ -414,7 +450,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._config[self._active_num]['status'] = status self._set_treeview_string() - def _on_status_radiobutton_toggled(self, _widget): + def _on_status_radiobutton_toggled(self, _widget: Gtk.RadioButton) -> None: if self._active_num < 0: return if self._ui.all_status_rb.get_active(): @@ -432,13 +468,13 @@ class ConfigDialog(Gtk.ApplicationWindow): self._set_treeview_string() - def _on_status_cb_toggled(self, _widget): + def _on_status_cb_toggled(self, _widget: Gtk.CheckButton) -> None: if self._active_num < 0: return self._set_status_config() # tab_opened OR (not xor) not_tab_opened must be active - def _on_tab_opened_cb_toggled(self, widget): + def _on_tab_opened_cb_toggled(self, widget: Gtk.CheckButton) -> None: if self._active_num < 0: return if widget.get_active(): @@ -454,7 +490,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.not_tab_opened_cb.set_active(True) self._config[self._active_num]['tab_opened'] = 'no' - def _on_not_tab_opened_cb_toggled(self, widget): + def _on_not_tab_opened_cb_toggled(self, widget: Gtk.CheckButton) -> None: if self._active_num < 0: return if widget.get_active(): @@ -467,7 +503,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._config[self._active_num]['tab_opened'] = 'yes' # has_focus OR (not xor) not_has_focus must be active - def _on_has_focus_cb_toggled(self, widget): + def _on_has_focus_cb_toggled(self, widget: Gtk.CheckButton) -> None: if self._active_num < 0: return if widget.get_active(): @@ -479,7 +515,7 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.not_has_focus_cb.set_active(True) self._config[self._active_num]['has_focus'] = 'no' - def _on_not_has_focus_cb_toggled(self, widget): + def _on_not_has_focus_cb_toggled(self, widget: Gtk.CheckButton) -> None: if self._active_num < 0: return if widget.get_active(): @@ -491,7 +527,12 @@ class ConfigDialog(Gtk.ApplicationWindow): self._ui.has_focus_cb.set_active(True) self._config[self._active_num]['has_focus'] = 'yes' - def _on_use_it_toggled(self, widget, opposite_widget, option): + def _on_use_it_toggled(self, + widget: Gtk.CheckButton, + opposite_widget: Gtk.CheckButton, + option: str + ) -> None: + if widget.get_active(): if opposite_widget.get_active(): opposite_widget.set_active(False) @@ -501,7 +542,12 @@ class ConfigDialog(Gtk.ApplicationWindow): else: self._config[self._active_num][option] = '' - def _on_disable_it_toggled(self, widget, opposite_widget, option): + def _on_disable_it_toggled(self, + widget: Gtk.CheckButton, + opposite_widget: Gtk.CheckButton, + option: str + ) -> None: + if widget.get_active(): if opposite_widget.get_active(): opposite_widget.set_active(False) @@ -511,38 +557,38 @@ class ConfigDialog(Gtk.ApplicationWindow): else: self._config[self._active_num][option] = '' - def _on_use_sound_cb_toggled(self, widget): + def _on_use_sound_cb_toggled(self, widget: Gtk.CheckButton) -> None: self._on_use_it_toggled(widget, self._ui.disable_sound_cb, 'sound') if widget.get_active(): self._ui.sound_file_box.set_sensitive(True) else: self._ui.sound_file_box.set_sensitive(False) - def _on_sound_file_set(self, widget): + def _on_sound_file_set(self, widget: Gtk.FileChooserButton) -> None: self._config[self._active_num]['sound_file'] = widget.get_filename() - def _on_play_button_clicked(self, _widget): + def _on_play_button_clicked(self, _button: Gtk.Button) -> None: play_sound_file(self._ui.filechooser.get_filename()) - def _on_disable_sound_cb_toggled(self, widget): + def _on_disable_sound_cb_toggled(self, widget: Gtk.CheckButton) -> None: self._on_disable_it_toggled(widget, self._ui.use_sound_cb, 'sound') - def _on_use_popup_cb_toggled(self, widget): + def _on_use_popup_cb_toggled(self, widget: Gtk.CheckButton) -> None: self._on_use_it_toggled(widget, self._ui.disable_popup_cb, 'popup') - def _on_disable_popup_cb_toggled(self, widget): + def _on_disable_popup_cb_toggled(self, widget: Gtk.CheckButton) -> None: self._on_disable_it_toggled(widget, self._ui.use_popup_cb, 'popup') - def _on_run_command_cb_toggled(self, widget): + def _on_run_command_cb_toggled(self, widget: Gtk.CheckButton) -> None: self._config[self._active_num]['run_command'] = widget.get_active() if widget.get_active(): self._ui.command_entry.set_sensitive(True) else: self._ui.command_entry.set_sensitive(False) - def _on_command_entry_changed(self, widget): + def _on_command_entry_changed(self, widget: Gtk.Entry) -> None: self._config[self._active_num]['command'] = widget.get_text() - def _on_one_shot_cb_toggled(self, widget): + def _on_one_shot_cb_toggled(self, widget: Gtk.CheckButton) -> None: self._config[self._active_num]['one_shot'] = widget.get_active() self._ui.command_entry.set_sensitive(widget.get_active()) diff --git a/triggers/triggers.py b/triggers/triggers.py index 34b2305..8cafe1f 100644 --- a/triggers/triggers.py +++ b/triggers/triggers.py @@ -14,16 +14,19 @@ # # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . -# from __future__ import annotations from typing import Any +from typing import Callable +from typing import cast from typing import Union import logging from functools import partial +from nbxmpp.protocol import JID + from gajim.common import app from gajim.common import ged from gajim.common.const import PROPAGATE_EVENT @@ -31,24 +34,26 @@ from gajim.common.const import STOP_EVENT from gajim.common.events import Notification from gajim.common.events import GcMessageReceived from gajim.common.events import MessageReceived +from gajim.common.events import PresenceReceived from gajim.common.helpers import exec_command from gajim.common.helpers import play_sound_file from gajim.plugins import GajimPlugin from gajim.plugins.plugins_i18n import _ +from triggers.gtk.config import ConfigDialog from triggers.util import log_result from triggers.util import RuleResult -from triggers.gtk.config import ConfigDialog log = logging.getLogger('gajim.p.triggers') MessageEventsT = Union[GcMessageReceived, MessageReceived] +ProcessableEventsT = Union[MessageEventsT, Notification, PresenceReceived] RuleT = dict[str, Any] class Triggers(GajimPlugin): - def init(self): + def init(self) -> None: self.description = _( 'Configure Gajim’s behaviour with triggers for each contact') self.config_dialog = partial(ConfigDialog, self) @@ -77,7 +82,7 @@ class Triggers(GajimPlugin): log.info('Result: %s', result) return self._excecute_message_rules(result) - def _on_presence_received(self, event): + def _on_presence_received(self, event: PresenceReceived) -> None: # TODO return @@ -89,14 +94,19 @@ class Triggers(GajimPlugin): check_func = self._check_rule_apply_status_changed self._check_all(event, check_func, self._apply_rule) - def _check_all(self, event, check_func, apply_func) -> RuleResult: + def _check_all(self, + event: ProcessableEventsT, + check_func: Callable[..., bool], + apply_func: Callable[..., Any] + ) -> RuleResult: + result = RuleResult() rules_num = [int(item) for item in self.config.keys()] rules_num.sort() - to_remove = [] + to_remove: list[int] = [] for num in rules_num: - rule = self.config[str(num)] + rule = cast(RuleT, self.config[str(num)]) if check_func(event, rule): apply_func(result, rule) if 'one_shot' in rule and rule['one_shot']: @@ -108,7 +118,8 @@ class Triggers(GajimPlugin): if num + decal in to_remove: num2 = num while str(num2 + 1) in self.config: - self.config[str(num2)] = self.config[str(num2 + 1)].copy() + copy = self.config[str(num2 + 1)].copy() # type: ignore + self.config[str(num2)] = copy num2 += 1 del self.config[str(num2)] decal += 1 @@ -120,43 +131,64 @@ class Triggers(GajimPlugin): @log_result def _check_rule_apply_msg_received(self, event: MessageEventsT, - rule: RuleT) -> bool: + rule: RuleT + ) -> bool: + return self._check_rule_all('message_received', event, rule) @log_result - def _check_rule_apply_connected(self, event, rule: RuleT) -> bool: + def _check_rule_apply_connected(self, + event: PresenceReceived, + rule: RuleT + ) -> bool: + return self._check_rule_all('contact_connected', event, rule) @log_result - def _check_rule_apply_disconnected(self, event, rule: RuleT) -> bool: + def _check_rule_apply_disconnected(self, + event: PresenceReceived, + rule: RuleT + ) -> bool: + return self._check_rule_all('contact_disconnected', event, rule) @log_result - def _check_rule_apply_status_changed(self, event, rule: RuleT) -> bool: + def _check_rule_apply_status_changed(self, + event: PresenceReceived, + rule: RuleT + ) -> bool: + return self._check_rule_all('contact_status_change', event, rule) @log_result def _check_rule_apply_notification(self, event: Notification, - rule: RuleT) -> bool: + rule: RuleT + ) -> bool: + # Check notification type notif_type = '' if event.type == 'incoming-message': notif_type = 'message_received' - if event.type == 'pres': - # TODO: - if (event.base_event.old_show < 2 and - event.base_event.new_show > 1): - notif_type = 'contact_connected' - elif (event.base_event.old_show > 1 and - event.base_event.new_show < 2): - notif_type = 'contact_disconnected' - else: - notif_type = 'contact_status_change' + # if event.type == 'pres': + # # TODO: + # if (event.base_event.old_show < 2 and + # event.base_event.new_show > 1): + # notif_type = 'contact_connected' + # elif (event.base_event.old_show > 1 and + # event.base_event.new_show < 2): + # notif_type = 'contact_disconnected' + # else: + # notif_type = 'contact_status_change' return self._check_rule_all(notif_type, event, rule) - def _check_rule_all(self, notif_type: str, event: Any, rule: RuleT) -> bool: + def _check_rule_all(self, + notif_type: str, + event: ProcessableEventsT, + rule: RuleT + ) -> bool: + # Check notification type if rule['event'] != notif_type: return False @@ -181,7 +213,11 @@ class Triggers(GajimPlugin): return True @log_result - def _check_rule_recipients(self, event, rule: RuleT) -> bool: + def _check_rule_recipients(self, + event: ProcessableEventsT, + rule: RuleT + ) -> bool: + rule_recipients = [t.strip() for t in rule['recipients'].split(',')] if rule['recipient_type'] == 'groupchat': if event.jid in rule_recipients: @@ -207,7 +243,11 @@ class Triggers(GajimPlugin): return True @log_result - def _check_rule_status(self, event, rule: RuleT) -> bool: + def _check_rule_status(self, + event: ProcessableEventsT, + rule: RuleT + ) -> bool: + rule_statuses = rule['status'].split() client = app.get_client(event.account) if rule['status'] != 'all' and client.status not in rule_statuses: @@ -216,10 +256,15 @@ class Triggers(GajimPlugin): return True @log_result - def _check_rule_tab_opened(self, event, rule: RuleT) -> bool: + def _check_rule_tab_opened(self, + event: ProcessableEventsT, + rule: RuleT + ) -> bool: + if rule['tab_opened'] == 'both': return True tab_opened = False + assert isinstance(event.jid, JID) if app.window.chat_exists(event.account, event.jid): tab_opened = True if tab_opened and rule['tab_opened'] == 'no': @@ -230,12 +275,17 @@ class Triggers(GajimPlugin): return True @log_result - def _check_rule_has_focus(self, event, rule: RuleT) -> bool: + def _check_rule_has_focus(self, + event: ProcessableEventsT, + rule: RuleT + ) -> bool: + if rule['has_focus'] == 'both': return True if rule['tab_opened'] == 'no': # Does not apply in this case return True + assert isinstance(event.jid, JID) chat_active = app.window.is_chat_active(event.account, event.jid) if chat_active and rule['has_focus'] == 'no': return False @@ -263,7 +313,9 @@ class Triggers(GajimPlugin): def _excecute_notification_rules(self, result: RuleResult, - event: Notification) -> bool: + event: Notification + ) -> bool: + if result.sound is False: event.sound = None diff --git a/triggers/util.py b/triggers/util.py index a200a56..d9f705b 100644 --- a/triggers/util.py +++ b/triggers/util.py @@ -12,20 +12,29 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . +from __future__ import annotations + +from typing import Any +from typing import Callable from typing import Optional +from typing import TYPE_CHECKING import logging from dataclasses import dataclass +if TYPE_CHECKING: + from .triggers import ProcessableEventsT + from .triggers import RuleT + log = logging.getLogger('gajim.p.triggers') -def log_result(func): - def wrapper(self, event, rule): - res = func(self, event, rule) - log.info(f'{event.name} -> {func.__name__} -> {res}') - return res - return wrapper +def log_result(func: Callable[..., Any]) -> Callable[..., bool]: + def wrapper(self: Any, event: ProcessableEventsT, rule: RuleT): + res = func(self, event, rule) + log.info(f'{event.name} -> {func.__name__} -> {res}') + return res + return wrapper @dataclass -- cgit v1.2.3