The first step is to define a base Plugin class. All we need is to include the following in base.py:
class Plugin(object):
pass
Next, we ensure that classes used in plugins derive from this base class. We only give one explicit example, that of the class included in op_2.py since the 4 classes included in op_1.py would be treated in exactly the same way.
Note that we added one more line of code to the class definition. We are now ready to deal with the plugin discovery and registration.
from plugins.base import Plugin
class operator_pow_token(Plugin):
symbol = '**'
lbp = 30
def led(self, left):
return left ** expression(30-1)
Rather than hard-coding the information about which plugin files to import as we did when we simply modularize the application, we give a way for our program to automatically find plugins. With the file structure that we have created, this can be accomplished as follows:
Note that the last line of code is included because of the "wart" mentioned in the previous post and would not usually be included. To be safe, we should probably have ensured that expression was not already defined in the modules to be imported since, in theory, Python files other than plugins (such as __init__.py) might be present in the plugin directory. In this tutorial series we will often ignore the need to insert try/except clauses to simplify the code.
def find_plugins(expression):
'''find all files in the plugin directory and imports them'''
plugin_dir = os.path.dirname(os.path.realpath(__file__))
plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]
sys.path.insert(0, plugin_dir)
for plugin in plugin_files:
mod = __import__(plugin)
mod.expression = expression
While we have imported the modules containing the plugins, they are not yet known in a useful form by the main application. To do so is very simple in this class-based approach, thanks to Python's treatment of (sub-)classes. Here's the code to do this:
def register_plugins():
'''Register all class based plugins.
Uses the fact that a class knows about all of its subclasses
to automatically initialize the relevant plugins
'''
for plugin in Plugin.__subclasses__():
OPERATORS[plugin.symbol] = plugin
That's it! It is hard to imagine anything simpler. With this last definition, the entire base.py module can be written as:
In the next post, I will show another simple alternative approach similar to the one used in Crunchy.
import os
import sys
OPERATORS = {}
class Plugin(object):
pass
def init_plugins(expression):
'''simple plugin initializer
'''
find_plugins(expression)
register_plugins()
def find_plugins(expression):
'''find all files in the plugin directory and imports them'''
plugin_dir = os.path.dirname(os.path.realpath(__file__))
plugin_files = [x[:-3] for x in os.listdir(plugin_dir) if x.endswith(".py")]
sys.path.insert(0, plugin_dir)
for plugin in plugin_files:
mod = __import__(plugin)
mod.expression = expression
def register_plugins():
'''Register all class based plugins.
Uses the fact that a class knows about all of its subclasses
to automatically initialize the relevant plugins
'''
for plugin in Plugin.__subclasses__():
OPERATORS[plugin.symbol] = plugin
I've found this a fun and interesting blog series, thanks!. I didn't know about __subclasses__()...that does make this relatively straightforward.
ReplyDeleteThanks for your comment. The series is not over yet: I expect to write at least 4 other posts if not more. However, I am going to take a bit of a break before I continue.
ReplyDelete