Skip to content

Instantly share code, notes, and snippets.

@caryhaynie
Created April 2, 2011 01:11
Show Gist options
  • Select an option

  • Save caryhaynie/899120 to your computer and use it in GitHub Desktop.

Select an option

Save caryhaynie/899120 to your computer and use it in GitHub Desktop.
Python module to provide plugin support via a simple but effective import hook.
#!/usr/bin/python
import imp
import os
import os.path
import sys
# get the platform-specific list of possible module suffixes.
MODULE_DESC = imp.get_suffixes()
class PluginLoader(object):
""" PluginLoader intercepts python import calls and allows a plugin directory to be mapped
to a specific top-level package.
"""
def __init__(self, plugin_root, plugin_dir):
self._plugin_root = plugin_root
self._plugin_dir = plugin_dir
# add a fake empty module.
sys.modules[self._plugin_root] = imp.new_module(self._plugin_root)
sys.modules[self._plugin_root].__file__ = "<plugin pseudo-package '%s'>" % self._plugin_root
sys.modules[self._plugin_root].__path__ = []
def _real_name(self, name):
return name[len(self._plugin_root) + 1:]
def _fs_path(self, name):
return os.path.join(self._plugin_dir, self._real_name(name))
def _real_path(self, name):
path = self._fs_path(name)
# test to see if this is a package or a module.
if self._is_package(path):
return path
else:
return "%s%s" % (path, self._get_suffix(path))
def _is_package(self, path):
return os.path.isdir(path)
def _get_suffix(self, path):
for suffix, args, type in MODULE_DESC:
test_path = "%s%s" % (path, suffix)
if os.path.exists(test_path):
return suffix
return ""
def _get_desc_for_suffix(self, suffix):
for index, desc in enumerate(MODULE_DESC):
if desc[0] == suffix:
return MODULE_DESC[index]
def find_module(self, fullname, path=None):
# we need to check here to make sure files actually exist, other wise the import mechanism
# breaks badly inside the plugins.
if fullname.startswith(self._plugin_root) and os.path.exists(self._real_path(fullname)):
return self
else:
return None
def load_module(self, fullname):
real_name = self._real_name(fullname)
real_path = self._real_path(fullname)
# don't reimport the module if it's already been imported. That's reload()'s job.
# need to use fullname here instead of real_name, so that reload() works correctly.
if fullname in sys.modules:
return sys.modules[fullname]
try:
if not self._is_package(real_path):
suffix = self._get_suffix(self._fs_path(fullname))
desc = self._get_desc_for_suffix(suffix)
return imp.load_module(fullname, open(real_path, desc[1]), real_path, desc)
else:
desc = ("", "", imp.PKG_DIRECTORY)
mod = imp.load_module(fullname, None, "", desc)
exec open(os.path.join(real_path, "__init__.py"), "rb").read() in mod.__dict__
mod.__path__ = [real_path]
return mod
except IOError:
raise ImportError("No module named %s" % real_name)
def map_plugins(plugin_root, plugin_dir):
sys.meta_path.append(PluginLoader(plugin_root, plugin_dir))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment