Skip to content

Commit adefa0b

Browse files
committed
Remove in-process plugin loading entirely
1 parent ca7f953 commit adefa0b

File tree

1 file changed

+29
-91
lines changed

1 file changed

+29
-91
lines changed

variantlib/plugins/loader.py

Lines changed: 29 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
import logging
99
import sys
1010
from abc import abstractmethod
11-
from functools import reduce
12-
from importlib.machinery import PathFinder
1311
from pathlib import Path
1412
from subprocess import run
1513
from tempfile import TemporaryDirectory
@@ -30,7 +28,6 @@
3028
from variantlib.models.provider import ProviderConfig
3129
from variantlib.models.provider import VariantFeatureConfig
3230
from variantlib.plugins.py_envs import INSTALLER_PYTHON_ENVS
33-
from variantlib.plugins.py_envs import ISOLATED_PYTHON_ENVS
3431
from variantlib.plugins.py_envs import BasePythonEnv
3532
from variantlib.plugins.py_envs import ExternalNonIsolatedPythonEnv
3633
from variantlib.protocols import PluginType
@@ -83,8 +80,7 @@ def _call_subprocess(plugin_apis: list[str], commands: dict[str, Any]) -> Any:
8380
class BasePluginLoader:
8481
"""Load and query plugins"""
8582

86-
_namespace_map: dict[str, str]
87-
_plugins: dict[str, PluginType] | None = None
83+
_namespace_map: dict[str, str] | None = None
8884
_python_ctx: BasePythonEnv | None = None
8985

9086
def __init__(self, python_ctx: BasePythonEnv | None = None) -> None:
@@ -99,8 +95,7 @@ def __enter__(self) -> Self:
9995
return self
10096

10197
def __exit__(self, *args: object) -> None:
102-
self._plugins = None
103-
self._namespace_map = {}
98+
self._namespace_map = None
10499

105100
if self._python_ctx is None:
106101
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:
123118
# Actual plugin installation
124119
self._python_ctx.install(reqs)
125120

126-
def _load_plugin(self, plugin_api: str) -> PluginType:
121+
def load_plugin(self, plugin_api: str) -> str:
127122
"""Load a specific plugin"""
128123

124+
if self._namespace_map is None:
125+
self._namespace_map = {}
129126
if self._python_ctx is None:
130127
raise RuntimeError("Impossible to load plugins outside a Python Context")
131128

@@ -134,35 +131,8 @@ def _load_plugin(self, plugin_api: str) -> PluginType:
134131
)
135132
import_name: str = plugin_api_match.group("module")
136133
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}"
166136

167137
logger.info(
168138
"Loading plugin via %(plugin_api)s",
@@ -171,59 +141,27 @@ def _load_plugin(self, plugin_api: str) -> PluginType:
171141
},
172142
)
173143

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+
]
208148

209-
if plugin_instance.namespace in self._plugins:
149+
if namespace in self._namespace_map.values():
210150
raise RuntimeError(
211151
"Two plugins found using the same namespace "
212-
f"{plugin_instance.namespace}. Refusing to proceed."
152+
f"{namespace}. Refusing to proceed."
213153
)
214154

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
219157

220158
@abstractmethod
221159
def _load_all_plugins(self) -> None: ...
222160

223161
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:
225163
raise RuntimeError(
226-
"Impossible to load plugins - `self._plugins` is not None"
164+
"Impossible to load plugins - `self._namespace_map` is not None"
227165
)
228166

229167
for plugin_api in plugin_apis:
@@ -253,14 +191,14 @@ def _call(self, method: Callable[[], Any]) -> Any:
253191
def _check_plugins_loaded(self) -> None:
254192
if self._python_ctx is None:
255193
raise RuntimeError("Impossible to load plugins outside a Python Context")
256-
if self._plugins is None:
194+
if self._namespace_map is None:
257195
raise NoPluginFoundError("No plugin has been loaded in the environment.")
258196

259197
def _get_configs(
260198
self, method: str, require_non_empty: bool
261199
) -> dict[str, ProviderConfig]:
262200
self._check_plugins_loaded()
263-
assert self._plugins is not None
201+
assert self._namespace_map is not None
264202

265203
configs = _call_subprocess(list(self._namespace_map.keys()), {method: {}})[
266204
method
@@ -298,7 +236,7 @@ def get_all_configs(self) -> dict[str, ProviderConfig]:
298236
def get_build_setup(self, variant_desc: VariantDescription) -> dict[str, list[str]]:
299237
"""Get build variables for a variant made of specified properties"""
300238
self._check_plugins_loaded()
301-
assert self._plugins is not None
239+
assert self._namespace_map is not None
302240

303241
namespaces = {vprop.namespace for vprop in variant_desc.properties}
304242
try:
@@ -321,6 +259,7 @@ def get_build_setup(self, variant_desc: VariantDescription) -> dict[str, list[st
321259
@property
322260
def plugin_api_values(self) -> dict[str, str]:
323261
self._check_plugins_loaded()
262+
assert self._namespace_map is not None
324263
return {
325264
namespace: plugin_api
326265
for plugin_api, namespace in self._namespace_map.items()
@@ -329,6 +268,7 @@ def plugin_api_values(self) -> dict[str, str]:
329268
@property
330269
def namespaces(self) -> list[str]:
331270
self._check_plugins_loaded()
271+
assert self._namespace_map is not None
332272
return list(self._namespace_map.values())
333273

334274

@@ -417,9 +357,9 @@ def _install_all_plugins(self) -> None:
417357
self._install_all_plugins_from_reqs(reqs)
418358

419359
def _load_all_plugins(self) -> None:
420-
if self._plugins is not None:
360+
if self._namespace_map is not None:
421361
raise RuntimeError(
422-
"Impossible to load plugins - `self._plugins` is not None"
362+
"Impossible to load plugins - `self._namespace_map` is not None"
423363
)
424364

425365
# Get the current environment for marker evaluation
@@ -439,9 +379,9 @@ class EntryPointPluginLoader(BasePluginLoader):
439379
_plugin_provider_packages: dict[str, Distribution] | None = None
440380

441381
def _load_all_plugins(self) -> None:
442-
if self._plugins is not None:
382+
if self._namespace_map is not None:
443383
raise RuntimeError(
444-
"Impossible to load plugins - `self._plugins` is not None"
384+
"Impossible to load plugins - `self._namespace_map` is not None"
445385
)
446386

447387
self._plugin_provider_packages = {}
@@ -459,16 +399,16 @@ def _load_all_plugins(self) -> None:
459399
)
460400

461401
try:
462-
plugin_instance = self.load_plugin(plugin_api=ep.value)
402+
namespace = self.load_plugin(plugin_api=ep.value)
463403
except PluginError:
464404
logger.debug("Impossible to load `%s`", ep)
465405
else:
466406
if ep.dist is not None:
467-
self._plugin_provider_packages[plugin_instance.namespace] = ep.dist
407+
self._plugin_provider_packages[namespace] = ep.dist
468408

469409
@property
470410
def plugin_provider_packages(self) -> dict[str, Distribution]:
471-
if self._plugins is None:
411+
if self._namespace_map is None:
472412
raise NoPluginFoundError("No plugin has been loaded in the environment.")
473413
assert self._plugin_provider_packages is not None
474414
return self._plugin_provider_packages
@@ -481,15 +421,13 @@ class ManualPluginLoader(BasePluginLoader):
481421

482422
def __init__(self) -> None:
483423
self._namespace_map = {}
484-
self._plugins = {}
485424
super().__init__(python_ctx=ExternalNonIsolatedPythonEnv().__enter__())
486425

487426
def __enter__(self) -> Self:
488427
return self
489428

490429
def __exit__(self, *args: object) -> None:
491430
self._namespace_map = {}
492-
self._plugins = {}
493431

494432
def __del__(self) -> None:
495433
self._python_ctx.__exit__()

0 commit comments

Comments
 (0)