MainModel¶
- class poriscope.models.main_model.MainModel(app_config: Dict[str, Any])¶
Bases:
QObjectApp-shell model: owns app configuration (loaded from/saved to config.json), discovers and holds every available plugin class under poriscope/plugins/ and the user plugin folder, and persists/restores session and tab-action history.
- get_plugin_data(plugin_key: str) Dict[str, Any]¶
Fetches plugin data from the local application data JSON file.
- Args:
plugin_key (str): The key representing the plugin to retrieve data for.
- Returns:
dict: Plugin data if available, otherwise returns an empty dictionary.
- load_plugin(plugin_key: str, folder: str | Path, allowed_base_classes: Tuple[type, ...]) type | None¶
Dynamically loads a plugin, ensuring it is a subclass of a supported abstract class.
- Args:
plugin_key (str): The key representing the plugin to load.
- Returns:
plugin_class (type): The loaded plugin class, or None if loading fails.
- Note:
This method uses dynamic module loading as described in the Python documentation: https://docs.python.org/3/library/importlib.html
The simple-plugin-loader package was initially considered but was found to be unsuitable for on-demand loading as it loads all plugins upon execution: https://pypi.org/project/simple-plugin-loader/
- populate_available_plugins() Tuple[Dict[str, Dict[str, type]], Dict[str, List[str]]]¶
Get a dict of available plugin names, keyed by base class. Each entry in the dict is a list of plugin class names. Built at runtime by searching plugin directories.
- refresh_available_plugins() None¶
Re-scan the plugin directories and replace the cached results.
The scan otherwise runs once, in the constructor, so a user who points the app at a different plugin folder sees no change until the next launch. Each plugin file is loaded fresh via
importlib.util.spec_from_file_location/exec_modulerather than throughsys.modules, so an edited file’s new code is always picked up on the next scan - but that also means every scan hands back a new class object, never the one a previous scan produced. This does not break anything already instantiated: an instance keeps working through its own__class__reference regardless of what this cache holds, it just does not become an instance of the freshly-scanned class - the two are distinct objects until nothing references the older one any more. Files that have since been deleted simply are not walked, and so drop out.Callers are responsible for propagating the new lists - the controllers and the view each hold a copy taken at construction.
- reset_app_config() Dict[str, Any]¶
Restore the three stored settings to their defaults and persist them.
Only
config/config.jsonis touched. Saved sessions, plugin history and log files are left alone.This writes the file and updates the in-memory config, but does not apply the values to anything already running: reverting the parent folder has to reach live data plugins, and reverting the log level has to reconfigure the logger.
MainController.reset_app_configroutes the returned values back through the same paths a manual edit uses, so those side effects are not duplicated here.- Returns:
The defaults that were applied, so the caller can act on them.
- Return type:
Dict[str, Any]
- save_session(plugin_history: Dict[str, Any], save_file: str | Path | None = None) None¶
Write the plugin history to disk as JSON
A write failure is logged rather than raised. Every caller is a Qt slot, and PySide6 does not tolerate an exception escaping a slot invoked from C++, so a read-only or otherwise unwritable destination would take the process down.
except Exceptionrather thanexcept OSErrorbecausejson.dumpalso raisesTypeErrorfor a value it cannot serialize.The level depends on who asked for the save. An autosave -
save_fileisNone, which includes the app-shutdown path - logs at WARNING, whichQtHandlerdoes not raise a dialog for; a modal dialog duringaboutToQuitwould be its own bug. It also emitsadd_text_to_display, so the status panel says autosaving has stopped: the failure is rare and non-blocking, but a user whose work is no longer being persisted needs to know they are operating unprotected. A save to a path the user chose logs at ERROR instead, and therefore does raise a dialog, so that a Save Session which did not happen is not mistaken for one that did.
- save_tab_actions(plugin_history: Dict[str, Any], save_file: str | Path | None = None) None¶
Write the tab action history to disk as JSON
Failures are handled exactly as in
save_session(), and for the same reasons: logged rather than raised because the callers are Qt slots, at WARNING plus a status-panel message for an autosave, and at ERROR for a save to a path the user chose.