Last active
April 10, 2025 17:12
-
-
Save StSav012/a1ec296d11a38da2cbeb372f11ca75bd to your computer and use it in GitHub Desktop.
Use custom meta hook to import modules available as strings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Use custom meta hook to import modules available as strings. | |
Cp. PEP 302 http://www.python.org/dev/peps/pep-0302/#specification-part-2-registering-hooks | |
Credit: Thorsten Kranz, https://stackoverflow.com/a/14192708/8554611 | |
""" | |
import sys | |
from importlib.abc import Loader, MetaPathFinder | |
from importlib.machinery import ModuleSpec | |
from importlib.util import spec_from_file_location | |
from types import ModuleType | |
class StringImporter(MetaPathFinder): | |
class Loader(Loader): | |
def __init__(self, modules: dict[str, str | dict]) -> None: | |
self._modules: dict[str, str | dict] = modules | |
# noinspection PyMethodMayBeStatic | |
def is_package(self, module_name: str) -> bool: | |
return isinstance(self._modules[module_name], dict) | |
# noinspection PyMethodMayBeStatic | |
def get_code(self, module_name: str): | |
return compile(self._modules[module_name], filename="<string>", mode="exec") | |
def create_module(self, spec: ModuleSpec) -> ModuleType | None: | |
return ModuleType(spec.name) | |
def exec_module(self, module: ModuleType) -> None: | |
if module.__name__ not in self._modules: | |
raise ImportError(module.__name__) | |
sys.modules[module.__name__] = module | |
if not self.is_package(module.__name__): | |
exec(self._modules[module.__name__], module.__dict__) | |
else: | |
for sub_module in self._modules[module.__name__]: | |
self._modules[".".join((module.__name__, sub_module))] = self._modules[module.__name__][ | |
sub_module | |
] | |
exec(self._modules[module.__name__].get("__init__", ""), module.__dict__) | |
def __init__(self, **modules: str | dict) -> None: | |
self._modules: dict[str, str | dict] = modules | |
self._loader = StringImporter.Loader(modules) | |
def find_spec( | |
self, | |
fullname: str, | |
path: "str | None", | |
target: "ModuleType | None" = None, | |
) -> "ModuleSpec | None": | |
if fullname in self._modules: | |
spec: ModuleSpec = spec_from_file_location(fullname, loader=self._loader) | |
spec.origin = "<string>" | |
return spec | |
return None | |
if __name__ == "__main__": | |
def main() -> None: | |
modules = { | |
"a": """def hello(): | |
return f'Hello World from {__spec__.name or __file__}!'""", | |
"b": """def hello(): | |
return f'Hello World from {__spec__.name or __file__}!'""", | |
"package": { | |
"__init__": """print('initializing the package')""", | |
"module": """def hello(): | |
return f'Hello World from {__spec__.name or __file__}!'""", | |
}, | |
} | |
sys.meta_path.append(StringImporter(**modules)) | |
# Let's use our import hook | |
# noinspection PyUnresolvedReferences | |
import a | |
print(a.hello()) | |
# noinspection PyUnresolvedReferences | |
from a import hello | |
print(hello()) | |
# noinspection PyUnresolvedReferences | |
from b import hello | |
print(hello()) | |
# noinspection PyUnresolvedReferences | |
from package.module import hello | |
print(hello()) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment