import importlib
|
|
from importlib.abc import Loader, MetaPathFinder
|
|
import sys
|
|
|
|
__all__ = [
|
|
'MetaVirtualModule',
|
|
'VirtualModule',
|
|
'add_to_module',
|
|
'create_module',
|
|
]
|
|
|
|
# Our virtual module registry
|
|
registry = dict()
|
|
|
|
# Built in module class
|
|
module_cls = type(sys)
|
|
# Built in module spec class
|
|
spec_cls = type(sys.__spec__)
|
|
|
|
|
|
def create_module(module_name):
|
|
"""Function for create a new empty virtual module and register it"""
|
|
module = module_cls(module_name)
|
|
setattr(module, '__spec__', spec_cls(name=module_name, loader=VirtualModuleLoader))
|
|
registry[module_name] = module
|
|
return module
|
|
|
|
|
|
def add_to_module(module, name=None):
|
|
"""Decorator to register a function or class to a module"""
|
|
def wrapper(value):
|
|
key = name or getattr(value, '__name__', None)
|
|
if key:
|
|
setattr(module, key, value)
|
|
return value
|
|
return wrapper
|
|
|
|
|
|
class VirtualModuleLoader(Loader):
|
|
"""Module loader class used for pulling virtual modules from our registry"""
|
|
def create_module(spec):
|
|
if spec.name not in registry:
|
|
return None
|
|
|
|
return registry[spec.name]
|
|
|
|
def exec_module(module):
|
|
module_name = module.__name__
|
|
if hasattr(module, '__spec__'):
|
|
module_name = module.__spec__.name
|
|
|
|
sys.modules[module_name] = module
|
|
|
|
|
|
class VirtualModuleFinder(MetaPathFinder):
|
|
"""Module finder to register with sys.meta_path for finding module specs from our registry"""
|
|
def find_spec(fullname, path, target=None):
|
|
if fullname in registry:
|
|
return registry[fullname].__spec__
|
|
return None
|
|
|
|
|
|
class MetaVirtualModule(type):
|
|
"""Metaclass used for automatically creating and registering VirtualModule class definitions"""
|
|
def __init__(cls, name, bases, attrs):
|
|
# Initialize the class
|
|
super(MetaVirtualModule, cls).__init__(name, bases, attrs)
|
|
|
|
# Do not register our base class
|
|
if name == 'VirtualModule':
|
|
return
|
|
|
|
module_name = getattr(cls, '__module_name__', cls.__name__) or name
|
|
# DEV: `create_module` will registry this module for us
|
|
module = create_module(module_name)
|
|
|
|
# Copy over class attributes
|
|
for key, value in attrs.items():
|
|
if key in ('__name__', '__module_name__', '__module__', '__qualname__'):
|
|
continue
|
|
setattr(module, key, value)
|
|
|
|
|
|
class VirtualModule(metaclass=MetaVirtualModule):
|
|
"""Base virtual module class for creating modules from class definitions"""
|
|
pass
|
|
|
|
|
|
# Push our virtual module finder at the beginning of the sys.meta_path
|
|
# DEV: Push in first so we always look for virtual modules first
|
|
sys.meta_path.insert(0, VirtualModuleFinder)
|