summaryrefslogtreecommitdiff
path: root/content/development
diff options
context:
space:
mode:
Diffstat (limited to 'content/development')
-rw-r--r--content/development/dynamic_module_loading_python.rst110
1 files changed, 110 insertions, 0 deletions
diff --git a/content/development/dynamic_module_loading_python.rst b/content/development/dynamic_module_loading_python.rst
new file mode 100644
index 0000000..3f3e960
--- /dev/null
+++ b/content/development/dynamic_module_loading_python.rst
@@ -0,0 +1,110 @@
+Loading Python modules/plug-ins at runtime
+##########################################
+
+:date: 2014-04-07 21:55
+:slug: dynamic-module-loading
+:tags: python, modules, import, programming, importlib
+:author: copyninja
+:summary: Post describes about loading arbitrary python files or
+ modules during runtime.
+
+Some times it is desired to load arbitrary python files or pre-
+installed python modules during application run time.I had encountered
+2 such usecases, one is in `SILPA application <http://silpa.org.in>`_
+and other is `dictionary-bot
+<https://github.com/copyninja/dictionary-bot>`_ which I was
+refactoring recently.
+
+Case 1: Loading installed python module
+---------------------------------------
+
+In case of SILPA I need to load pre-installed modules and here is the
+`old code
+<https://github.com/Project-SILPA/Silpa-Flask/blob/master/core/modulehelper.py#L24>`_
+, that is a bit hacky code I copied from Active State Python
+recipies. I found a bit better way to do it using `importlib` module
+as shown below.
+
+.. code-block:: python
+
+ from __future__ import print_function
+ import sys
+ import importlib
+
+ def load_module(modulename):
+ mod = None
+ try:
+ mod = importlib.import_module(modulename)
+ except ImportError:
+ print("Failed to load {module}".format(module=modulename),
+ file=sys.stderr)
+ return mod
+
+Here `importlib` itself takes care of checking if modulename is
+already loaded by checking `sys.modules[modulename]`, if loaded it
+returns that value, otherwise it loads the module and sets it to
+`sys.modules[modulename]` before returning module itself.
+
+
+Case 2: Loading python files from arbitrary location
+----------------------------------------------------
+
+In case of dictionary bot my requirement was bit different, I had some
+python files lying around in a directory, which I wanted to plug into
+the bot during run time and use them depending on some conditions. So
+basic structure which I was looking was as follows.
+
+
+.. raw:: html
+
+ <pre>
+ pluginloader.py
+ plugins
+ |
+ |__ aplugin.py
+ |
+ |__ bplugin.py
+ </pre>
+
+`pluginloader.py` is the file which needs to load python files under
+`plugins` directory. This was again done using `importlib` module as
+shown below.
+
+.. code-block:: python
+
+ import os
+ import sys
+ import re
+ import importlib
+
+ def load_plugins():
+ pysearchre = re.compile('.py$', re.IGNORECASE)
+ pluginfiles = filter(pysearchre.search,
+ os.listdir(os.path.join(os.path.dirname(__file__),
+ 'plugins')))
+ form_module = lambda fp: '.' + os.path.splitext(fp)[0]
+ plugins = map(form_module, pluginfiles)
+ # import parent module / namespace
+ importlib.import_module('plugins')
+ modules = []
+ for plugin in plugins:
+ if not plugin.startswith('__'):
+ modules.append(importlib.import_module(plugin, package="plugins"))
+
+ return modules
+
+Above code first searches for all python file under specified
+directory and creates a relative module name from it. For eg. file
+`aplugin.py` will become `.aplugin`. Before loading modules itself we
+will load the parent module in our case `plugins`, this is because
+**relative imports in python expects parent module to be already
+loaded**. Finally for relative imports to work with
+`importlib.import_module` we need to specify parent module name in
+`package` argument. Note that we ignore files begining with *__*, or
+specifically we don't want to import __init__.py, this will be done
+when we import parent module.
+
+The above code was inspired from a `answer on StackOverflow
+<https://stackoverflow.com/a/3381582>`_, which uses `imp` module, I
+avoided `imp` because its been deprecated from *Python 3.4* in favor
+of `importlib` module.