This module provides the architecture for creating and handling xdress plugins.
author: | Anthony Scopatz <scopatz@gmail.com> |
---|
The purpose of xdress is to be as modular and extensible as possible, allowing for developers to build and execute their own tools as needed. As such, xdress has a very nimble plugin interface that easily handles run control, adding arguments to the command line interface, setting up & validating the run control, command execution, and teardown. In fact, the entire xdress execution is based on this plugin architecture. You can be certain that this is well supported feature and not some hack’d add on.
Writing plugins is easy! You simply need to have a variable named XDressPlugin in a module. Say your module is called mymod and lives in a package mypack, then xdress would know this plugin by the name "mypack.mymod". This is exactly the same string that you would use to do an absolute import of mymod.
To expose this plugin to an xdress execution, either add it to the plugins variable in your xdressrc.py file:
from xdress.utils import DEFAULT_PLUGINS
plugins = list(DEFAULT_PLUGINS) + ['mypack.mymod']
Or you can add it on the command line:
~ $ xdress --plugins xdress.stlwrap xdress.autoall xdress.cythongen mypack.mymod
Note that in both of the above cases we retain normal functionality by including the default plugins that come with xdress.
The XDressPlugin variable must be callable with no arguments and return a variable with certain attributes. Normally this is done as a class but through the magic of duck typing it doesn’t have to be. The Plugin class is provided as a base class which implements a minimal, zero-work interface. This is useful for inheriting your modules plugin from. You need only override the attributes you want. Again, inheriting from Plugin is suggested but not required.
requires: | This is a list of module names or a function that returns such a list. The names in this list will be loaded and executed in order prior to this plugin. If multiple plugins require the same upstream plugin, the upstream on will only be run once. |
---|---|
defaultrc: | This is a dictionary or run control instance that maps run control parameters to their default values if they are otherwise not specified. To make a parameter have to be given by the user, set the value to the singleton xdress.utils.NotSpecified. Parameters with the same name in different plugins will clobber each other, with the last plugin’s value being ultimately assigned. The exception to this is if a later plugin’s parameter value is NotSpecified then the previous plugin value will be retained. See the RunControl class for more details. Generally it is not advised for two plugins to share run control parameter names unless you really know what you are doing. |
rcupdaters: | This may be a dict, another mapping, a function which returns a mapping. The keys are the string names of the run control parameters. The values are callables which indicate how to update or merge two rc parameters with this key. The callable should take two instances and return a copy that represents the merger, e.g. lambda old, new: old + new. One useful example is for paths. Normally you want new paths to prepend old ones: rcupdaters = {'includes': lambda old, new: list(new) + list(old)}
If a callable is not supplied for an rc parameter then the the default behaviour is to simply override the old value with the new one. |
rcdocs: | This may be a dict, another mapping, a function which returns a mapping. The keys are the string names of the run control parameters. The values are docstrings for the rc parameters. |
update_argparser(parser): | |
This is method that takes an argparse.ArgumentParser() instance and modifies it in-place. This allows for run control parameters to be exposed as command line arguments and options. Default arguments in parser.add_argument() values should not be given, or should only be set to Not Specified. This is to prevent collisions with the run controller. Default values should instead be given in the plugin’s defaultrc. Thus argument names or the dest keyword argument should match the keys in defaultrc. |
|
setup(rc): | Performs all setup tasks needed for this plugin. This may include validation and munging of the run control object (rc) as well as creating directories and files in the OS environment. If needed, the rc should be modified in-place so that changes propagate to other plugins and further calls on this plugin. This should return None. |
execute(rc): | Performs the heavy lifting of the plugin, which may require a run controller.If needed, the rc should be modified in-place so that changes propagate to other plugins and further calls on this plugin. This should return None. |
teardown(rc): | Performs any cleanup tasks needed by the plugin, including removing temporary files. If needed, the rc should be modified in-place so that changes propagate to other plugins and further calls on this plugin. This should return None. |
report_debug(rc): | |
Generates and returns a message to report in the debug.txt file in the event that execute() fails and additional debugging information is requested. This message is a string. |
Here is simple, if morbid, plugin example:
from xdress.plugins import Plugin
class XDressPlugin(Plugin):
'''Which famous person was executed?'''
# everything should require base, it is useful!
requires = ('xdress.base',)
defaultrc = {
'choices': ['J. Edgar Hoover', 'Hua Mulan', 'Leslie'],
'answer': 'Joan of Arc',
}
rcupdaters = {'choices': lambda old, new: list(new) + list(old)}
rcdocs = {'choices': "Possible answers.",
'answer': "The correct answer"}
def update_argparser(self, parser):
# Note, no 'default=' keyword arguments are given
parser.add_argument('-c', '--choices', action='store', dest='choices',
nargs="+", help="famous people chocies")
parser.add_argument('-a', '--answer', action='store', dest='answer',
help="person who was executed")
def setup(self, rc):
'''Ensures that Joan of Arc is a choice.'''
if 'Joan of Arc' not in rc.choices:
rc.choices.append('Joan of Arc')
def execute(self, rc):
'''Kills Joan...'''
if rc.answer == 'Joan of Arc':
print('Joan has met an untimely demise!')
else:
raise ValueError('Joan of Arc was executed, not ' + rc.answer)
def report_debug(self, rc):
return "the possible choices were " + str(rc.choices)
A base plugin for other xdress pluigins to inherit.
The __init__() method may take no arguments or keyword arguments.
Performs the actual work of the plugin, which may require a run controller.
Parameters : | rc : xdress.utils.RunControl |
---|
A message to report in the event that execute() fails and additional debugging information is requested.
Parameters : | rc : xdress.utils.RunControl |
---|---|
Returns : | message : str or None
|
Performs all setup tasks needed for this plugin. This may include validation and munging of the run control object as well as creating the portions of the OS environment.
Parameters : | rc : xdress.utils.RunControl |
---|
Performs any cleanup tasks needed by the plugin.
Parameters : | rc : xdress.utils.RunControl |
---|
This method takes an argparse.ArgumentParser() instance and modifies it in-place. This allows for run control parameters to be modified from as command line arguments.
Parameters : | parser : argparse.ArgumentParser
|
---|
This may be a dict, RunControl instance, or other mapping or a function which returns any of these. The keys are string names of the run control parameters and the values are the associated default values.
This may be a dict, another mapping, a function which returns a mapping. The keys are the string names of the run control parameters. The values are docstrings for the rc parameters.
This may be a dict, another mapping, a function which returns a mapping. The keys are the string names of the run control parameters. The values are callables which indicate how to update or merge two rc parameters with this key. The callable should take two instances and return a copy that represents the merger, e.g. lambda old, new: old + new. One useful example is for paths. Normally you want new paths to prepend old ones:
rcupdaters = {'includes': lambda old, new: list(new) + list(old)}
If a callable is not supplied for an rc parameter then the the default behaviour is to simply override the old value with the new one.
This is a sequence of strings, or a function which returns such, that lists the module names of other plugins that this plugin requires.
This is a class for managing the instantiation and execution of plugins.
The execution and control of plugins should happen in the following order:
Parameters : | modnames : list of str
loaddeps: bool, optional :
|
---|
Builds and returns a command line interface based on the plugins.
Returns : | parser : argparse.ArgumentParser |
---|