Skip to content

Commit

Permalink
Define relevant plugin types for BluemanApplet.Plugins
Browse files Browse the repository at this point in the history
This is rather ugly, especially as it breaks the dynamic nature of the PluginManager and involves a cast, but it is definitely an improvement as it would have caught #1887 early.

There might be some generic way of defining this, like `__getattr__(self, key: Literal[_T]) -> _T` but I did not find a way to actually express something like "literal name for a typevar's value".
  • Loading branch information
cschramm committed Oct 15, 2022
1 parent 30fa6dd commit 8252a68
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 18 deletions.
45 changes: 40 additions & 5 deletions blueman/main/Applet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import Any
from gi.repository import Gio, GLib
import logging
import signal
from typing import Any, cast

from blueman.Functions import *
from blueman.bluez.Manager import Manager
Expand All @@ -8,9 +11,12 @@
from blueman.main.PluginManager import PersistentPluginManager
from blueman.main.DbusService import DbusService
from blueman.plugins.AppletPlugin import AppletPlugin
from gi.repository import Gio, GLib
import logging
import signal
from blueman.plugins.applet.DBusService import DBusService
from blueman.plugins.applet.Menu import Menu
from blueman.plugins.applet.PowerManager import PowerManager
from blueman.plugins.applet.RecentConns import RecentConns
from blueman.plugins.applet.StandardItems import StandardItems
from blueman.plugins.applet.StatusIcon import StatusIcon


class BluemanApplet(Gio.Application):
Expand Down Expand Up @@ -40,7 +46,7 @@ def do_quit(_: object) -> bool:
Gio.BusType.SESSION)
self.DbusSvc.register()

self.Plugins = PersistentPluginManager(AppletPlugin, blueman.plugins.applet, self)
self.Plugins = Plugins(self)
self.Plugins.load_plugin()

for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
Expand Down Expand Up @@ -100,3 +106,32 @@ def on_device_removed(self, _manager: Manager, path: str) -> None:
logging.info(f"Device removed {path}")
for plugin in self.Plugins.get_loaded_plugins(AppletPlugin):
plugin.on_device_removed(path)


class Plugins(PersistentPluginManager):
def __init__(self, applet: BluemanApplet):
super().__init__(AppletPlugin, blueman.plugins.applet, applet)

@property
def DBusService(self) -> DBusService:
return cast(DBusService, self._plugins["DBusService"])

@property
def Menu(self) -> Menu:
return cast(Menu, self._plugins["Menu"])

@property
def PowerManager(self) -> PowerManager:
return cast(PowerManager, self._plugins["PowerManager"])

@property
def RecentConns(self) -> RecentConns:
return cast(RecentConns, self._plugins["RecentConns"])

@property
def StandardItems(self) -> StandardItems:
return cast(StandardItems, self._plugins["StandardItems"])

@property
def StatusIcon(self) -> StatusIcon:
return cast(StatusIcon, self._plugins["StatusIcon"])
18 changes: 9 additions & 9 deletions blueman/main/PluginManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import traceback
import importlib
from types import ModuleType
from typing import Dict, List, Type, TypeVar, Iterable, Optional, Any
from typing import Dict, List, Type, TypeVar, Iterable, Optional

from gi.repository import GObject, Gio

Expand Down Expand Up @@ -32,7 +32,7 @@ def __init__(self, plugin_class: Type[_T], module_path: ModuleType, parent: obje
super().__init__()
self.__deps: Dict[str, List[str]] = {}
self.__cfls: Dict[str, List[str]] = {}
self.__plugins: Dict[str, _T] = {}
self._plugins: Dict[str, _T] = {}
self.__classes: Dict[str, Type[_T]] = {}
self.__loaded: List[str] = []
self.parent = parent
Expand Down Expand Up @@ -168,14 +168,14 @@ def __load_plugin(self, cls: Type[_T]) -> None:
raise # NOTE TO SELF: might cause bugs

else:
self.__plugins[cls.__name__] = inst
self._plugins[cls.__name__] = inst

self.__loaded.append(cls.__name__)
self.emit("plugin-loaded", cls.__name__)

def __getattr__(self, key: str) -> Any:
def __getattr__(self, key: str) -> object:
try:
return self.__plugins[key]
return self._plugins[key]
except KeyError:
return self.__dict__[key]

Expand All @@ -187,26 +187,26 @@ def unload_plugin(self, name: str) -> None:
if name in self.__loaded:
logging.info(f"Unloading {name}")
try:
inst = self.__plugins[name]
inst = self._plugins[name]
inst._unload()
except NotImplementedError:
logging.warning("Plugin cannot be unloaded")
else:
self.__loaded.remove(name)
del self.__plugins[name]
del self._plugins[name]
self.emit("plugin-unloaded", name)

else:
raise Exception(f"Plugin {name} is not unloadable")

def get_plugins(self) -> Dict[str, _T]:
return self.__plugins
return self._plugins

_U = TypeVar("_U")

def get_loaded_plugins(self, protocol: Type[_U]) -> Iterable[_U]:
for name in self.__loaded:
plugin = self.__plugins[name]
plugin = self._plugins[name]
if isinstance(plugin, protocol):
yield plugin

Expand Down
4 changes: 2 additions & 2 deletions blueman/plugins/applet/DisconnectItems.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from gettext import gettext as _
from typing import Any, TYPE_CHECKING
from typing import Any, TYPE_CHECKING, cast, Callable

from blueman.plugins.AppletPlugin import AppletPlugin

Expand Down Expand Up @@ -39,4 +39,4 @@ def _render(self) -> None:
if device["Connected"]:
self._menu.add(self, (25, idx), text=_("Disconnect %s") % device["Alias"],
icon_name="bluetooth-disconnected-symbolic",
callback=lambda dev=device: dev.disconnect())
callback=cast(Callable[[], None], lambda dev=device: dev.disconnect()))
3 changes: 2 additions & 1 deletion blueman/plugins/applet/RecentConns.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from blueman.gui.Notification import Notification
from blueman.Sdp import ServiceUUID
from blueman.plugins.AppletPlugin import AppletPlugin
from blueman.plugins.applet.Menu import MenuItem
from blueman.plugins.applet.PowerManager import PowerManager, PowerStateListener

if TYPE_CHECKING:
Expand Down Expand Up @@ -182,7 +183,7 @@ def _build_menu_item(self, item: "Item") -> "SubmenuItemDict":

def _rebuild_menu(self) -> None:
menu: "Menu" = self.parent.Plugins.Menu
self._mitems = []
self._mitems: List[MenuItem] = []
menu.unregister(self)
menu.add(self, 52, text=_("Recent _Connections"), icon_name="document-open-recent-symbolic",
sensitive=False, callback=lambda: None)
Expand Down
2 changes: 1 addition & 1 deletion blueman/plugins/applet/StatusIcon.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class StatusIcon(AppletPlugin, GObject.GObject):

visible = None

visibility_timeout = None
visibility_timeout: Optional[int] = None

_implementations = None

Expand Down

0 comments on commit 8252a68

Please sign in to comment.