Coverage for src/robotide/application/pluginloader.py: 87%
54 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-06 10:40 +0100
1# Copyright 2008-2015 Nokia Networks
2# Copyright 2016- Robot Framework Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
16import importlib 1ab
17import importlib.util 1ab
18import inspect 1ab
19import os 1ab
21from ..context import LOG 1ab
22from ..pluginapi import Plugin 1ab
23from .pluginconnector import plugin_factory 1ab
26class PluginLoader(object): 1ab
28 def __init__(self, application, load_dirs, standard_classes): 1ab
29 self._load_errors = []
30 self.plugins = [plugin_factory(application, cls) for cls in standard_classes + self._find_classes(load_dirs)]
31 if self._load_errors: 31 ↛ 32line 31 didn't jump to line 32 because the condition on line 31 was never true
32 LOG.error('\n\n'.join(self._load_errors))
34 def enable_plugins(self): 1ab
35 for p in self.plugins: 1acd
36 p.enable_on_startup() 1acd
38 def _find_classes(self, load_dirs): 1ab
39 classes = []
40 for path in self._find_python_files(load_dirs):
41 for cls in self._import_classes(path):
42 if self._is_plugin_class(path, cls):
43 classes.append(cls)
44 return classes
46 def _is_plugin_class(self, path, cls): 1ab
47 try:
48 return issubclass(cls, Plugin) and cls is not Plugin
49 except Exception as err:
50 msg = "Finding classes from module '%s' failed: %s"
51 self._load_errors.append(msg % (path, err))
53 def _find_python_files(self, load_dirs): 1ab
54 files = []
55 for path in load_dirs:
56 if not os.path.exists(path):
57 continue
58 for filename in os.listdir(path):
59 full_path = os.path.join(path, filename)
60 if filename[0].isalpha() and \
61 os.path.splitext(filename)[1].lower() == ".py":
62 files.append(full_path)
63 elif os.path.isdir(full_path):
64 files.extend(self._find_python_files([full_path]))
65 return files
67 def _import_classes(self, path): 1ab
68 _, filename = os.path.split(path)
69 modulename = os.path.splitext(filename)[0]
70 spec = importlib.util.spec_from_file_location(modulename, path)
71 if spec is None: 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true
72 return []
73 try:
74 m_module = importlib.util.module_from_spec(spec)
75 spec.loader.exec_module(m_module)
76 except Exception as err:
77 self._load_errors.append("Importing plugin module '%s' failed:\n%s"
78 % (path, err))
79 return []
80 return [cls for _, cls in
81 inspect.getmembers(m_module, predicate=inspect.isclass)]