Commit | Line | Data |
---|---|---|
55bb57e0 PP |
1 | # The MIT License (MIT) |
2 | # | |
3 | # Copyright (c) 2017 Philippe Proulx <pproulx@efficios.com> | |
4 | # | |
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | # of this software and associated documentation files (the "Software"), to deal | |
7 | # in the Software without restriction, including without limitation the rights | |
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | # copies of the Software, and to permit persons to whom the Software is | |
10 | # furnished to do so, subject to the following conditions: | |
11 | # | |
12 | # The above copyright notice and this permission notice shall be included in | |
13 | # all copies or substantial portions of the Software. | |
14 | # | |
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
21 | # THE SOFTWARE. | |
22 | ||
23 | from bt2 import utils | |
24 | import bt2.component | |
25 | ||
26 | ||
27 | def plugin_component_class(component_class): | |
28 | if not issubclass(component_class, bt2.component._UserComponent): | |
29 | raise TypeError('component class is not a subclass of a user component class') | |
30 | ||
31 | component_class._bt_plugin_component_class = None | |
32 | return component_class | |
33 | ||
34 | ||
35 | def register_plugin(module_name, name, description=None, author=None, | |
36 | license=None, version=None): | |
37 | import sys | |
38 | ||
39 | if module_name not in sys.modules: | |
40 | raise RuntimeError("cannot find module '{}' in loaded modules".format(module_name)) | |
41 | ||
42 | utils._check_str(name) | |
43 | ||
44 | if description is not None: | |
45 | utils._check_str(description) | |
46 | ||
47 | if author is not None: | |
48 | utils._check_str(author) | |
49 | ||
50 | if license is not None: | |
51 | utils._check_str(license) | |
52 | ||
53 | if version is not None: | |
54 | if not _validate_version(version): | |
55 | raise ValueError('wrong version: expecting a tuple: (major, minor, patch) or (major, minor, patch, extra)') | |
56 | ||
57 | sys.modules[module_name]._bt_plugin_info = _PluginInfo(name, description, | |
58 | author, license, | |
59 | version) | |
60 | ||
61 | ||
62 | def _validate_version(version): | |
63 | if version is None: | |
64 | return True | |
65 | ||
66 | if not isinstance(version, tuple): | |
67 | return False | |
68 | ||
69 | if len(version) < 3 or len(version) > 4: | |
70 | return False | |
71 | ||
72 | if not isinstance(version[0], int): | |
73 | return False | |
74 | ||
75 | if not isinstance(version[1], int): | |
76 | return False | |
77 | ||
78 | if not isinstance(version[2], int): | |
79 | return False | |
80 | ||
81 | if len(version) == 4: | |
82 | if not isinstance(version[3], str): | |
83 | return False | |
84 | ||
85 | return True | |
86 | ||
87 | ||
88 | class _PluginInfo: | |
89 | def __init__(self, name, description, author, license, version): | |
90 | self.name = name | |
91 | self.description = description | |
92 | self.author = author | |
93 | self.license = license | |
94 | self.version = version | |
95 | self.comp_class_addrs = None | |
96 | ||
97 | ||
98 | # called by the BT plugin system | |
99 | def _try_load_plugin_module(path): | |
100 | import importlib.machinery | |
101 | import inspect | |
102 | import hashlib | |
103 | ||
104 | if path is None: | |
105 | raise TypeError('missing path') | |
106 | ||
107 | # In order to load the module uniquely from its path, even from | |
108 | # different files which have the same basename, we hash the path | |
109 | # and prefix with `bt_plugin_`. This is its key in sys.modules. | |
110 | h = hashlib.sha256() | |
111 | h.update(path.encode()) | |
112 | module_name = 'bt_plugin_{}'.format(h.hexdigest()) | |
113 | ||
114 | # try loading the module: any raised exception is catched by the caller | |
115 | mod = importlib.machinery.SourceFileLoader(module_name, path).load_module() | |
116 | ||
117 | # we have the module: look for its plugin info first | |
118 | if not hasattr(mod, '_bt_plugin_info'): | |
119 | raise RuntimeError("missing '_bt_plugin_info' module attribute") | |
120 | ||
121 | plugin_info = mod._bt_plugin_info | |
122 | ||
123 | # search for user component classes | |
124 | def is_user_comp_class(obj): | |
125 | if not inspect.isclass(obj): | |
126 | return False | |
127 | ||
128 | if not hasattr(obj, '_bt_plugin_component_class'): | |
129 | return False | |
130 | ||
131 | return True | |
132 | ||
133 | comp_class_entries = inspect.getmembers(mod, is_user_comp_class) | |
134 | plugin_info.comp_class_addrs = [entry[1].addr for entry in comp_class_entries] | |
135 | return plugin_info |