Daniel Brötzmann pushed to branch master at gajim / gajim
Commits: 231ef904 by wurstsalat at 2022-01-24T18:38:28+01:00 Menus: Typing
- - - - - 7807a8f8 by wurstsalat at 2022-01-24T18:38:28+01:00 Preview: Improve Save As dialog on Windows
- - - - - 9d1df09e by wurstsalat at 2022-01-24T18:38:28+01:00 Plugin system: Typing
- - - - -
8 changed files:
- gajim/common/app.py - gajim/common/types.py - gajim/gtk/menus.py - gajim/gtk/preview.py - gajim/plugins/gajimplugin.py - gajim/plugins/gui.py - gajim/plugins/helpers.py - gajim/plugins/pluginmanager.py
Changes:
===================================== gajim/common/app.py ===================================== @@ -75,7 +75,7 @@ window = None # type: MainWindow
ged = ged_module.GlobalEventsDispatcher() # Global Events Dispatcher -plugin_manager = None # Plugins Manager +plugin_manager = cast(types.PluginManagerT, None) # Plugins Manager
class Storage: def __init__(self):
===================================== gajim/common/types.py ===================================== @@ -45,6 +45,8 @@ from gajim.common.settings import Settings from gajim.gtk.css_config import CSSConfig
+ from gajim.plugins.pluginmanager import PluginManager + ContactT = Union[BareContact, ResourceContact, GroupchatContact, @@ -52,6 +54,7 @@
InterfaceT = Union['Interface'] +PluginManagerT = Union['PluginManager']
ConnectionT = Union['Client'] CSSConfigT = Union['CSSConfig']
===================================== gajim/gtk/menus.py ===================================== @@ -206,10 +206,12 @@ def get_account_menu(account: str) -> Gio.Menu:
def build_accounts_menu() -> None: menubar = app.app.get_menubar() + assert isinstance(menubar, Gio.Menu) # Accounts Submenu menu_position = 1
acc_menu = menubar.get_item_link(menu_position, 'submenu') + assert isinstance(acc_menu, Gio.Menu) acc_menu.remove_all()
accounts_list = sorted(app.settings.get_active_accounts()) @@ -261,7 +263,9 @@ def get_encryption_menu(control_id: str, return menu
-def get_conv_action_context_menu(account: str, selected_text: str) -> Gtk.MenuItem: +def get_conv_action_context_menu(account: str, + selected_text: str + ) -> Gtk.MenuItem: selected_text_short = reduce_chars_newlines(selected_text, 10, 1)
action_menu_item = Gtk.MenuItem.new_with_mnemonic(
===================================== gajim/gtk/preview.py ===================================== @@ -198,8 +198,21 @@ def _on_open(self, _menu: Gtk.Menu) -> None: open_file(self._preview.orig_path)
def _on_save_as(self, _menu: Gtk.Menu) -> None: - def _on_ok(target_path): - dirname = Path(target_path).parent + assert self._preview is not None + + def _on_ok(target: str) -> None: + assert self._preview is not None + assert self._preview.orig_path is not None + + target_path = Path(target) + orig_ext = self._preview.orig_path.suffix + new_ext = target_path.suffix + if orig_ext != new_ext: + # Windows file chooser selects the full file name including + # extension. Starting to type will overwrite the extension + # as well. Restore the extension if it's lost. + target_path = target_path.with_suffix(orig_ext) + dirname = target_path.parent if not os.access(dirname, os.W_OK): ErrorDialog( _('Directory '%s' is not writable') % dirname,
===================================== gajim/plugins/gajimplugin.py ===================================== @@ -21,18 +21,18 @@ :license: GPL '''
-from typing import List # pylint: disable=W0611 -from typing import Tuple # pylint: disable=W0611 -from typing import Dict # pylint: disable=W0611 -from typing import Any # pylint: disable=W0611 +from __future__ import annotations + +from typing import Any
import os -import locale import logging import pickle
from gajim.common import configpaths -from gajim.common.types import PluginExtensionPoints # pylint: disable=W0611 +from gajim.common.events import ApplicationEvent +from gajim.common.ged import HandlerFuncT +from gajim.common.types import PluginExtensionPoints
from gajim.plugins.helpers import log from gajim.plugins.gui import GajimPluginConfigDialog @@ -45,13 +45,15 @@ class GajimPlugin: ''' Base class for implementing Gajim plugins. ''' + __path__: str = '' + + name = '' ''' Name of plugin.
Will be shown in plugins management GUI.
- :type: str ''' short_name = '' ''' @@ -59,8 +61,6 @@ class GajimPlugin:
Used for quick identification of plugin.
- :type: str - :todo: decide whether we really need this one, because class name (with module name) can act as such short name ''' @@ -71,15 +71,11 @@ class GajimPlugin: The name that Gajim displays in the encryption menu. Leave empty if the plugin is not an encryption plugin.
- :type: str - ''' version = '' ''' Version of plugin.
- :type: str - :todo: decide how to compare version between each other (which one is higher). Also rethink: do we really need to compare versions of plugins between each other? This would be only useful if we detect @@ -90,16 +86,12 @@ class GajimPlugin: ''' Plugin description.
- :type: str - :todo: should be allow rich text here (like HTML or reStructuredText)? ''' - authors = [] # type: List[str] + authors = '' ''' Plugin authors.
- :type: [] of str - :todo: should we decide on any particular format of author strings? Especially: should we force format of giving author's e-mail? ''' @@ -112,7 +104,7 @@ class GajimPlugin: :todo: should we check whether provided string is valid URI? (Maybe using 'property') ''' - gui_extension_points = {} # type: PluginExtensionPoints + gui_extension_points: PluginExtensionPoints = {} ''' Extension points that plugin wants to connect with and handlers to be used.
@@ -126,7 +118,7 @@ class GajimPlugin: or when extpoint is destroyed and plugin is activate (eg. chat window closed). ''' - config_default_values = {} # type: Dict[str, Tuple[Any, str]] + config_default_values: dict[str, tuple[Any, str]] = {} ''' Default values for keys that should be stored in plug-in config.
@@ -140,7 +132,7 @@ class GajimPlugin:
:type: {} of 2-element tuples ''' - events_handlers = {} + events_handlers: dict[str, tuple[int, HandlerFuncT]] = {} ''' Dictionary with events handlers.
@@ -151,12 +143,10 @@ class GajimPlugin:
:type: {} with 2-element tuples ''' - events = [] + events: list[ApplicationEvent] = [] ''' New network event classes to be registered in Network Events Controller.
- :type: [] of `nec.NetworkIncomingEvent` or `nec.NetworkOutgoingEvent` - subclasses. '''
def __init__(self) -> None: @@ -169,6 +159,7 @@ def __init__(self) -> None: :type: `plugins.plugin.GajimPluginConfig` ''' self.activatable = True + self.active = False self.available_text = '' self.load_config() self.config_dialog = GajimPluginConfigDialog(self) @@ -180,59 +171,59 @@ def save_config(self) -> None: def load_config(self) -> None: self.config.load()
- def __eq__(self, plugin): + def __eq__(self, plugin: object) -> bool: if self.short_name == plugin.short_name: return True
return False
- def __ne__(self, plugin): + def __ne__(self, plugin: object) -> bool: if self.short_name != plugin.short_name: return True
return False
- def local_file_path(self, file_name): + def local_file_path(self, file_name: str) -> str: return os.path.join(self.__path__, file_name)
- def init(self): + def init(self) -> None: pass
- def activate(self): + def activate(self) -> None: pass
- def deactivate(self): + def deactivate(self) -> None: pass
-class GajimPluginConfig(): - def __init__(self, plugin): +class GajimPluginConfig: + def __init__(self, plugin: GajimPlugin) -> None: self.plugin = plugin self.FILE_PATH = (configpaths.get('PLUGINS_CONFIG_DIR') / self.plugin.short_name) - self.data = {} + self.data: dict[str, Any] = {}
- def __getitem__(self, key): + def __getitem__(self, key: str) -> None: if not key in self.data: self.data[key] = self.plugin.config_default_values[key][0] self.save()
return self.data[key]
- def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any) -> None: self.data[key] = value self.save()
- def __delitem__(self, key): + def __delitem__(self, key: str) -> None: del self.data[key] self.save()
- def __contains__(self, key): + def __contains__(self, key: str) -> bool: return key in self.data
def __iter__(self): - for k in self.data.keys(): - yield k + for key in self.data.keys(): + yield key
def keys(self): return self.data.keys() @@ -240,11 +231,11 @@ def keys(self): def items(self): return self.data.items()
- def save(self): + def save(self) -> None: with open(self.FILE_PATH, 'wb') as fd: pickle.dump(self.data, fd)
- def load(self): + def load(self) -> None: if not self.FILE_PATH.is_file(): self.data = {} self.save() @@ -255,23 +246,23 @@ def load(self): except Exception: try: import shelve - s = shelve.open(self.FILE_PATH) - for (k, v) in s.items(): - self.data[k] = v + shelf = shelve.open(str(self.FILE_PATH)) + for (key, value) in shelf.items(): + self.data[key] = value if not isinstance(self.data, dict): raise GajimPluginException - s.close() + shelf.close() self.save() except Exception: - enc = locale.getpreferredencoding() - filename = self.FILE_PATH.decode(enc) + '.bak' + filepath_bak = self.FILE_PATH.with_suffix('bak') log.warning( '%s plugin config file not readable. Saving it as ' '%s and creating a new one', - self.plugin.short_name, filename) - if os.path.exists(self.FILE_PATH + '.bak'): - os.remove(self.FILE_PATH + '.bak') - os.rename(self.FILE_PATH, self.FILE_PATH + '.bak') + self.plugin.short_name, filepath_bak) + if filepath_bak.exists(): + filepath_bak.unlink() + + self.FILE_PATH.rename(f'{self.FILE_PATH}.bak') self.data = {} self.save()
@@ -279,5 +270,6 @@ def load(self): class GajimPluginException(Exception): pass
+ class GajimPluginInitError(GajimPluginException): pass
===================================== gajim/plugins/gui.py ===================================== @@ -21,6 +21,12 @@ :license: GPL '''
+from __future__ import annotations + +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING + import os from enum import IntEnum from enum import unique @@ -31,6 +37,8 @@
from gajim.common import app from gajim.common import ged +from gajim.common.events import PluginAdded +from gajim.common.events import PluginRemoved from gajim.common.exceptions import PluginsystemError from gajim.common.helpers import open_uri from gajim.common.ged import EventHelper @@ -45,6 +53,9 @@ from gajim.gui.builder import get_builder from gajim.gui.util import load_icon_pixbuf
+if TYPE_CHECKING: + from gajim.plugins.gajimplugin import GajimPlugin +
@unique class Column(IntEnum): @@ -56,7 +67,7 @@ class Column(IntEnum):
class PluginsWindow(Gtk.ApplicationWindow, EventHelper): - def __init__(self): + def __init__(self) -> None: Gtk.ApplicationWindow.__init__(self) EventHelper.__init__(self)
@@ -75,8 +86,13 @@ def __init__(self): _('Click to view Gajim's wiki page on how to install plugins ' 'in Flatpak.'))
- self.installed_plugins_model = Gtk.ListStore(object, str, bool, bool, - GdkPixbuf.Pixbuf) + self.installed_plugins_model = Gtk.ListStore( + object, + str, + bool, + bool, + GdkPixbuf.Pixbuf + ) self._ui.installed_plugins_treeview.set_model( self.installed_plugins_model)
@@ -101,7 +117,7 @@ def __init__(self): # connect signal for selection change selection = self._ui.installed_plugins_treeview.get_selection() selection.connect( - 'changed', self._installed_plugins_treeview_selection_changed) + 'changed', self._selection_changed) selection.set_mode(Gtk.SelectionMode.SINGLE)
self._clear_installed_plugin_info() @@ -128,19 +144,21 @@ def __init__(self):
self.show_all()
- def get_notebook(self): + def get_notebook(self) -> Gtk.Notebook: # Used by plugins return self._ui.plugins_notebook
- def _on_key_press(self, _widget, event): + def _on_key_press(self, _widget: Gtk.Widget, event: Gdk.EventKey) -> None: if event.keyval == Gdk.KEY_Escape: self.destroy()
- def _on_destroy(self, *args): + def _on_destroy(self, *args: Any) -> None: self.unregister_events() app.plugin_manager.remove_gui_extension_point('plugin_window', self)
- def _installed_plugins_treeview_selection_changed(self, treeview_selection): + def _selection_changed(self, + treeview_selection: Gtk.TreeSelection + ) -> None: model, iter_ = treeview_selection.get_selected() if iter_: plugin = model.get_value(iter_, Column.PLUGIN) @@ -148,11 +166,11 @@ def _installed_plugins_treeview_selection_changed(self, treeview_selection): else: self._clear_installed_plugin_info()
- def _display_installed_plugin_info(self, plugin): + def _display_installed_plugin_info(self, plugin: GajimPlugin) -> None: self._ui.plugin_name_label.set_text(plugin.name) self._ui.plugin_version_label.set_text(plugin.version) self._ui.plugin_authors_label.set_text(plugin.authors) - markup = '<a href="%s">%s</a>' % (plugin.homepage, plugin.homepage) + markup = f'<a href="{plugin.homepage}">{plugin.homepage}</a>' self._ui.plugin_homepage_linkbutton.set_markup(markup)
if plugin.available_text: @@ -170,7 +188,7 @@ def _display_installed_plugin_info(self, plugin): self._ui.configure_plugin_button.set_sensitive( plugin.config_dialog is not None and plugin.active)
- def _clear_installed_plugin_info(self): + def _clear_installed_plugin_info(self) -> None: self._ui.plugin_name_label.set_text('') self._ui.plugin_version_label.set_text('') self._ui.plugin_authors_label.set_text('') @@ -180,7 +198,7 @@ def _clear_installed_plugin_info(self): self._ui.uninstall_plugin_button.set_sensitive(False) self._ui.configure_plugin_button.set_sensitive(False)
- def _fill_installed_plugins_model(self): + def _fill_installed_plugins_model(self) -> None: pm = app.plugin_manager self.installed_plugins_model.clear() self.installed_plugins_model.set_sort_column_id(1, @@ -195,15 +213,19 @@ def _fill_installed_plugins_model(self): plugin.activatable, icon])
- def _get_plugin_icon(self, plugin): - icon_file = os.path.join(plugin.__path__, os.path.split( - plugin.__path__)[1]) + '.png' + def _get_plugin_icon(self, plugin: GajimPlugin) -> GdkPixbuf.Pixbuf: + icon_file = os.path.join( + plugin.__path__, os.path.split(plugin.__path__)[1]) + '.png' + assert self.def_icon is not None icon = self.def_icon if os.path.isfile(icon_file): icon = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file, 16, 16) return icon
- def _installed_plugin_toggled(self, _cell, path): + def _installed_plugin_toggled(self, + _cell: Gtk.CellRendererToggle, + path: str + ) -> None: is_active = self.installed_plugins_model[path][Column.ACTIVE] plugin = self.installed_plugins_model[path][Column.PLUGIN]
@@ -221,7 +243,7 @@ def _installed_plugin_toggled(self, _cell, path): plugin.config_dialog is not None and not is_active) self.installed_plugins_model[path][Column.ACTIVE] = not is_active
- def _on_configure_plugin(self, _widget): + def _on_configure_plugin(self, _button: Gtk.Button) -> None: selection = self._ui.installed_plugins_treeview.get_selection() model, iter_ = selection.get_selected() if iter_: @@ -238,25 +260,25 @@ def _on_configure_plugin(self, _widget): # XXX: maybe throw exception here? pass
- def _on_uninstall_plugin(self, _widget): + def _on_uninstall_plugin(self, _button: Gtk.ToolButton) -> None: selection = self._ui.installed_plugins_treeview.get_selection() model, iter_ = selection.get_selected() if iter_: plugin = model.get_value(iter_, Column.PLUGIN) try: app.plugin_manager.uninstall_plugin(plugin) - except PluginsystemError as e: + except PluginsystemError as error: WarningDialog(_('Unable to properly remove the plugin'), - str(e), self) + str(error), self) return
- def _on_plugin_removed(self, event): + def _on_plugin_removed(self, event: PluginRemoved) -> None: for row in self.installed_plugins_model: if row[Column.PLUGIN] == event.plugin: self.installed_plugins_model.remove(row.iter) break
- def _on_plugin_added(self, event): + def _on_plugin_added(self, event: PluginAdded) -> None: icon = self._get_plugin_icon(event.plugin) self.installed_plugins_model.append([event.plugin, event.plugin.name, @@ -264,18 +286,18 @@ def _on_plugin_added(self, event): event.plugin.activatable, icon])
- def _on_install_plugin(self, _widget): + def _on_install_plugin(self, _button: Gtk.ToolButton) -> None: if app.is_flatpak(): open_uri('https://dev.gajim.org/gajim/gajim/wikis/help/flathub') return
- def _show_warn_dialog(): + def _show_warn_dialog() -> None: text = _('Archive is malformed') dialog = WarningDialog(text, '', transient_for=self) dialog.set_modal(False) dialog.popup()
- def _on_plugin_exists(zip_filename): + def _on_plugin_exists(zip_filename: str) -> None: def _on_yes(): plugin = app.plugin_manager.install_from_zip(zip_filename, overwrite=True) @@ -293,7 +315,7 @@ def _on_yes(): callback=_on_yes)], transient_for=self).show()
- def _try_install(zip_filename): + def _try_install(zip_filename: str) -> None: try: plugin = app.plugin_manager.install_from_zip(zip_filename) except PluginsystemError as er_type: @@ -302,7 +324,7 @@ def _try_install(zip_filename): _on_plugin_exists(zip_filename) return
- WarningDialog(error_text, '"%s"' % zip_filename, self) + WarningDialog(error_text, f'"{zip_filename}"', self) return if not plugin: _show_warn_dialog() @@ -312,9 +334,10 @@ def _try_install(zip_filename):
class GajimPluginConfigDialog(Gtk.Dialog): - def __init__(self, plugin, **kwargs): - Gtk.Dialog.__init__(self, title='%s %s' % (plugin.name, - _('Configuration')), **kwargs) + def __init__(self, plugin: GajimPlugin, **kwargs: Any) -> None: + Gtk.Dialog.__init__(self, + title='%s %s' % (plugin.name, _('Configuration')), + **kwargs) self.plugin = plugin button = self.add_button('gtk-close', Gtk.ResponseType.CLOSE) button.connect('clicked', self.on_close_button_clicked) @@ -323,14 +346,14 @@ def __init__(self, plugin, **kwargs):
self.init()
- def on_close_dialog(self, widget, data): + def on_close_dialog(self, widget: Gtk.Widget, data: Any) -> bool: self.hide() return True
- def on_close_button_clicked(self, widget): + def on_close_button_clicked(self, widget: Gtk.Button) -> None: self.hide()
- def run(self, parent=None): + def run(self, parent: Optional[PluginsWindow] = None): self.set_transient_for(parent) self.on_run() self.show_all() @@ -338,8 +361,8 @@ def run(self, parent=None): result = super(GajimPluginConfigDialog, self) return result
- def init(self): + def init(self) -> None: pass
- def on_run(self): + def on_run(self) -> None: pass
===================================== gajim/plugins/helpers.py ===================================== @@ -23,7 +23,8 @@
__all__ = ['log', 'log_calls']
-from typing import List +from pathlib import Path +from typing import Optional
import logging import functools @@ -56,7 +57,7 @@ class log_calls: called, even though `log_calls` decorator is used. '''
- def __init__(self, classname=''): + def __init__(self, classname: str = '') -> None: ''' :Keywords: classname : str @@ -117,14 +118,14 @@ def wrapper(*args, **kwargs): return wrapper
-def get_builder(file_name: str, widgets: List[str] = None) -> Builder: +def get_builder(file_name: str, widgets: Optional[list[str]] = None) -> Builder: return Builder(file_name, widgets, domain=plugins_i18n.DOMAIN, gettext_=plugins_i18n._)
-def is_shipped_plugin(path): +def is_shipped_plugin(path: Path) -> bool: base = configpaths.get('PLUGINS_BASE') if not base.exists(): return False
===================================== gajim/plugins/pluginmanager.py ===================================== @@ -21,8 +21,14 @@ :license: GPL '''
+from __future__ import annotations + __all__ = ['PluginManager']
+from typing import Any +from typing import Optional +from typing import Type + import os import sys import zipfile @@ -38,6 +44,7 @@ from gajim.common import app from gajim.common import configpaths from gajim.common import modules +from gajim.common.client import Client from gajim.common.events import PluginAdded from gajim.common.events import PluginRemoved from gajim.common.i18n import _ @@ -48,7 +55,8 @@ from gajim.plugins.helpers import log from gajim.plugins.helpers import GajimPluginActivateException from gajim.plugins.helpers import is_shipped_plugin -from gajim.plugins.gajimplugin import GajimPlugin, GajimPluginException +from gajim.plugins.gajimplugin import GajimPlugin +from gajim.plugins.gajimplugin import GajimPluginException
FIELDS = ('name', @@ -75,7 +83,7 @@ class Plugin: path: Path
@classmethod - def from_manifest(cls, path): + def from_manifest(cls, path: Path) -> Plugin: shipped = is_shipped_plugin(path) manifest = path / 'manifest.ini' if not manifest.exists() and not manifest.is_dir(): @@ -129,7 +137,7 @@ def from_manifest(cls, path): shipped=shipped, path=path)
- def load_module(self): + def load_module(self) -> Optional[Type[GajimPlugin]]: module_path = self.path / '__init__.py' if not module_path.exists(): # On Windows we only ship compiled files @@ -188,7 +196,7 @@ class PluginManager(metaclass=Singleton): '''
def __init__(self): - self.plugins = [] + self.plugins: list[GajimPlugin] = [] ''' Detected plugin classes.
@@ -196,7 +204,7 @@ def __init__(self):
:type: [] of class objects ''' - self.active_plugins = [] + self.active_plugins: list[GajimPlugin] = [] ''' Instance objects of active plugins.
@@ -205,17 +213,17 @@ def __init__(self):
:type: [] of `GajimPlugin` based objects ''' - self.gui_extension_points = {} + self.gui_extension_points: dict[str, Any] = {} ''' Registered GUI extension points. '''
- self.gui_extension_points_handlers = {} + self.gui_extension_points_handlers: dict[str, Any] = {} ''' Registered handlers of GUI extension points. '''
- self.encryption_plugins = {} + self.encryption_plugins: dict[str, GajimPlugin] = {} ''' Registered names with instances of encryption Plugins. ''' @@ -223,20 +231,20 @@ def __init__(self): self.update_plugins() self._load_plugins()
- def update_plugins(self, replace=True, activate=False, plugin_name=None): + def update_plugins(self, + replace: bool = True, + activate: bool = False, + plugin_name: Optional[str] = None + ) -> list[str]: ''' Move plugins from the downloaded folder to the user plugin folder
:param replace: replace plugin files if they already exist. - :type replace: boolean :param activate: load and activate the plugin - :type activate: boolean :param plugin_name: if provided, update only this plugin - :type plugin_name: str :return: list of updated plugins (files have been installed) - :rtype: [] of str ''' - updated_plugins = [] + updated_plugins: list[str] = [] user_dir = configpaths.get('PLUGINS_USER') dl_dir = configpaths.get('PLUGINS_DOWNLOAD') to_update = [plugin_name] if plugin_name else next(os.walk(dl_dir))[1] @@ -264,7 +272,7 @@ def update_plugins(self, replace=True, activate=False, plugin_name=None): self.add_plugin(plugin, activate=True) return updated_plugins
- def init_plugins(self): + def init_plugins(self) -> None: for plugin in self.plugins: if not app.settings.get_plugin_setting(plugin.short_name, 'active'): continue @@ -276,14 +284,18 @@ def init_plugins(self): except GajimPluginActivateException: pass
- def add_plugin(self, plugin, activate=False): + def add_plugin(self, + plugin: Plugin, + activate: bool = False + ) -> Optional[GajimPlugin]: plugin_class = plugin.load_module() if plugin_class is None: return None
if plugin in self.plugins: log.info('Not loading plugin %s v %s. Plugin already loaded.', - plugin.short_name, plugin.version) + plugin.short_name, + plugin.version) return None
try: @@ -307,7 +319,7 @@ def add_plugin(self, plugin, activate=False):
return plugin_obj
- def remove_plugin(self, plugin): + def remove_plugin(self, plugin: GajimPlugin) -> None: ''' removes the plugin from the plugin list and deletes all loaded modules from sys. This way we will have a fresh start when the plugin gets added @@ -332,19 +344,19 @@ def remove_plugin(self, plugin): for module_to_remove in modules_to_remove: del sys.modules[module_to_remove]
- def get_active_plugin(self, plugin_name): + def get_active_plugin(self, plugin_name: str) -> Optional[GajimPlugin]: for plugin in self.active_plugins: if plugin.short_name == plugin_name: return plugin return None
- def get_plugin(self, short_name): + def get_plugin(self, short_name: str) -> Optional[GajimPlugin]: for plugin in self.plugins: if plugin.short_name == short_name: return plugin return None
- def extension_point(self, gui_extpoint_name, *args): + def extension_point(self, gui_extpoint_name: str, *args: Any) -> None: ''' Invokes all handlers (from plugins) for a particular extension point, but doesn't add it to collection for further processing. @@ -353,24 +365,22 @@ def extension_point(self, gui_extpoint_name, *args): plugin. For example after an update.
:param gui_extpoint_name: name of GUI extension point. - :type gui_extpoint_name: str :param args: parameters to be passed to extension point handlers (typically and object that invokes `gui_extension_point`; however, this can be practically anything) :type args: tuple '''
- self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, - *args) + self._execute_all_handlers_of_gui_extension_point( + gui_extpoint_name, *args)
- def gui_extension_point(self, gui_extpoint_name, *args): + def gui_extension_point(self, gui_extpoint_name: str, *args: Any) -> None: ''' Invokes all handlers (from plugins) for particular GUI extension point and adds it to collection for further processing (eg. by plugins not active yet).
:param gui_extpoint_name: name of GUI extension point. - :type gui_extpoint_name: str :param args: parameters to be passed to extension point handlers (typically and object that invokes `gui_extension_point`; however, this can be practically anything) @@ -392,7 +402,10 @@ def gui_extension_point(self, gui_extpoint_name, *args): self._execute_all_handlers_of_gui_extension_point(gui_extpoint_name, *args)
- def remove_gui_extension_point(self, gui_extpoint_name, *args): + def remove_gui_extension_point(self, + gui_extpoint_name: str, + *args: Any + ) -> None: ''' Removes GUI extension point from collection held by `PluginManager`.
@@ -420,7 +433,6 @@ def remove_gui_extension_point(self, gui_extpoint_name, *args): freedom, but is this necessary?
:param gui_extpoint_name: name of GUI extension point. - :type gui_extpoint_name: str :param args: arguments that `PluginManager.gui_extension_point` was called with for this extension point. This is used (along with extension point name) to identify element to be removed. @@ -441,7 +453,10 @@ def remove_gui_extension_point(self, gui_extpoint_name, *args): if disconnect_handler is not None: disconnect_handler(args[0])
- def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args): + def _add_gui_extension_point_call_to_list(self, + gui_extpoint_name: str, + *args: Any + ) -> None: ''' Adds GUI extension point call to list of calls.
@@ -453,8 +468,6 @@ def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args):
:param gui_extpoint_name: GUI extension point name used to identify it by plugins. - :type gui_extpoint_name: str - :param args: parameters to be passed to extension point handlers (typically and object that invokes `gui_extension_point`; however, this can be practically anything) @@ -466,8 +479,10 @@ def _add_gui_extension_point_call_to_list(self, gui_extpoint_name, *args): self.gui_extension_points.setdefault(gui_extpoint_name, []).append( args)
- def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, - *args): + def _execute_all_handlers_of_gui_extension_point(self, + gui_extpoint_name: str, + *args: Any + ) -> None: if gui_extpoint_name in self.gui_extension_points_handlers: for handlers in self.gui_extension_points_handlers[ gui_extpoint_name]: @@ -477,47 +492,49 @@ def _execute_all_handlers_of_gui_extension_point(self, gui_extpoint_name, log.warning('Error executing %s', handlers[0], exc_info=True)
- def _register_events_handlers_in_ged(self, plugin): + def _register_events_handlers_in_ged(self, plugin: GajimPlugin) -> None: for event_name, handler in plugin.events_handlers.items(): priority = handler[0] handler_function = handler[1] - app.ged.register_event_handler(event_name, priority, - handler_function) + app.ged.register_event_handler( + event_name, priority, handler_function)
- def _remove_events_handler_from_ged(self, plugin): + def _remove_events_handler_from_ged(self, plugin: GajimPlugin) -> None: for event_name, handler in plugin.events_handlers.items(): priority = handler[0] handler_function = handler[1] - app.ged.remove_event_handler(event_name, priority, - handler_function) + app.ged.remove_event_handler( + event_name, priority, handler_function)
- def _remove_name_from_encryption_plugins(self, plugin): + def _remove_name_from_encryption_plugins(self, + plugin: GajimPlugin + ) -> None: if plugin.encryption_name: del self.encryption_plugins[plugin.encryption_name]
- def _register_modules_with_handlers(self, plugin): + def _register_modules_with_handlers(self, plugin: GajimPlugin) -> None: if not hasattr(plugin, 'modules'): return - for con in app.connections.values(): + for client in app.connections.values(): for module in plugin.modules: - instance, name = module.get_instance(con) - modules.register_single_module(con, instance, name) + instance, name = module.get_instance(client) + modules.register_single_module(client, instance, name)
for handler in instance.handlers: - con.connection.register_handler(handler) + client.connection.register_handler(handler)
- def _unregister_modules_with_handlers(self, plugin): + def _unregister_modules_with_handlers(self, plugin: GajimPlugin) -> None: if not hasattr(plugin, 'modules'): return - for con in app.connections.values(): + for client in app.connections.values(): for module in plugin.modules: instance = con.get_module(module.name) - modules.unregister_single_module(con, module.name) + modules.unregister_single_module(client, module.name)
for handler in instance.handlers: - con.connection.unregister_handler(handler) + client.connection.unregister_handler(handler)
- def activate_plugin(self, plugin): + def activate_plugin(self, plugin: GajimPlugin) -> None: ''' :param plugin: plugin to be activated :type plugin: class object of `GajimPlugin` subclass @@ -539,7 +556,7 @@ def activate_plugin(self, plugin): app.settings.set_plugin_setting(plugin.short_name, 'active', True) plugin.active = True
- def deactivate_plugin(self, plugin): + def deactivate_plugin(self, plugin: GajimPlugin) -> None: # remove GUI extension points handlers (provided by plug-in) from # handlers list for gui_extpoint_name, gui_extpoint_handlers in \ @@ -573,17 +590,21 @@ def deactivate_plugin(self, plugin): app.settings.set_plugin_setting(plugin.short_name, 'active', False) plugin.active = False
- def _add_gui_extension_points_handlers_from_plugin(self, plugin): + def _add_gui_extension_points_handlers_from_plugin(self, + plugin: GajimPlugin + ) -> None: for gui_extpoint_name, gui_extpoint_handlers in \ plugin.gui_extension_points.items(): - self.gui_extension_points_handlers.setdefault(gui_extpoint_name, - []).append(gui_extpoint_handlers) + self.gui_extension_points_handlers.setdefault( + gui_extpoint_name, []).append(gui_extpoint_handlers)
- def _add_encryption_name_from_plugin(self, plugin): + def _add_encryption_name_from_plugin(self, plugin: GajimPlugin) -> None: if plugin.encryption_name: self.encryption_plugins[plugin.encryption_name] = plugin
- def _handle_all_gui_extension_points_with_plugin(self, plugin): + def _handle_all_gui_extension_points_with_plugin(self, + plugin: GajimPlugin + ) -> None: for gui_extpoint_name, gui_extpoint_handlers in \ plugin.gui_extension_points.items(): if gui_extpoint_name in self.gui_extension_points: @@ -597,7 +618,7 @@ def _handle_all_gui_extension_points_with_plugin(self, plugin): log.warning('Error executing %s', handler, exc_info=True)
- def register_modules_for_account(self, con): + def register_modules_for_account(self, client: Client) -> None: ''' A new account has been added, register modules of all active plugins @@ -610,21 +631,22 @@ def register_modules_for_account(self, con): continue
for module in plugin.modules: - instance, name = module.get_instance(con) - modules.register_single_module(con, instance, name) + instance, name = module.get_instance(client) + modules.register_single_module(client, instance, name)
for handler in instance.handlers: - con.connection.register_handler(handler) + client.connection.register_handler(handler)
@staticmethod - def _load_plugin(plugin_path): + def _load_plugin(plugin_path: Path) -> Optional[Plugin]: try: return Plugin.from_manifest(plugin_path) except Exception as error: log.warning(error) + return None
- def _load_plugins(self): - plugins = {} + def _load_plugins(self) -> None: + plugins: dict[str, Plugin] = {} for plugin_dir in configpaths.get_plugin_dirs(): if not plugin_dir.is_dir(): continue @@ -646,7 +668,10 @@ def _load_plugins(self): for plugin in plugins.values(): self.add_plugin(plugin)
- def install_from_zip(self, zip_filename, overwrite=None): + def install_from_zip(self, + zip_filename: str, + overwrite: bool = False + ) -> Optional[GajimPlugin]: ''' Install plugin from zip and return plugin ''' @@ -662,11 +687,11 @@ def install_from_zip(self, zip_filename, overwrite=None): # CRC error raise PluginsystemError(_('Archive corrupted'))
- dirs = [] + dirs: list[str] = [] manifest = None for filename in zip_file.namelist(): - if filename.startswith('.') or filename.startswith('/') or \ - ('/' not in filename): + if (filename.startswith('.') or filename.startswith('/') or + ('/' not in filename)): # members not safe raise PluginsystemError(_('Archive is malformed')) if filename.endswith('/') and filename.find('/', 0, -1) < 0: @@ -686,7 +711,9 @@ def install_from_zip(self, zip_filename, overwrite=None): # Plugin dir already exists if not overwrite: raise PluginsystemError(_('Plugin already exists')) - self.uninstall_plugin(self.get_plugin_by_path(str(plugin_path))) + plugin = self.get_plugin_by_path(str(plugin_path)) + assert isinstance(plugin, GajimPlugin) + self.uninstall_plugin(plugin)
zip_file.extractall(user_dir) zip_file.close() @@ -699,7 +726,7 @@ def install_from_zip(self, zip_filename, overwrite=None):
return self.add_plugin(plugin)
- def delete_plugin_files(self, plugin_path): + def delete_plugin_files(self, plugin_path: Path) -> None: def on_error(func, path, error): if func is os.path.islink: # if symlink @@ -710,7 +737,7 @@ def on_error(func, path, error):
rmtree(plugin_path, False, on_error)
- def uninstall_plugin(self, plugin): + def uninstall_plugin(self, plugin: GajimPlugin) -> None: ''' Deactivate and remove plugin from `plugins` list ''' @@ -718,17 +745,17 @@ def uninstall_plugin(self, plugin): return
self.remove_plugin(plugin) - self.delete_plugin_files(plugin.__path__) + self.delete_plugin_files(Path(plugin.__path__)) if not is_shipped_plugin(Path(plugin.__path__)): path = configpaths.get('PLUGINS_BASE') / plugin.short_name if path.exists(): - self.delete_plugin_files(str(path)) + self.delete_plugin_files(path)
app.settings.remove_plugin(plugin.short_name)
app.ged.raise_event(PluginRemoved(plugin=plugin))
- def get_plugin_by_path(self, plugin_dir): + def get_plugin_by_path(self, plugin_dir: str) -> Optional[GajimPlugin]: for plugin in self.plugins: if plugin.__path__ in plugin_dir: return plugin
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/ef52d49b2801eae4317a08fa7fdb7c25...