Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python 3 import hooks

I'm trying to implement an "import hook" in Python 3. The hook is supposed to add an attribute to every class that is imported. (Not really every class, but for the sake of simplifying the question, let's assume so.)

I have a loader defined as follows:

import sys

class ConfigurableImports(object):

    def find_module(self, fullname, path):
        return self

    def create_module(self, spec):
        # ???

    def exec_module(self, module):
        # ???

sys.meta_path = [ConfigurableImports()]

The documentation states that as of 3.6, loaders will have to implement both create_module and exec_module. However, the documentation also has little indication what one should do to implement these, and no examples. My use case is very simple because I'm only loading Python modules and the behavior of the loader is supposed to be almost exactly the same as the default behavior.

If I could, I'd just use importlib.import_module and then modify the module contents accordingly; however, since importlib leverages the import hook, I get an infinite recursion.

EDIT: I've also tried using the imp module's load_module, but this is deprecated.

Is there any easy way to implement this functionality with import hooks, or am I going about this the wrong way?

like image 440
Tom Avatar asked Sep 09 '16 19:09

Tom


1 Answers

Imho, if you only need to alter the module, that is, play with it after it has been found and loaded, there's no need to actually create a full hook that finds, loads and returns the module; just patch __import__.

This can easily be done in a few lines:

import builtins 
from inspect import getmembers, isclass

old_imp = builtins.__import__

def add_attr(mod):
    for name, val in getmembers(mod):
        if isclass(val):
            setattr(val, 'a', 10)

def custom_import(*args, **kwargs):
    m = old_imp(*args, **kwargs)
    add_attr(m)
    return m

builtins.__import__ = custom_import

Here, __import__ is replaced by your custom import that calls the original __import__ to get the loaded module and then calls a function add_attr that does the actual modification of the classes in a module (with getmembers and isclass from inspect) before returning the module.

Of course this is created in a way that when you import the script, the changes are made.

You can and probably should create auxiliary functions that restore and change it again if needed i.e things like:

def revert(): builtins.__import__ = old_imp

def apply(): builtins.__import__ = custom_import

A context-manager would also make this implementation cleaner.

like image 157
Dimitris Fasarakis Hilliard Avatar answered Nov 11 '22 14:11

Dimitris Fasarakis Hilliard