Supercharge Your Python Applications with Plugin Modules

Maxim Soukharev
3 min readDec 11, 2021

--

Photo by https://unsplash.com/@davidclode

Python is a great choice of language to build powerful and flexible applications. Dynamic typing, simple syntax, a large community of developers and an abundance of high quality libraries and frameworks all allow developers to quickly roll out feature-rich products, services and tools.

You can make you Python applications even more powerful and flexible by using plugin modules.

In this story, we will implement a CLI script that ingests a plugin module to customize its own output to stdout.

Example: CLI Script

Python gives you a flexible toolset to import modules. In this example, we want to export a module by providing its file path.

First, let’s create the CLI script and call it my_cli.py. There, we import the util module from the importlib package.

import importlib.util as imputils

Then, let’s define a function that takes the module path and returns a module object for our plugin. We will be able to access the contents of the plugin module (objects, classes, functions, etc.) through this module object.

import os.path as osp...def get_module(path):
# we require a .py script as a plugin
# extract the name of the module from the path
mod_name = osp.basename(path).rstrip('.py')
# create a module spec from the file path
spec = imputils.spec_from_file_location(mod_name, path)
# create a module from the spec
module = imputils.module_from_spec(spec)
# execute the module
spec.loader.exec_module(module)
return module

We also need to provide the path to our plugin script via a command line argument. In the context of this example, we want to ask for the name of the plugin function that we want to run.

import argparse...if __name__ == '__main__':    
parser = argparse.ArgumentParser()
# We ask for the path to the plugin module
parser.add_argument('--module', required=True)
# We also ask for the name of the function that will customize the output
parser.add_argument('--func_name', required=True)
args = parser.parse_args()
run(args.module, args.func_name)

Inside the CLI script, let’s implement the run function. It accepts the path to the module and the name of the plugin function. The run function passes a string to the plugin function, saves the result and then outputs it to stdout.

def run(module_path, func_name):
# get the executed module
module = get_module(module_path)
# get reference to the specified function
func = module.__getattribute__(func_name)
# pass a message to the function and get the result
full_msg = func('Parent: Hello plugin!')
print(full_msg)

Finally, let’s create a plugin script plugin.py. There, we define the plugin function that will be called from the parent.

def append_reply(message):
return message + '\nPlugin: Hey there!'

The full code:

Running the Script

Let’s run our command line script (Python 3.x)

python ./plugin_cli.py --module ./plugin.py --func_name append_reply

In the output below, you can see that the plugin effectively added to the original message string. The modified message string was then printed out by the CLI script.

Security Considerations

As you can see, it is very easy to start using plugin scripts in Python! Notice that in our example, we execute the plugin module in our main interpreter. This is safe as long as we can trust code inside the module.

However, in more advanced cases, we might not be able to trust the plugin modules. An example would be a web application that ingests a client’s script as a plugin and then uses it to perform some custom operation within its framework. In this scenario, we open the web application and its host to all sorts of attacks.

Securely running untrusted Python code is no trivial matter. Python interpreter is inheritently introspecitve. Unless you have time to do a deep dive into how to restrict plugin execution operations and plugin access to the OS resources (including the CPU cycles), you might want to avoid running an untrusted plugin.

--

--