Skip to content

Commit fe1562d

Browse files
Merge pull request #961 from annie-xd-wang/945-pip-installable-plugins
945 pip installable plugins
2 parents bdd5e8e + 2c10ddd commit fe1562d

File tree

6 files changed

+664
-355
lines changed

6 files changed

+664
-355
lines changed

src/navigate/config/config.py

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,12 @@
3838
from pathlib import Path
3939
from os.path import isfile
4040
from multiprocessing.managers import ListProxy, DictProxy
41-
import inspect
4241

4342
# Third Party Imports
4443
import yaml
4544

4645
# Local Imports
47-
from navigate.tools.common_functions import build_ref_name, load_module_from_file
48-
from navigate.model.features import feature_related_functions
46+
from navigate.tools.common_functions import build_ref_name
4947

5048

5149
def get_navigate_path():
@@ -56,11 +54,6 @@ def get_navigate_path():
5654
-------
5755
str
5856
Path to Navigate home directory.
59-
60-
Examples
61-
--------
62-
>>> get_navigate_path()
63-
'C:\\Users\\username\\AppData\\Local\\.navigate'
6457
"""
6558
if platform.system() == "Windows":
6659
base_directory = os.getenv("LOCALAPPDATA")
@@ -172,14 +165,6 @@ def build_nested_dict(manager, parent_dict, key_name, dict_data):
172165
Name of dictionary to insert
173166
dict_data : dict
174167
Dictionary to insert
175-
176-
Returns
177-
-------
178-
None
179-
180-
Examples
181-
--------
182-
>>> build_nested_dict(manager, parent_dict, key_name, dict_data)
183168
"""
184169
if type(dict_data) != dict and type(dict_data) != list:
185170
parent_dict[key_name] = dict_data
@@ -215,10 +200,6 @@ def update_config_dict(manager, parent_dict, config_name, new_config) -> bool:
215200
-------
216201
bool
217202
True or False
218-
219-
Examples
220-
--------
221-
>>> update_config_dict(manager, parent_dict, config_name, new_config)
222203
"""
223204
if type(new_config) != dict and type(new_config) != list:
224205
file_path = str(new_config)
@@ -1098,23 +1079,3 @@ def verify_configuration(manager, configuration):
10981079
"gui",
10991080
{"channels": {"count": channel_count}},
11001081
)
1101-
1102-
1103-
def set_feature_attributes(plugin_path):
1104-
"""Load feature module and set function attributes.
1105-
1106-
Parameters
1107-
----------
1108-
plugin_path : str
1109-
The path to the plugins folder.
1110-
"""
1111-
features_dir = os.path.join(plugin_path, "model", "features")
1112-
if os.path.exists(features_dir):
1113-
features = os.listdir(features_dir)
1114-
for feature in features:
1115-
feature_file = os.path.join(features_dir, feature)
1116-
if os.path.isfile(feature_file):
1117-
temp = load_module_from_file(feature, feature_file)
1118-
for c in dir(temp):
1119-
if inspect.isclass(getattr(temp, c)):
1120-
setattr(feature_related_functions, c, getattr(temp, c))

src/navigate/controller/sub_controllers/plugins.py

Lines changed: 67 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@
4242
from navigate.config.config import get_navigate_path
4343
from navigate.view.custom_widgets.popup import PopUp
4444
from navigate.tools.file_functions import load_yaml_file, save_yaml_file
45-
from navigate.tools.common_functions import combine_funcs, load_module_from_file
45+
from navigate.tools.common_functions import combine_funcs
4646
from navigate.tools.decorators import AcquisitionMode
47-
from navigate.model.features import feature_related_functions
4847
from navigate.controller.sub_controllers.gui import GUIController
4948
from navigate.view.popups.plugins_popup import PluginsPopup
50-
from navigate.config import set_feature_attributes
49+
50+
from navigate.plugins.plugin_manager import PluginFileManager, PluginPackageManager
5151

5252

5353
class PluginsController:
@@ -69,11 +69,6 @@ def __init__(self, view, parent_controller):
6969
#: object: navigate controller.
7070
self.parent_controller = parent_controller
7171

72-
#: str: plugins default path.
73-
self.plugins_path = os.path.join(
74-
Path(__file__).resolve().parent.parent.parent, "plugins"
75-
)
76-
7772
#: dict: installed plugins with GUI
7873
self.plugins_dict = {}
7974

@@ -87,115 +82,61 @@ def populate_experiment_setting(self):
8782

8883
def load_plugins(self):
8984
"""Load plugins"""
90-
plugins = os.listdir(self.plugins_path)
91-
installed_plugins = dict(
92-
[(f, os.path.join(self.plugins_path, f)) for f in plugins]
85+
# load through files
86+
plugins_path = os.path.join(
87+
Path(__file__).resolve().parent.parent.parent, "plugins"
9388
)
94-
# load plugins from plugins_config
9589
plugins_config_path = os.path.join(
9690
get_navigate_path(), "config", "plugins_config.yml"
9791
)
98-
plugins_config = load_yaml_file(plugins_config_path)
99-
if plugins_config is None:
100-
plugins_config = {}
101-
save_yaml_file(get_navigate_path(), {}, "plugins_config.yml")
102-
else:
103-
for plugin_name, plugin_path in plugins_config.items():
104-
if plugin_path and os.path.exists(plugin_path):
105-
installed_plugins[plugin_name] = plugin_path
106-
for _, plugin_path in installed_plugins.items():
107-
if not os.path.isdir(plugin_path):
108-
continue
92+
plugin_file_manager = PluginFileManager(plugins_path, plugins_config_path)
93+
self.load_plugins_through_manager(plugin_file_manager)
94+
self.load_plugins_through_manager(PluginPackageManager)
95+
96+
def load_plugins_through_manager(self, plugin_manager):
97+
"""Load plugins through plugin manager
98+
99+
Parameters
100+
----------
101+
plugin_manager : object
102+
PluginManager object
103+
"""
104+
plugins = plugin_manager.get_plugins()
105+
106+
for plugin_name, plugin_ref in plugins.items():
109107

110108
# read "plugin_config.yml"
111-
plugin_config = load_yaml_file(
112-
os.path.join(plugin_path, "plugin_config.yml")
113-
)
109+
plugin_config = plugin_manager.load_config(plugin_ref)
114110
if plugin_config is None:
115111
continue
116-
plugin_name = plugin_config.get("name", _)
117-
plugin_file_name = "_".join(plugin_name.lower().split())
118-
plugin_class_name = "".join(plugin_name.title().split())
119-
if "view" in plugin_config:
120-
# verify if "frame_name" and "file_name" are given and correct
121-
view_file = os.path.join(
122-
plugin_path, "view", f"{plugin_file_name}_frame.py"
123-
)
124-
controller_file = os.path.join(
125-
plugin_path,
126-
"controller",
127-
f"{plugin_file_name}_controller.py",
128-
)
129-
if (
130-
os.path.exists(view_file)
131-
and os.path.isfile(view_file)
132-
and os.path.exists(controller_file)
133-
and os.path.isfile(controller_file)
134-
):
135-
plugin_frame_module = load_module_from_file(
136-
f"{plugin_class_name}Frame", view_file
137-
)
138-
if plugin_frame_module is None:
139-
print(
140-
f"Make sure that the plugin frame name "
141-
f"{plugin_class_name} is correct! "
142-
f"Plugin {plugin_name} needs to be uninstalled from "
143-
f"navigate or reinstalled!"
144-
)
145-
continue
146-
plugin_frame = getattr(
147-
plugin_frame_module, f"{plugin_class_name}Frame"
148-
)
149-
plugin_controller_module = load_module_from_file(
150-
f"{plugin_class_name}Controller", controller_file
151-
)
152-
if plugin_controller_module is None:
153-
print(
154-
f"Make sure that the plugin controller "
155-
f"{plugin_class_name} is correct! "
156-
f"Plugin {plugin_name} needs to be uninstalled from "
157-
f"navigate or reinstalled!"
158-
)
159-
continue
160-
plugin_controller = getattr(
161-
plugin_controller_module, f"{plugin_class_name}Controller"
162-
)
112+
plugin_display_name = plugin_config.get("name", plugin_name)
113+
114+
plugin_frame = plugin_manager.load_view(plugin_ref, plugin_display_name)
115+
plugin_controller = plugin_manager.load_controller(
116+
plugin_ref, plugin_display_name
117+
)
163118

164-
if plugin_config["view"] == "Popup":
165-
# menu
166-
self.parent_controller.view.menubar.menu_plugins.add_command(
167-
label=plugin_name,
168-
command=self.build_popup_window(
169-
plugin_name, plugin_frame, plugin_controller
170-
),
171-
)
172-
else:
173-
self.build_tab_window(
119+
if plugin_frame and plugin_controller:
120+
if plugin_config["view"] == "Popup":
121+
# menu
122+
self.parent_controller.view.menubar.menu_plugins.add_command(
123+
label=plugin_name,
124+
command=self.build_popup_window(
174125
plugin_name, plugin_frame, plugin_controller
175-
)
126+
),
127+
)
128+
else:
129+
self.build_tab_window(plugin_name, plugin_frame, plugin_controller)
176130
# feature
177-
set_feature_attributes(plugin_path)
131+
plugin_manager.load_features(plugin_ref)
178132

179133
# acquisition mode
180134
acquisition_modes = plugin_config.get("acquisition_modes", [])
181-
for acquisition_mode_config in acquisition_modes:
182-
acquisition_file = os.path.join(
183-
plugin_path, acquisition_mode_config["file_name"]
184-
)
185-
if os.path.exists(acquisition_file):
186-
module = load_module_from_file(
187-
acquisition_mode_config["file_name"][:-3], acquisition_file
188-
)
189-
acquisition_mode = [
190-
m
191-
for m in dir(module)
192-
if isinstance(getattr(module, m), AcquisitionMode)
193-
]
194-
if acquisition_mode:
195-
self.parent_controller.add_acquisition_mode(
196-
acquisition_mode_config["name"],
197-
getattr(module, acquisition_mode[0]),
198-
)
135+
plugin_manager.load_acquisition_modes(
136+
plugin_ref,
137+
acquisition_modes,
138+
self.register_acquisition_mode,
139+
)
199140

200141
def build_tab_window(self, plugin_name, frame, controller):
201142
"""Build tab for a plugin
@@ -220,11 +161,12 @@ def build_tab_window(self, plugin_name, frame, controller):
220161
"__plugin" + "_".join(plugin_name.lower().split()) + "_controller"
221162
)
222163
self.plugins_dict[controller_name] = plugin_controller
223-
except Exception:
164+
except Exception as e:
224165
messagebox.showwarning(
225166
title="Warning",
226167
message=(
227168
f"Plugin {plugin_name} has something went wrong."
169+
f"Error: {e}"
228170
f"Please make sure the plugin works correctly or uninstall it!"
229171
),
230172
)
@@ -295,6 +237,27 @@ def func_with_wrapper(*args, **kwargs):
295237

296238
return func_with_wrapper
297239

240+
def register_acquisition_mode(self, acquisition_mode_name, module):
241+
"""Register acquisition mode
242+
243+
Parameters
244+
----------
245+
acquisition_mode_name : str
246+
The name of an acquisition mode
247+
module : module
248+
acquisition mode module
249+
"""
250+
if not module:
251+
return
252+
acquisition_mode = [
253+
m for m in dir(module) if isinstance(getattr(module, m), AcquisitionMode)
254+
]
255+
if acquisition_mode:
256+
self.parent_controller.add_acquisition_mode(
257+
acquisition_mode_name,
258+
getattr(module, acquisition_mode[0]),
259+
)
260+
298261

299262
class UninstallPluginController(GUIController):
300263
"""Uninstall plugin controller"""

0 commit comments

Comments
 (0)