8
8
import logging
9
9
import sys
10
10
from abc import abstractmethod
11
- from functools import reduce
12
- from importlib .machinery import PathFinder
13
11
from pathlib import Path
14
12
from subprocess import run
15
13
from tempfile import TemporaryDirectory
30
28
from variantlib .models .provider import ProviderConfig
31
29
from variantlib .models .provider import VariantFeatureConfig
32
30
from variantlib .plugins .py_envs import INSTALLER_PYTHON_ENVS
33
- from variantlib .plugins .py_envs import ISOLATED_PYTHON_ENVS
34
31
from variantlib .plugins .py_envs import BasePythonEnv
35
32
from variantlib .plugins .py_envs import ExternalNonIsolatedPythonEnv
36
33
from variantlib .protocols import PluginType
@@ -83,8 +80,7 @@ def _call_subprocess(plugin_apis: list[str], commands: dict[str, Any]) -> Any:
83
80
class BasePluginLoader :
84
81
"""Load and query plugins"""
85
82
86
- _namespace_map : dict [str , str ]
87
- _plugins : dict [str , PluginType ] | None = None
83
+ _namespace_map : dict [str , str ] | None = None
88
84
_python_ctx : BasePythonEnv | None = None
89
85
90
86
def __init__ (self , python_ctx : BasePythonEnv | None = None ) -> None :
@@ -99,8 +95,7 @@ def __enter__(self) -> Self:
99
95
return self
100
96
101
97
def __exit__ (self , * args : object ) -> None :
102
- self ._plugins = None
103
- self ._namespace_map = {}
98
+ self ._namespace_map = None
104
99
105
100
if self ._python_ctx is None :
106
101
logger .warning ("The Python installer is None. Should not happen." )
@@ -123,9 +118,11 @@ def _install_all_plugins_from_reqs(self, reqs: list[str]) -> None:
123
118
# Actual plugin installation
124
119
self ._python_ctx .install (reqs )
125
120
126
- def _load_plugin (self , plugin_api : str ) -> PluginType :
121
+ def load_plugin (self , plugin_api : str ) -> str :
127
122
"""Load a specific plugin"""
128
123
124
+ if self ._namespace_map is None :
125
+ self ._namespace_map = {}
129
126
if self ._python_ctx is None :
130
127
raise RuntimeError ("Impossible to load plugins outside a Python Context" )
131
128
@@ -134,35 +131,8 @@ def _load_plugin(self, plugin_api: str) -> PluginType:
134
131
)
135
132
import_name : str = plugin_api_match .group ("module" )
136
133
attr_path : str = plugin_api_match .group ("attr" )
137
- # make sure to normalize it
138
- subprocess_namespace_map = _call_subprocess (
139
- [f"{ import_name } :{ attr_path } " ], {"namespaces" : {}}
140
- )["namespaces" ]
141
-
142
- try :
143
- if isinstance (self ._python_ctx , ISOLATED_PYTHON_ENVS ):
144
- # We need to load the module first to allow `importlib` to find it
145
- pkg_name = import_name .split ("." , maxsplit = 1 )[0 ]
146
- spec = PathFinder .find_spec (
147
- pkg_name , path = [str (self ._python_ctx .package_dir )]
148
- )
149
- if spec is None or spec .loader is None :
150
- raise ModuleNotFoundError # noqa: TRY301
151
-
152
- _module = importlib .util .module_from_spec (spec )
153
- spec .loader .exec_module (_module )
154
- sys .modules [pkg_name ] = _module
155
-
156
- # We load the complete module
157
- module = importlib .import_module (import_name )
158
-
159
- attr_chain = attr_path .split ("." )
160
- plugin_callable = reduce (getattr , attr_chain , module )
161
-
162
- except Exception as exc :
163
- raise PluginError (
164
- f"Loading the plugin from { plugin_api !r} failed: { exc } "
165
- ) from exc
134
+ # normalize it before passing to the subprocess
135
+ plugin_api = f"{ import_name } :{ attr_path } "
166
136
167
137
logger .info (
168
138
"Loading plugin via %(plugin_api)s" ,
@@ -171,59 +141,27 @@ def _load_plugin(self, plugin_api: str) -> PluginType:
171
141
},
172
142
)
173
143
174
- if not callable (plugin_callable ):
175
- raise PluginError (
176
- f"{ plugin_api !r} points at a value that is not callable: "
177
- f"{ plugin_callable !r} "
178
- )
179
-
180
- try :
181
- # Instantiate the plugin
182
- plugin_instance = plugin_callable ()
183
- except Exception as exc :
184
- raise PluginError (
185
- f"Instantiating the plugin from { plugin_api !r} failed: { exc } "
186
- ) from exc
187
-
188
- required_attributes = PluginType .__abstractmethods__
189
- if missing_attributes := required_attributes .difference (dir (plugin_instance )):
190
- raise PluginError (
191
- f"Instantiating the plugin from { plugin_api !r} "
192
- "returned an object that does not meet the PluginType prototype: "
193
- f"{ plugin_instance !r} (missing attributes: "
194
- f"{ ', ' .join (sorted (missing_attributes ))} )"
195
- )
196
-
197
- # sanity check the subprocess loader until we switch fully to it
198
- assert subprocess_namespace_map == {plugin_api : plugin_instance .namespace }
199
-
200
- return plugin_instance
201
-
202
- def load_plugin (self , plugin_api : str ) -> PluginType :
203
- plugin_instance = self ._load_plugin (plugin_api )
204
-
205
- if self ._plugins is None :
206
- self ._plugins = {}
207
- self ._namespace_map = {}
144
+ # make sure to normalize it
145
+ namespace = _call_subprocess ([plugin_api ], {"namespaces" : {}})["namespaces" ][
146
+ plugin_api
147
+ ]
208
148
209
- if plugin_instance . namespace in self ._plugins :
149
+ if namespace in self ._namespace_map . values () :
210
150
raise RuntimeError (
211
151
"Two plugins found using the same namespace "
212
- f"{ plugin_instance . namespace } . Refusing to proceed."
152
+ f"{ namespace } . Refusing to proceed."
213
153
)
214
154
215
- self ._namespace_map [plugin_api ] = plugin_instance .namespace
216
- self ._plugins [plugin_instance .namespace ] = plugin_instance
217
-
218
- return plugin_instance
155
+ self ._namespace_map [plugin_api ] = namespace
156
+ return namespace
219
157
220
158
@abstractmethod
221
159
def _load_all_plugins (self ) -> None : ...
222
160
223
161
def _load_all_plugins_from_tuple (self , plugin_apis : list [str ]) -> None :
224
- if self ._plugins is not None :
162
+ if self ._namespace_map is not None :
225
163
raise RuntimeError (
226
- "Impossible to load plugins - `self._plugins ` is not None"
164
+ "Impossible to load plugins - `self._namespace_map ` is not None"
227
165
)
228
166
229
167
for plugin_api in plugin_apis :
@@ -253,14 +191,14 @@ def _call(self, method: Callable[[], Any]) -> Any:
253
191
def _check_plugins_loaded (self ) -> None :
254
192
if self ._python_ctx is None :
255
193
raise RuntimeError ("Impossible to load plugins outside a Python Context" )
256
- if self ._plugins is None :
194
+ if self ._namespace_map is None :
257
195
raise NoPluginFoundError ("No plugin has been loaded in the environment." )
258
196
259
197
def _get_configs (
260
198
self , method : str , require_non_empty : bool
261
199
) -> dict [str , ProviderConfig ]:
262
200
self ._check_plugins_loaded ()
263
- assert self ._plugins is not None
201
+ assert self ._namespace_map is not None
264
202
265
203
configs = _call_subprocess (list (self ._namespace_map .keys ()), {method : {}})[
266
204
method
@@ -298,7 +236,7 @@ def get_all_configs(self) -> dict[str, ProviderConfig]:
298
236
def get_build_setup (self , variant_desc : VariantDescription ) -> dict [str , list [str ]]:
299
237
"""Get build variables for a variant made of specified properties"""
300
238
self ._check_plugins_loaded ()
301
- assert self ._plugins is not None
239
+ assert self ._namespace_map is not None
302
240
303
241
namespaces = {vprop .namespace for vprop in variant_desc .properties }
304
242
try :
@@ -321,6 +259,7 @@ def get_build_setup(self, variant_desc: VariantDescription) -> dict[str, list[st
321
259
@property
322
260
def plugin_api_values (self ) -> dict [str , str ]:
323
261
self ._check_plugins_loaded ()
262
+ assert self ._namespace_map is not None
324
263
return {
325
264
namespace : plugin_api
326
265
for plugin_api , namespace in self ._namespace_map .items ()
@@ -329,6 +268,7 @@ def plugin_api_values(self) -> dict[str, str]:
329
268
@property
330
269
def namespaces (self ) -> list [str ]:
331
270
self ._check_plugins_loaded ()
271
+ assert self ._namespace_map is not None
332
272
return list (self ._namespace_map .values ())
333
273
334
274
@@ -417,9 +357,9 @@ def _install_all_plugins(self) -> None:
417
357
self ._install_all_plugins_from_reqs (reqs )
418
358
419
359
def _load_all_plugins (self ) -> None :
420
- if self ._plugins is not None :
360
+ if self ._namespace_map is not None :
421
361
raise RuntimeError (
422
- "Impossible to load plugins - `self._plugins ` is not None"
362
+ "Impossible to load plugins - `self._namespace_map ` is not None"
423
363
)
424
364
425
365
# Get the current environment for marker evaluation
@@ -439,9 +379,9 @@ class EntryPointPluginLoader(BasePluginLoader):
439
379
_plugin_provider_packages : dict [str , Distribution ] | None = None
440
380
441
381
def _load_all_plugins (self ) -> None :
442
- if self ._plugins is not None :
382
+ if self ._namespace_map is not None :
443
383
raise RuntimeError (
444
- "Impossible to load plugins - `self._plugins ` is not None"
384
+ "Impossible to load plugins - `self._namespace_map ` is not None"
445
385
)
446
386
447
387
self ._plugin_provider_packages = {}
@@ -459,16 +399,16 @@ def _load_all_plugins(self) -> None:
459
399
)
460
400
461
401
try :
462
- plugin_instance = self .load_plugin (plugin_api = ep .value )
402
+ namespace = self .load_plugin (plugin_api = ep .value )
463
403
except PluginError :
464
404
logger .debug ("Impossible to load `%s`" , ep )
465
405
else :
466
406
if ep .dist is not None :
467
- self ._plugin_provider_packages [plugin_instance . namespace ] = ep .dist
407
+ self ._plugin_provider_packages [namespace ] = ep .dist
468
408
469
409
@property
470
410
def plugin_provider_packages (self ) -> dict [str , Distribution ]:
471
- if self ._plugins is None :
411
+ if self ._namespace_map is None :
472
412
raise NoPluginFoundError ("No plugin has been loaded in the environment." )
473
413
assert self ._plugin_provider_packages is not None
474
414
return self ._plugin_provider_packages
@@ -481,15 +421,13 @@ class ManualPluginLoader(BasePluginLoader):
481
421
482
422
def __init__ (self ) -> None :
483
423
self ._namespace_map = {}
484
- self ._plugins = {}
485
424
super ().__init__ (python_ctx = ExternalNonIsolatedPythonEnv ().__enter__ ())
486
425
487
426
def __enter__ (self ) -> Self :
488
427
return self
489
428
490
429
def __exit__ (self , * args : object ) -> None :
491
430
self ._namespace_map = {}
492
- self ._plugins = {}
493
431
494
432
def __del__ (self ) -> None :
495
433
self ._python_ctx .__exit__ ()
0 commit comments