Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python: intercept a class loading action

Summary: when a certain python module is imported, I want to be able to intercept this action, and instead of loading the required class, I want to load another class of my choice.

Reason: I am working on some legacy code. I need to write some unit test code before I start some enhancement/refactoring. The code imports a certain module which will fail in a unit test setting, however. (Because of database server dependency)

Pseduo Code:

from LegacyDataLoader import load_me_data
...
def do_something():
   data = load_me_data()

So, ideally, when python excutes the import line above in a unit test, an alternative class, says MockDataLoader, is loaded instead.

I am still using 2.4.3. I suppose there is an import hook I can manipulate

Edit

Thanks a lot for the answers so far. They are all very helpful.

One particular type of suggestion is about manipulation of PYTHONPATH. It does not work in my case. So I will elaborate my particular situation here.

The original codebase is organised in this way

./dir1/myapp/database/LegacyDataLoader.py
./dir1/myapp/database/Other.py
./dir1/myapp/database/__init__.py
./dir1/myapp/__init__.py

My goal is to enhance the Other class in the Other module. But since it is legacy code, I do not feel comfortable working on it without strapping a test suite around it first.

Now I introduce this unit test code

./unit_test/test.py

The content is simply:

from myapp.database.Other import Other

def test1():
            o = Other()
            o.do_something()

if __name__ == "__main__":
            test1()

When the CI server runs the above test, the test fails. It is because class Other uses LegacyDataLoader, and LegacydataLoader cannot establish database connection to the db server from the CI box.

Now let's add a fake class as suggested:

./unit_test_fake/myapp/database/LegacyDataLoader.py
./unit_test_fake/myapp/database/__init__.py
./unit_test_fake/myapp/__init__.py

Modify the PYTHONPATH to

export PYTHONPATH=unit_test_fake:dir1:unit_test

Now the test fails for another reason

  File "unit_test/test.py", line 1, in <module>
    from myapp.database.Other import Other
ImportError: No module named Other

It has something to do with the way python resolves classes/attributes in a module

like image 474
Anthony Kong Avatar asked Dec 23 '09 00:12

Anthony Kong


People also ask

What is __ all __ in Python?

Python __all__ It's a list of public objects of that module, as interpreted by import * . It overrides the default of hiding everything that begins with an underscore.

What is _find_and_load?

The _find_and_load() function takes an absolute module name and performs the following steps: If the module is in sys. modules , return it. Initialize the module search path to None . If the module has a parent module (the name contains at least one dot), import the parent module if it's not in sys.

How do you use relative import in Python?

Relative imports make use of dot notation to specify location. A single dot means that the module or package referenced is in the same directory as the current location. Two dots mean that it is in the parent directory of the current location—that is, the directory above.

How do you import absolute value in Python?

Absolute imports in PythonAbsolute import involves a full path i.e., from the project's root folder to the desired module. An absolute import state that the resource is to be imported using its full path from the project's root folder.


1 Answers

You can intercept import and from ... import statements by defining your own __import__ function and assigning it to __builtin__.__import__ (make sure to save the previous value, since your override will no doubt want to delegate to it; and you'll need to import __builtin__ to get the builtin-objects module).

For example (Py2.4 specific, since that's what you're asking about), save in aim.py the following:

import __builtin__
realimp = __builtin__.__import__
def my_import(name, globals={}, locals={}, fromlist=[]):
  print 'importing', name, fromlist
  return realimp(name, globals, locals, fromlist)
__builtin__.__import__ = my_import

from os import path

and now:

$ python2.4 aim.py
importing os ('path',)

So this lets you intercept any specific import request you want, and alter the imported module[s] as you wish before you return them -- see the specs here. This is the kind of "hook" you're looking for, right?

like image 160
Alex Martelli Avatar answered Sep 19 '22 10:09

Alex Martelli