Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/lml/loader.py : 65%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2 lml.loader
3 ~~~~~~~~~~~~~~~~~~~
5 Plugin discovery module. It supports plugins installed via pip tools
6 and pyinstaller. :func:`~lml.loader.scan_plugins` is expected to be
7 called in the main package of yours at an earliest time of convenience.
9 :copyright: (c) 2017-2020 by Onni Software Ltd.
10 :license: New BSD License, see LICENSE for more details
11"""
12import re
13import logging
14import pkgutil
15import warnings
16from itertools import chain
18from lml.utils import do_import
20log = logging.getLogger(__name__)
23def scan_plugins(
24 prefix,
25 pyinstaller_path,
26 black_list=None,
27 white_list=None,
28 plugin_name_patterns=None,
29):
30 """
31 Implicitly discover plugins via pkgutil and pyinstaller path
33 Parameters
34 -----------------
36 prefix:string
37 module prefix. This prefix should become the prefix of the module name
38 of all plugins.
40 In the tutorial, robotchef-britishcuisine is a plugin package
41 of robotchef and its module name is 'robotchef_britishcuisine'. When
42 robotchef call scan_plugins to load its cuisine plugins, it specifies
43 its prefix as "robotchef_". All modules that starts with 'robotchef_'
44 will be auto-loaded: robotchef_britishcuisine, robotchef_chinesecuisine,
45 etc.
47 pyinstaller_path:string
48 used in pyinstaller only. When your end developer would package
49 your main library and its plugins using pyinstaller, this path
50 helps pyinstaller to find the plugins.
52 black_list:list
53 a list of module names that should be skipped.
55 white_list:list
56 a list of modules that comes with your main module. If you have a
57 built-in module, the module name should be inserted into the list.
59 For example, robot_cuisine is a built-in module inside robotchef. It
60 is listed in white_list.
61 """
62 __plugin_name_patterns = "^%s.+$" % prefix
63 warnings.warn(
64 "Deprecated! since version 0.0.3. Please use scan_plugins_regex!"
65 )
66 scan_plugins_regex(
67 plugin_name_patterns=__plugin_name_patterns,
68 pyinstaller_path=pyinstaller_path,
69 black_list=black_list,
70 white_list=white_list,
71 )
74def scan_plugins_regex(
75 plugin_name_patterns=None,
76 pyinstaller_path=None,
77 black_list=None,
78 white_list=None,
79):
81 """
82 Implicitly discover plugins via pkgutil and pyinstaller path using
83 regular expression
85 Parameters
86 -----------------
88 plugin_name_patterns: python regular expression
89 it is used to match all your plugins, either it is a prefix,
90 a suffix, some text in the middle or all.
92 pyinstaller_path:string
93 used in pyinstaller only. When your end developer would package
94 your main library and its plugins using pyinstaller, this path
95 helps pyinstaller to find the plugins.
97 black_list:list
98 a list of module names that should be skipped.
100 white_list:list
101 a list of modules that comes with your main module. If you have a
102 built-in module, the module name should be inserted into the list.
104 For example, robot_cuisine is a built-in module inside robotchef. It
105 is listed in white_list.
106 """
107 log.debug("scanning for plugins...")
108 if black_list is None:
109 black_list = []
111 if white_list is None:
112 white_list = []
114 # scan pkgutil.iter_modules
115 module_names = (
116 module_info[1]
117 for module_info in pkgutil.iter_modules()
118 if module_info[2] and re.match(plugin_name_patterns, module_info[1])
119 )
121 # scan pyinstaller
122 module_names_from_pyinstaller = scan_from_pyinstaller(
123 plugin_name_patterns, pyinstaller_path
124 )
126 all_modules = chain(
127 module_names, module_names_from_pyinstaller, white_list
128 )
129 # loop through modules and find our plug ins
130 for module_name in all_modules:
132 if module_name in black_list:
133 log.debug("ignored " + module_name)
134 continue
136 try:
137 do_import(module_name)
138 except ImportError as e:
139 log.debug(module_name)
140 log.debug(e)
141 continue
142 log.debug("scanning done")
145# load modules to work based with and without pyinstaller
146# from: https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py
147# see: https://github.com/pyinstaller/pyinstaller/issues/1905
148# load modules using iter_modules()
149# (should find all plug ins in normal build, but not pyinstaller)
150def scan_from_pyinstaller(plugin_name_patterns, path):
151 """
152 Discover plugins from pyinstaller
153 """
154 table_of_content = set()
155 for a_toc in (
156 importer.toc
157 for importer in map(pkgutil.get_importer, path)
158 if hasattr(importer, "toc")
159 ):
160 table_of_content |= a_toc
162 for module_name in table_of_content:
163 if "." in module_name:
164 continue
165 if re.match(plugin_name_patterns, module_name):
166 yield module_name