from abc import ABC, abstractmethod class XtendRBase(ABC): """Abstract base class for all plugins. Lifecycle: pre_load() -> run() -> stop() -> unload(). XtendRSystem calls unload() exactly once, right before it drops its own references to the plugin instance and removes its module from sys.modules. This is the plugin's only chance to release anything it holds that XtendRSystem doesn't know about and can't clean up on its own, for example: - GObject/GTK signal handler ids from .connect() (disconnect them, or the GObject side keeps a reference to the bound method, which keeps the plugin instance -- and everything it references -- alive). - Widgets the plugin inserted into the app's widget tree (remove them from their parent; a parented widget is kept alive by GTK regardless of what Python does with its own references). - GLib.timeout_add / idle_add source ids (GLib.source_remove them). - Any threads it started that aren't daemon threads, or open files/ sockets/subprocesses. unload() has a default no-op implementation so existing plugins that don't hold any such resources keep working unchanged; override it only when there's something to release. Example: >>> class TestPlugin(XtendRBase): ... def run(self): ... print("Running TestPlugin") ... def stop(self): ... print("Stopping TestPlugin") ... def unload(self): ... print("Unloading TestPlugin") >>> plugin = TestPlugin() >>> plugin.run() Running TestPlugin >>> plugin.stop() Stopping TestPlugin >>> plugin.unload() Unloading TestPlugin """ @abstractmethod def run(self, *args, **kwargs): pass @abstractmethod def stop(self): pass @abstractmethod def pre_load(self, *args): pass def unload(self): """Release any resources the plugin holds. Called once by XtendRSystem.detach(), after stop(). Optional to override; default is a no-op.""" pass