Skip to content Skip to sidebar Skip to footer

How To Implement An Import Hook That Can Modify The Source Code On The Fly Using Importlib?

Using the deprecated module imp, I can write a custom import hook that modifies the source code of a module on the fly, prior to importation/execution by Python. Given the source c

Solution 1:

find_module and load_module are both deprecated. You'll need to switch to find_spec and (create_module and exec_module) module respectively. See the importlibdocumentation for details.

You will also need to examine if you want to use a MetaPathFinder or a PathEntryFinder as the system to invoke them is different. That is, the meta path finder goes first and can override builtin modules, whereas the path entry finder works specifically for modules found on sys.path.

The following is a very basic importer that attempts to replace the entire import machinery for. It shows how to use the functions (find_spec, create_module, and exec_module).

import sys
import os.path

from importlib.abc import Loader, MetaPathFinder
from importlib.util import spec_from_file_location

classMyMetaFinder(MetaPathFinder):
    deffind_spec(self, fullname, path, target=None):
        if path isNoneor path == "":
            path = [os.getcwd()] # top level import -- if"."in fullname:
            *parents, name = fullname.split(".")
        else:
            name = fullname
        for entry in path:
            if os.path.isdir(os.path.join(entry, name)):
                # this module has child modules
                filename = os.path.join(entry, name, "__init__.py")
                submodule_locations = [os.path.join(entry, name)]
            else:
                filename = os.path.join(entry, name + ".py")
                submodule_locations = Noneifnot os.path.exists(filename):
                continuereturn spec_from_file_location(fullname, filename, loader=MyLoader(filename),
                submodule_search_locations=submodule_locations)

        returnNone# we don't know how to import thisclassMyLoader(Loader):
    def__init__(self, filename):
        self.filename = filename

    defcreate_module(self, spec):
        returnNone# use default module creation semanticsdefexec_module(self, module):
        withopen(self.filename) as f:
            data = f.read()

        # manipulate data some way...exec(data, vars(module))

definstall():
    """Inserts the finder into the import machinery"""
    sys.meta_path.insert(0, MyMetaFinder())

Next is a slightly more delicate version that attempts to reuse more of the import machinery. As such, you only need to define how to get the source of the module.

import sys
from os.path import isdir
from importlib import invalidate_caches
from importlib.abc import SourceLoader
from importlib.machinery import FileFinder


classMyLoader(SourceLoader):
    def__init__(self, fullname, path):
        self.fullname = fullname
        self.path = path

    defget_filename(self, fullname):
        return self.path

    defget_data(self, filename):
        """exec_module is already defined for us, we just have to provide a way
        of getting the source code of the module"""withopen(filename) as f:
            data = f.read()
        # do something with data ...# eg. ignore it... return "print('hello world')"return data


loader_details = MyLoader, [".py"]

definstall():
    # insert the path hook ahead of other path hooks
    sys.path_hooks.insert(0, FileFinder.path_hook(loader_details))
    # clear any loaders that might already be in use by the FileFinder
    sys.path_importer_cache.clear()
    invalidate_caches()

Solution 2:

See also this nice project https://pypi.org/project/importhook/

pip install importhook
import importhook

# Setup hook to be called any time the `socket` module is imported and loaded into module cache@importhook.on_import('socket')defon_socket_import(socket):
    new_socket = importhook.copy_module(socket)
    setattr(new_socket, 'gethostname', lambda: 'patched-hostname')
    return new_socket

# Import moduleimport socket

# Prints: 'patched-hostname'print(socket.gethostname())

Post a Comment for "How To Implement An Import Hook That Can Modify The Source Code On The Fly Using Importlib?"