|
@@ -1,346 +0,0 @@
|
1
|
|
-import types
|
2
|
|
-import sys
|
3
|
|
-import imp
|
4
|
|
-import importlib.machinery, importlib.abc
|
5
|
|
-
|
6
|
|
-import re
|
7
|
|
-import warnings
|
8
|
|
-
|
9
|
|
-from .exceptions import MultiSiteIdentifierError
|
10
|
|
-
|
11
|
|
-##@brief Constant name of the package containing all sites module
|
12
|
|
-SITE_PACKAGE = 'lodelsites'
|
13
|
|
-##@brief Stores all the lodel sites modules indexed by site identifier
|
14
|
|
-lodel_site_packages = dict()
|
15
|
|
-##@brief Will stores the module object of the SITE_PACKAGE
|
16
|
|
-_lodel_site_root_package = None
|
17
|
|
-##@brief Stores the site identifier validation re
|
18
|
|
-_site_identifier_re = None
|
19
|
|
-
|
20
|
|
-
|
21
|
|
-SITE_PACKAGE_STRUCT = {
|
22
|
|
- 'modules': {
|
23
|
|
- 'auth': {
|
24
|
|
-
|
25
|
|
- },
|
26
|
|
- 'settings': {
|
27
|
|
-
|
28
|
|
- },
|
29
|
|
- 'logger': {
|
30
|
|
-
|
31
|
|
- },
|
32
|
|
- 'plugin': {
|
33
|
|
- 'modules': {
|
34
|
|
- 'hooks': {
|
35
|
|
- 'classes': [ 'DecoratedWrapper', 'LodelHook' ]
|
36
|
|
- },
|
37
|
|
- 'plugins': {
|
38
|
|
- 'classes': [
|
39
|
|
- 'PluginVersion', 'MetaPlugType', 'Plugin',
|
40
|
|
- 'CustomMethod']
|
41
|
|
- },
|
42
|
|
- 'interface': {
|
43
|
|
- 'classes': [ 'InterfacePlugin']
|
44
|
|
- },
|
45
|
|
- 'extensions': {
|
46
|
|
- 'classes': [ 'Extension']
|
47
|
|
- }
|
48
|
|
- },
|
49
|
|
- }
|
50
|
|
- },
|
51
|
|
- 'classes': {}
|
52
|
|
-}
|
53
|
|
-
|
54
|
|
-class LodelSiteModuleSpec(importlib.machinery.ModuleSpec):
|
55
|
|
-
|
56
|
|
- ##@brief Add a site_id attribute to ModuleSpec object
|
57
|
|
- #
|
58
|
|
- #@param name str : fully qualified module name
|
59
|
|
- #@param loader : module loader. Child of importlib.abc.Loader and in our
|
60
|
|
- #case mostly CustomModuleLoader instances
|
61
|
|
- #@param parent str : parent absolute package name. Will be used to set
|
62
|
|
- #new module __package___ attribute
|
63
|
|
- #@param origin None : None because no source code
|
64
|
|
- #@param loader_state ? <- leave None
|
65
|
|
- #@param is_package bool : <- useless, can be deleted
|
66
|
|
- #
|
67
|
|
- #@see https://docs.python.org/3.4/library/importlib.html#importlib.machinery.ModuleSpec
|
68
|
|
- def __init__(self, name, loader, *,
|
69
|
|
- parent=None, origin=None, loader_state=None, is_package=None,
|
70
|
|
- site_id = None):
|
71
|
|
- super().__init__(name=name, loader=loader, origin=origin,
|
72
|
|
- loader_state = loader_state)
|
73
|
|
- ##@brief Stores the parent package fullname
|
74
|
|
- self.parent_package = parent
|
75
|
|
- ##@brief Stores the module site_id
|
76
|
|
- self.site_id = None
|
77
|
|
- self._is_package = False if is_package is None else True
|
78
|
|
- if not site_id is None:
|
79
|
|
- self.site_id = site_id
|
80
|
|
-
|
81
|
|
- def __str__(self):
|
82
|
|
- res = super().__str__()
|
83
|
|
- res = res[:-1] +", site_id = %s, parent = %s)" % (
|
84
|
|
- self.site_id, repr(self.parent))
|
85
|
|
- return res
|
86
|
|
-
|
87
|
|
-
|
88
|
|
-##@brief Custom class of module Loader
|
89
|
|
-#
|
90
|
|
-#Designed to handle dynamic module creation for lodel2 sites
|
91
|
|
-#@see https://docs.python.org/3.4/library/importlib.html#importlib.abc.Loader
|
92
|
|
-#@see https://docs.python.org/3.4/library/types.html#types.ModuleType
|
93
|
|
-class LodelSiteModuleLoader(importlib.abc.Loader):
|
94
|
|
-
|
95
|
|
- ##@brief Handles module creation
|
96
|
|
- #@param spec ModuleSpec instance
|
97
|
|
- #@return The newly created module
|
98
|
|
- def create_module(self, spec):
|
99
|
|
- #Here we do not want to import but get the module object of the parent
|
100
|
|
- #to store it's reference and set the new module as attribute
|
101
|
|
- print("CREATE_MODULE debug : ", spec)
|
102
|
|
- if spec.parent_package is not None:
|
103
|
|
- print("PARENT NAMe ;", spec.parent_package)
|
104
|
|
- parent_module = importlib.import_module(spec.parent_package)
|
105
|
|
- if hasattr(parent_module, spec.name):
|
106
|
|
- warnings.warn("Overloading an existing module attribute will \
|
107
|
|
-creating %s module" % spec.name)
|
108
|
|
- else:
|
109
|
|
- parent_module = None
|
110
|
|
-
|
111
|
|
- res = types.ModuleType(spec.name)
|
112
|
|
-
|
113
|
|
- root_pkg_name = globals()['SITE_PACKAGE']
|
114
|
|
-
|
115
|
|
- res.__spec__ = spec
|
116
|
|
- res.__name__ = spec.name
|
117
|
|
- res.__loader__ = self
|
118
|
|
- res.__package__ = spec.parent_package
|
119
|
|
- res.__path__ = [] if spec._is_package else None
|
120
|
|
- if spec.site_id is not None:
|
121
|
|
- res.__site_id__ = spec.site_id
|
122
|
|
- if parent_module is not None:
|
123
|
|
- rel_name = spec.name.split('.')[-1]
|
124
|
|
- setattr(parent_module, rel_name, res)
|
125
|
|
- #sys.modules[fullname] = res
|
126
|
|
- sys.modules[spec.name] = res
|
127
|
|
- print("INFO : module %s loaded" % spec.name)
|
128
|
|
- print("INFO current state on sys.modules : ", [ mname for mname in sys.modules.keys() if mname .startswith('lodelsites.')])
|
129
|
|
- self.__dyn_module__ = res
|
130
|
|
- return res
|
131
|
|
-
|
132
|
|
- def load_module(self, fullname):
|
133
|
|
- if self.__dyn_module__.__name__ != fullname:
|
134
|
|
- raise MultiSiteError("The name given to load_module do not match \
|
135
|
|
-the name of the handled module...")
|
136
|
|
- sys.modules[fullname] = self.__dyn_module__
|
137
|
|
- print("INFO : module %s loaded" % fullname)
|
138
|
|
-
|
139
|
|
-
|
140
|
|
-##@brief Custom metapath finder that is able to handle our
|
141
|
|
-#dynamically created modules
|
142
|
|
-class LodelSiteMetaPathFinder(importlib.abc.MetaPathFinder):
|
143
|
|
-
|
144
|
|
- def find_spec(fullname, path, target = None):
|
145
|
|
- print("FINDSPEC CALLEd : ", fullname, path)
|
146
|
|
- if not fullname.startswith(globals()['SITE_PACKAGE']):
|
147
|
|
- return None
|
148
|
|
- n_spl = fullname.split('.')
|
149
|
|
- site_id = n_spl[1]
|
150
|
|
- res_mod = get_site_module(site_id)
|
151
|
|
- print("Begin to walk in submodules. Starting from ", res_mod.__name__)
|
152
|
|
- print("DEBUG RESMOD : ", res_mod.__name__, dir(res_mod))
|
153
|
|
- for nxt in n_spl[2:]:
|
154
|
|
- res_mod = getattr(res_mod, nxt)
|
155
|
|
- print("Real result : ", res_mod.__name__, res_mod)
|
156
|
|
- return res_mod.__spec__
|
157
|
|
-
|
158
|
|
-
|
159
|
|
-##@brief Simple getter using site identifier
|
160
|
|
-def get_site_module(identifier):
|
161
|
|
- glob_pkg = globals()['lodel_site_packages']
|
162
|
|
- if identifier not in glob_pkg:
|
163
|
|
- raise MultiSiteIdentifierError(
|
164
|
|
- "No site identified by '%s'" % identifier)
|
165
|
|
- return glob_pkg[identifier]
|
166
|
|
-
|
167
|
|
-
|
168
|
|
-##@brief Create a new site module with given site identifier
|
169
|
|
-#@param identifier str : site identifier
|
170
|
|
-def new_site_module(identifier):
|
171
|
|
- new_module_fullname = globals()['SITE_PACKAGE'] + '.' + identifier
|
172
|
|
- new_modules = _new_site_module(
|
173
|
|
- identifier, new_module_fullname, globals()['SITE_PACKAGE_STRUCT'],
|
174
|
|
- parent = globals()['_lodel_site_root_package'])
|
175
|
|
- new_mod = new_modules[0] #fetching root module
|
176
|
|
- globals()['lodel_site_packages'][identifier] = new_mod
|
177
|
|
- setattr(globals()['_lodel_site_root_package'], identifier, new_mod)
|
178
|
|
-
|
179
|
|
- for mod in new_modules:
|
180
|
|
- print("Call reload on : ", mod)
|
181
|
|
- imp.reload(mod)
|
182
|
|
- for n in dir(mod):
|
183
|
|
- v = getattr(mod, n)
|
184
|
|
- if isinstance(v, types.ModuleType):
|
185
|
|
- print("\t%s : %s" % (n, getattr(mod, n)))
|
186
|
|
- return new_mod
|
187
|
|
-
|
188
|
|
-
|
189
|
|
-##@brief Create a new site module (part of a site package)
|
190
|
|
-#
|
191
|
|
-#@note module informations are expected to be part of SITE_PACKAGE_STRUCT
|
192
|
|
-#@note reccursiv function
|
193
|
|
-#
|
194
|
|
-#@param identifier str
|
195
|
|
-#@param module_name str
|
196
|
|
-#@param module_infos dict
|
197
|
|
-#@param parent : modul object
|
198
|
|
-#@param all_mods list : in/out accumulator for reccursiv calls allowing to
|
199
|
|
-#return the list of all created modules
|
200
|
|
-#
|
201
|
|
-#@return the created module
|
202
|
|
-def _new_site_module(identifier, module_name, module_infos, parent,
|
203
|
|
- mod_acc = None):
|
204
|
|
- mod_acc = list() if mod_acc is None else mod_acc
|
205
|
|
-
|
206
|
|
- print("Rec debug : ", identifier, module_name, module_infos, parent)
|
207
|
|
- identifier = identifier_validation(identifier)
|
208
|
|
-
|
209
|
|
- if parent is None:
|
210
|
|
- parent_name = None
|
211
|
|
- else:
|
212
|
|
- parent_name = parent.__name__
|
213
|
|
-
|
214
|
|
- res = _module_from_spec(name = module_name, parent = parent_name,
|
215
|
|
- site_id = identifier)
|
216
|
|
- orig_modname = _original_name_from_module(res)
|
217
|
|
- if len(mod_acc) == 0:
|
218
|
|
- #we just created a site root package. Because we will reimport
|
219
|
|
- #parent modules when creating submodules we have to insert the
|
220
|
|
- #site root package NOW to the site_root_packages
|
221
|
|
- print("WARNING : inserting module as site root package : ", res)
|
222
|
|
- globals()['lodel_site_packages'][identifier] = res
|
223
|
|
- mod_acc.append(res) #Add created module to accumulator asap
|
224
|
|
-
|
225
|
|
- orig_mod = importlib.import_module(orig_modname)
|
226
|
|
- print("ORIG MOD = ", orig_mod)
|
227
|
|
- if 'classes' in module_infos:
|
228
|
|
- for cname in module_infos['classes']:
|
229
|
|
- orig_cls = getattr(orig_mod, cname)
|
230
|
|
- res_cls = onthefly_child_class(cname, orig_cls, parent)
|
231
|
|
- setattr(res, cname, res_cls)
|
232
|
|
- #child modules creation
|
233
|
|
- if 'modules' in module_infos:
|
234
|
|
- for mname in module_infos['modules']:
|
235
|
|
- new_mname = module_name + '.' + mname
|
236
|
|
- print("DEBUG NAME ON REC CALL : ", new_mname)
|
237
|
|
- submod = _new_site_module(
|
238
|
|
- identifier, new_mname, module_infos['modules'][mname],
|
239
|
|
- parent = res, mod_acc = mod_acc)
|
240
|
|
- submod = submod[-1]
|
241
|
|
- #setattr(res, mname, submod) #done in create_module
|
242
|
|
- return mod_acc
|
243
|
|
-
|
244
|
|
-##@brief Validate a site identifier
|
245
|
|
-#@param identifier str
|
246
|
|
-#@return the identifier
|
247
|
|
-#@throw MultiSiteIdentifierError on invalid id
|
248
|
|
-def identifier_validation(identifier):
|
249
|
|
- re_str = r'^[a-zA-Z][a-zA-Z0-9-]*$'
|
250
|
|
- _site_identifier_re = globals()['_site_identifier_re']
|
251
|
|
- if _site_identifier_re is None:
|
252
|
|
- _site_identifier_re = re.compile(re_str)
|
253
|
|
- globals()['_site_identifier_re'] = _site_identifier_re
|
254
|
|
- if not _site_identifier_re.match(identifier):
|
255
|
|
- raise MultiSiteIdentifierError("Excpected an identifier that matches \
|
256
|
|
-r'%s', but got '%s'" % (re_str, identifier))
|
257
|
|
- return identifier
|
258
|
|
-
|
259
|
|
-
|
260
|
|
-##@brief Create new root package for a lodel site
|
261
|
|
-#@param identifer str : the site identifier
|
262
|
|
-def new_site_root_package(identifier):
|
263
|
|
- identifier_validation(identifier)
|
264
|
|
- if identifier in _lodel_site_root_package:
|
265
|
|
- raise NameError("A site identified by '%s' allready exists")
|
266
|
|
- module_name = identifier
|
267
|
|
- res = _module_from_spec(
|
268
|
|
- name = identifier, parent = globals()['SITE_PACKAGE'])
|
269
|
|
- _lodel_site_root_packages[identifier] = res
|
270
|
|
- return res
|
271
|
|
-
|
272
|
|
-##@brief Create a new child class on the fly
|
273
|
|
-#@param identifier str : site identifier
|
274
|
|
-#@param original_cls class : the original class (will be the single base class
|
275
|
|
-#of our dynamically created class)
|
276
|
|
-#@param parent_module module object : the module designed to contains the class
|
277
|
|
-#@return the created class
|
278
|
|
-def onthefly_child_class(identifier, original_cls, parent_module):
|
279
|
|
- def ns_callback(ns):
|
280
|
|
- ns['__module__'] = parent_module.__name__
|
281
|
|
- ns['__site_id__'] = identifier
|
282
|
|
- res = types.new_class(original_cls.__name__, (original_cls,), None,
|
283
|
|
- ns_callback)
|
284
|
|
- setattr(parent_module, original_cls.__name__, res)
|
285
|
|
- return res
|
286
|
|
-
|
287
|
|
-##@brief Module initialisation function
|
288
|
|
-#
|
289
|
|
-#Takes care to create the lodel_site_package module object
|
290
|
|
-def init_module():
|
291
|
|
- #Creating the package that contains all site packages
|
292
|
|
- site_pkg_name = globals()['SITE_PACKAGE']
|
293
|
|
- res = _module_from_spec(name = site_pkg_name)
|
294
|
|
- globals()['_lodel_site_root_package'] = res
|
295
|
|
- sys.modules[site_pkg_name] = res
|
296
|
|
- #Add our custom metapathfinder
|
297
|
|
- sys.meta_path = [LodelSiteMetaPathFinder] + sys.meta_path
|
298
|
|
- return res
|
299
|
|
-
|
300
|
|
-
|
301
|
|
-##@brief Utility function that takes LodelSiteModuleSpec __init__ arguments
|
302
|
|
-#as parameter and handles the module creation
|
303
|
|
-#@return A newly created module according to given arguments
|
304
|
|
-def _module_from_spec(name, parent = None, origin = None, loader_state = None,
|
305
|
|
- is_package = None, site_id = None):
|
306
|
|
- loader = LodelSiteModuleLoader()
|
307
|
|
- spec = LodelSiteModuleSpec(name = name, parent = parent, origin = origin,
|
308
|
|
- loader_state = None, loader = loader, is_package = is_package,
|
309
|
|
- site_id = site_id)
|
310
|
|
-
|
311
|
|
- return loader.create_module(spec)
|
312
|
|
-
|
313
|
|
-
|
314
|
|
-##@brief Replace all lodel modules references by references on dynamically
|
315
|
|
-#modules
|
316
|
|
-#@param mod ModuleType : the module to update
|
317
|
|
-#@return the modified module
|
318
|
|
-def _module_update_globals(mod):
|
319
|
|
- return None
|
320
|
|
- print("DEBUG : ", mod.__name__, dir(mod), mod.__dir__())
|
321
|
|
- site_id = mod.__site_id__
|
322
|
|
- lodel_site_package = get_site_module(mod.__site_id__)
|
323
|
|
- for kname, val in mod.__globals__:
|
324
|
|
- if isinstance(val, types.ModuleType) and \
|
325
|
|
- val.__package__.startswith('lodel'):
|
326
|
|
- #we have to replace the module reference
|
327
|
|
- fullname = "%s.%s" % (val.__package__, val.__name__)
|
328
|
|
- walkthrough = fullname.split('.')[1:]
|
329
|
|
- repl = lodel_site_package
|
330
|
|
- for submod in walkthrough:
|
331
|
|
- repl = getattr(repl, submod)
|
332
|
|
- mod.__globals__[kname] = repl
|
333
|
|
- return mod
|
334
|
|
-
|
335
|
|
-
|
336
|
|
-##@brief Build the original fully quilified module name given a module
|
337
|
|
-#@warning Behavior is totally hardcoded given the lodel2 architecture
|
338
|
|
-#@param mod
|
339
|
|
-#@return a fully qualified module name
|
340
|
|
-def _original_name_from_module(mod):
|
341
|
|
- print("DEBUG MODNAME : ", mod, mod.__name__, mod.__package__)
|
342
|
|
- spl = mod.__name__.split('.')
|
343
|
|
- if len(spl) <= 2:
|
344
|
|
- return "lodel"
|
345
|
|
- return "lodel.%s" % ('.'.join(spl[2:]))
|
346
|
|
-
|