|
|
|
@ -0,0 +1,86 @@ |
|
|
|
Importhook |
|
|
|
========= |
|
|
|
|
|
|
|
[](https://badge.fury.io/py/importhook) |
|
|
|
|
|
|
|
`importhook` is a Python package that lets you configure functions to call whenever a specific module is imported. |
|
|
|
|
|
|
|
|
|
|
|
## Installation |
|
|
|
|
|
|
|
```bash |
|
|
|
pip install importhook |
|
|
|
``` |
|
|
|
|
|
|
|
## Usage |
|
|
|
Configure a hook to be called when `socket` module is imported. |
|
|
|
|
|
|
|
```python |
|
|
|
import importhook |
|
|
|
|
|
|
|
# Setup hook to be called any time the `socket` module is imported and loaded into module cache |
|
|
|
@importhook.on_import('socket') |
|
|
|
def on_socket_import(socket): |
|
|
|
print('"socket" module has been imported') |
|
|
|
|
|
|
|
# Import module |
|
|
|
import socket |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
You can also use `importhook` to intercept and modify a module on import by returning a Python module from your hook function. |
|
|
|
|
|
|
|
```python |
|
|
|
import importhook |
|
|
|
|
|
|
|
# Setup hook to be called any time the `socket` module is imported and loaded into module cache |
|
|
|
@importhook.on_import('socket') |
|
|
|
def on_socket_import(socket): |
|
|
|
new_socket = importhook.copy_module(socket) |
|
|
|
setattr(new_socket, 'gethostname', lambda: 'patched-hostname') |
|
|
|
return new_socket |
|
|
|
|
|
|
|
# Import module |
|
|
|
import socket |
|
|
|
|
|
|
|
# Prints: 'patched-hostname' |
|
|
|
print(socket.gethostname()) |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
`importhook` also comes with helpers to reload modules that have already been imported. |
|
|
|
|
|
|
|
```python |
|
|
|
import socket |
|
|
|
import importhook |
|
|
|
|
|
|
|
|
|
|
|
# Setup hook to be called any time the `socket` module is imported and loaded into module cache |
|
|
|
@ DEV: `on_socket_import` will be called immediately because the `socket` module is already loaded |
|
|
|
@importhook.on_import('socket') |
|
|
|
def on_socket_import(socket): |
|
|
|
print('"socket" module has been imported') |
|
|
|
|
|
|
|
|
|
|
|
# Reload the socket module |
|
|
|
# DEV: Reassign to `socket` in case one of our hooks modifies the module |
|
|
|
socket = importhook.reload_module(socket) |
|
|
|
``` |
|
|
|
## Design decisions |
|
|
|
### Overwriting sys.meta_paths |
|
|
|
If a Python developer wants to modify the import behavior they can do so by adding a new `importlib.abc.Finder` |
|
|
|
class into `sys.meta_path`. |
|
|
|
|
|
|
|
```python |
|
|
|
import sys |
|
|
|
|
|
|
|
# Add our custom `importlib.abc.Finder` to `sys.meta_path` |
|
|
|
sys.meta_path.append(MyCustomFinder) |
|
|
|
``` |
|
|
|
|
|
|
|
One of the major design decisions we have taken with `importhook` is to wrap/overwrite `sys.meta_path`. |
|
|
|
What it means is that `importhook` will continue to work as expected regardless of any other modifications of `sys.meta_path`. |
|
|
|
|
|
|
|
There is however one caveat, if you were to do `sys.meta_path = [MyCustomFinder] + sys.meta_path` then `sys.meta_path` will get |
|
|
|
converted back into a `list`. Existing modifications to the finders in `sys.meta_path` will still work as expected, but any |
|
|
|
new finders added will not get hooked. |