Skip to content

Instantly share code, notes, and snippets.

@a-recknagel
Last active July 5, 2019 11:24
Show Gist options
  • Save a-recknagel/15811a7b68afbe10e5c6864ed7e56db2 to your computer and use it in GitHub Desktop.
Save a-recknagel/15811a7b68afbe10e5c6864ed7e56db2 to your computer and use it in GitHub Desktop.
Dynamic module reload from given site package
# This script contains instructions that can be used to showcase how the reloader.py script is suppsoed to be used.
# The module reloading itself cotnains only pure python, so the only thing you'll need to run it is a working
# python3 interpreter
# setup start:
$ python3 -m venv env_0_12 # version 0.12 is going to be installed in here ...
$ python3 -m venv env_0_13 # ... and version 0.13 in here
$ env_0_12/bin/python3 -m pip install pyarrow==0.12
[...]
Successfully installed numpy-1.16.4 pyarrow-0.12.0 six-1.12.0
$ env_0_13/bin/python3 -m pip install pyarrow==0.13
[...]
Successfully installed numpy-1.16.4 pyarrow-0.13.0 six-1.12.0
# setup done.
# The reloader utlity can be tested by running it with any of the venvs iterpreters, e.g.
$ env_0_12/bin/python3 reloader.py
0.12.0
0.13.0
# it works! 🎉
# Running it with an unrelated interpreter that has a different version has lead to a range
# of bugs in my case, probable because numpy is opinionated about python versions. This
# implies that the whole "reloading a different version of module x" might fail horribly if
# their dependencies were updated, or if they were installed with different python versions.
import sys
from importlib import reload, import_module
from types import ModuleType
def deep_reload(parent, module, seen):
name = module.__name__
if not name.startswith(parent) or name in seen:
# don't care about unrelated modules, avoid infinite loops
return
seen.add(name)
try:
reload(module)
except AttributeError:
# happens if different versions define different submodules
return
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType:
deep_reload(parent, attribute, seen)
class TempSysPath:
def __init__(self, path):
self.path = path
self.old_path = sys.path
def __enter__(self):
sys.path = [self.path] + sys.path
def __exit__(self, exc_type, exc_val, exc_tb):
sys.path = self.old_path
def load_module(module, path):
"""Load a module (and all of its defined sub-modules) given a path.
This will load a module independent of the running python interpreter's
site package directory.
Args:
module (str): Name of the module you want to load/reload.
path (str): Absolute path to the site package where the module is
installed.
Returns:
ModuleType: The module that was loaded. It can also be imported at any
other location by just writing 'import module', this function
doesn't do anything special after loading it into sys.modules.
"""
with TempSysPath(path):
if module not in sys.modules:
import_module(module)
else:
# handling module references within the module itself is hard, so
# just reload twice to be on the safe side
deep_reload(module, sys.modules[module], set())
deep_reload(module, sys.modules[module], set())
return sys.modules[module]
if __name__ == '__main__':
# load version 0.12
print(load_module(
'pyarrow',
'/path/to/env_0_12/lib/python3.6/site-packages').__version__)
# load version 0.13
print(load_module(
'pyarrow',
'/path/to/env_0_13/lib/python3.6/site-packages').__version__)
@a-recknagel
Copy link
Author

a-recknagel commented Jul 5, 2019

Credit for the idea for recursive module reload goes to matthew: https://stackoverflow.com/a/17194836/962190, but their version had a couple of bugs if unrelated modules were reloaded, or they referred each other in infinite loops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment