|
|
|
@ -2,81 +2,47 @@ 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__) |
|
|
|
|
|
|
|
|
|
|
|
class VirtualModule: |
|
|
|
__slots__ = ['name', 'module', 'spec'] |
|
|
|
|
|
|
|
def __init__(self, name): |
|
|
|
self.name = name |
|
|
|
self.module = module_cls(name) |
|
|
|
self.spec = spec_cls(name=name, loader=VirtualModuleLoader) |
|
|
|
setattr(self.module, '__spec__', self.spec) |
|
|
|
|
|
|
|
def set_attribute(self, name, value): |
|
|
|
setattr(self.module, name, value) |
|
|
|
|
|
|
|
def delete_attribute(self, name): |
|
|
|
delattr(self.module, name) |
|
|
|
|
|
|
|
|
|
|
|
def create_module(name): |
|
|
|
module = VirtualModule(name) |
|
|
|
registry[name] = module |
|
|
|
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 copy_module(module): |
|
|
|
name = module.__name__ |
|
|
|
if hasattr(module, '__spec__'): |
|
|
|
name = module.__spec__.name |
|
|
|
|
|
|
|
virt_mod = VirtualModule(name) |
|
|
|
for key, value in module.__dict__.items(): |
|
|
|
if key in ('__spec__', '__name__', '__loader__', '__package__'): |
|
|
|
continue |
|
|
|
virt_mod.set_attribute(key, value) |
|
|
|
|
|
|
|
registry[name] = virt_mod |
|
|
|
importlib.reload(module) |
|
|
|
return virt_mod |
|
|
|
|
|
|
|
|
|
|
|
def as_module(cls_or_name): |
|
|
|
if isinstance(cls_or_name, str): |
|
|
|
cls = None |
|
|
|
name = cls_or_name |
|
|
|
elif isinstance(cls_or_name, type): |
|
|
|
cls = cls_or_name |
|
|
|
name = getattr(cls, '__module_name__', cls.__name__) |
|
|
|
else: |
|
|
|
raise ValueError('Expected as_module to be passed a string or a class type') |
|
|
|
|
|
|
|
def wrapper(cls): |
|
|
|
module = create_module(name) |
|
|
|
|
|
|
|
for key, value in cls.__dict__.items(): |
|
|
|
if key.startswith('__') and key.endswith('__'): |
|
|
|
continue |
|
|
|
|
|
|
|
module.set_attribute(key, value) |
|
|
|
return module |
|
|
|
|
|
|
|
if cls is None: |
|
|
|
return wrapper |
|
|
|
return wrapper(cls) |
|
|
|
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].module |
|
|
|
return registry[spec.name] |
|
|
|
|
|
|
|
def exec_module(module): |
|
|
|
module_name = module.__name__ |
|
|
|
@ -87,10 +53,39 @@ class VirtualModuleLoader(Loader): |
|
|
|
|
|
|
|
|
|
|
|
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 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) |