diff --git a/aikido_zen/importhook/__init__.py b/aikido_zen/importhook/__init__.py deleted file mode 100644 index a33145a66..000000000 --- a/aikido_zen/importhook/__init__.py +++ /dev/null @@ -1,166 +0,0 @@ -""" -importhook -========== - -Python module for registering hooks to call when certain modules are imported. - -.. code:: python - - import importhook - - # Configure a function to call when `socket` is imported - @importhook.on_import('socket') - def socket_import(socket): - print('Socket module imported') - - # Import the `socket` module - import socket -""" - -import functools -import importlib -import sys -import types - -from .meta_paths import HookMetaPaths -from .registry import registry -from .utils import get_module_name - -__all__ = [ - "ANY_MODULE", - "copy_module", - "get_module_name", - "on_import", - "registry", - "reload_module", - "reset_module", -] - -ANY_MODULE = None - -# Wrap existing (and future) system meta path finders -sys.meta_path = HookMetaPaths(sys.meta_path[:]) - - -def on_import(module_name, func=None): - """ - Helper function used to register a hook function for a given module - - .. code:: python - - import importhook - - @importhook.on_import('socket') - def on_socket_import(socket): - print('socket imported') - - - @importhook.on_import(importhook.ANY_MODULE) - def on_any_import(module): - print(f'{module.__spec__.name} imported') - - - def on_httplib_import(httplib): - print('httplib imported') - - - importhook.on_import('httplib', on_httplib_import) - """ - if func is None: - - @functools.wraps(func) - def decorator(func): - registry[module_name] = func - return func - - return decorator - else: - registry[module_name] = func - - -def reset_module(module_name): - """ - Helper function to reset a copied module. - - .. code:: python - - import socket - import importhook - - # Copy `socket` module - socket = importhook.copy_module(socket) - - # Reset copied `socket` module back to it's original version - socket = importhook.reset_module(socket) - """ - if not isinstance(module_name, str): - module_name = get_module_name(module_name) - - module = sys.modules.get(module_name) - if not module: - return None - - if not hasattr(module, "__original_module__"): - return module - - sys.modules[module_name] = module.__original_module__ - return module.__original_module__ - - -def copy_module(module, copy_attributes=True, copy_spec=True): - """ - Helper function for copying a python module - - .. code:: python - - import importhook - - @importhook.on_import('socket') - def on_socket_import(socket): - new_socket = importhook.copy_module(socket) - setattr(new_socket, 'get_hostname', lambda: 'hostname') - return new_socket - """ - name = get_module_name(module) - new_mod = types.ModuleType(name) - setattr(new_mod, "__original_module__", module) - setattr(new_mod, "__reset_module__", lambda: reset_module(name)) - - # Copy all module attributes - if copy_attributes: - for attr, value in module.__dict__.items(): - setattr(new_mod, attr, value) - - # Make a copy of the modules spec if one is present - if copy_spec and getattr(new_mod, "__spec__", None): - spec = type(new_mod.__spec__)(name=name, loader=new_mod.__spec__.loader) - for attr, value in new_mod.__spec__.__dict__.items(): - if attr not in ("name", "loader"): - setattr(spec, attr, value) - new_mod.__spec__ = spec - return new_mod - - -def reload_module(module_name): - """ - Helper function to reload the specified module - - .. code:: python - - import socket - import importhook - - # Reload the `socket` module by passing in module - socket = importhook.reload_module(socket) - - # Reload the `socket` module by passing in the name - socket = importhook.reload_module('socket') - """ - if not isinstance(module_name, str): - module_name = get_module_name(module_name) - - module = sys.modules.get(module_name) - if not module: - return None - - return importlib.reload(module) diff --git a/aikido_zen/importhook/finder.py b/aikido_zen/importhook/finder.py deleted file mode 100644 index 6ba0938e1..000000000 --- a/aikido_zen/importhook/finder.py +++ /dev/null @@ -1,55 +0,0 @@ -import functools - -from .loader import HookLoader - - -def hook_finder(finder): - """ - Helper function to create a new "hooked" subclass of the provided finder class - - This function replaces the `Finder.find_spec` function to ensure that any ModuleSpecs will - use an `importmod.HookLoader` - """ - # If this finder has already been 'hooked', then return as-is - if hasattr(finder, "__hooked__"): - return finder - - # Determine if we were given an instance or a class - if isinstance(finder, type): - finder_cls = finder - else: - finder_cls = finder.__class__ - - # Determine the class name of the finder - finder_name = finder_cls.__name__ - - def wrap_find_spec(find_spec): - @functools.wraps(find_spec) - def wrapper(fullname, path, target=None): - spec = find_spec(fullname, path, target=target) - if spec is not None and spec.loader is not None: - spec.loader = HookLoader(spec.loader) - return spec - - return wrapper - - def wrap_find_loader(find_loader): - @functools.wraps(find_loader) - def wrapper(fullname, path): - loader = find_loader(fullname, path) - if loader is None: - return None - else: - return HookLoader(loader) - - return wrapper - - # Override the functions we care about - if hasattr(finder, "find_spec"): - setattr(finder, "find_spec", wrap_find_spec(finder.find_spec)) - if hasattr(finder, "find_loader"): - setattr(finder, "find_loader", wrap_find_loader(finder.find_loader)) - - # Make this finder as being 'hooked' - setattr(finder, "__hooked__", True) - return finder diff --git a/aikido_zen/importhook/loader.py b/aikido_zen/importhook/loader.py deleted file mode 100644 index 048ed5b2d..000000000 --- a/aikido_zen/importhook/loader.py +++ /dev/null @@ -1,84 +0,0 @@ -from importlib.abc import Loader -import sys - -from .registry import registry -from .utils import get_module_name - - -def call_module_hooks(module): - name = get_module_name(module) - - # If we have a hook in the registry, then call it now - if name in registry: - mod = registry[name](module) - if mod is not None: - sys.modules[name] = mod - - # If we have a global hook in the registry, then call it now - if None in registry: - mod = registry[None](module) - if mod is not None: - sys.modules[name] = mod - - -class HookLoader(Loader): - """ - Custom `importlib.abc.Loader` which ensures we call any registered hooks when a module is loaded. - """ - - __slots__ = ["loader"] - - def __init__(self, loader): - self.loader = loader - - def __getattribute__(self, name): - # If they are requesting the "loader" attribute, return it right away - loader = super(HookLoader, self).__getattribute__("loader") - if name == "loader": - return loader - - # Pass through attributes/methods only if they exist on the underlying loader - if hasattr(loader, name): - try: - return super(HookLoader, self).__getattribute__(name) - except AttributeError: - return getattr(loader, name) - - raise AttributeError - - def create_module(self, *args, **kwargs): - if not hasattr(self.loader, "create_module"): - return None - - return self.loader.create_module(*args, **kwargs) - - def find_module(self, name, *args, **kwargs): - if not hasattr(self.loader, "find_module"): - return None - - module = self.loader.find_module(name=name, *args, **kwargs) - if module is None: - return None - call_module_hooks(module) - return module - - def load_module(self, name, *args, **kwargs): - if not hasattr(self.loader, "load_module"): - return None - - module = self.loader.load_module(name, *args, **kwargs) - if module is None: - return None - call_module_hooks(module) - return module - - def exec_module(self, module, *args, **kwargs): - if not hasattr(self.loader, "exec_module"): - return None - - mod = self.loader.exec_module(module, *args, **kwargs) - if mod is not None: - module = mod - - call_module_hooks(module) - return module diff --git a/aikido_zen/importhook/meta_paths.py b/aikido_zen/importhook/meta_paths.py deleted file mode 100644 index 102d093ec..000000000 --- a/aikido_zen/importhook/meta_paths.py +++ /dev/null @@ -1,15 +0,0 @@ -from .finder import hook_finder - - -class HookMetaPaths(list): - """ - Custom list that will ensure any items added are wrapped as a "hooked" finder - - This class is made to replace `sys.meta_paths` - """ - - def __init__(self, finders): - super(HookMetaPaths, self).__init__([hook_finder(f) for f in finders]) - - def __setitem__(self, key, val): - super(HookMetaPaths, self).__setitem__(key, hook_finder(val)) diff --git a/aikido_zen/importhook/registry.py b/aikido_zen/importhook/registry.py deleted file mode 100644 index 9c6a1b0f6..000000000 --- a/aikido_zen/importhook/registry.py +++ /dev/null @@ -1,41 +0,0 @@ -import sys - - -class Hooks(set): - """Custom set that is used to maintain and call a list of hooks""" - - def __call__(self, module): - for hook in self: - mod = hook(module) - # If they modified the module, then use that instead - if mod is not None: - module = mod - return module - - -class HookRegistry(dict): - """ - Class used as the registry for import hooks - """ - - def __setitem__(self, key, hook): - module_name = key or "ANY_MODULE" - - # Ensure we have a key for this module and it's value is a `Hooks` set - if key not in self: - super(HookRegistry, self).__setitem__(key, Hooks()) - - # Add our hook to the registry - self[key].add(hook) - - # Call hook for a module which has already been loaded - if key is not None and key in sys.modules: - module = sys.modules[key] - - module = hook(sys.modules[key]) - if module is not None: - sys.modules[key] = module - - -# Create our global registry -registry = HookRegistry() diff --git a/aikido_zen/importhook/utils.py b/aikido_zen/importhook/utils.py deleted file mode 100644 index b3d242804..000000000 --- a/aikido_zen/importhook/utils.py +++ /dev/null @@ -1,5 +0,0 @@ -def get_module_name(module): - """Helper function to get a module's name""" - if hasattr(module, "__spec__"): - return module.__spec__.name - return module.__name__