-
Notifications
You must be signed in to change notification settings - Fork 137
Plugins
Gourmet's plugin architecture was developed to allow development to focus on simplifying and streamlining the main interface without losing any power and without closing down the development of future functionality.
At this point, a great number of features are plugins, and therefore can be turned off -- most notably, all functionality exposing the concept of the "ingredient key" to users is a plugin, as is all import and export code, as is nutritional information. Another advantage of the plugin system is that if a component fails for whatever reason -- for example, if there is a missing dependency for one of the export plugins -- Gourmet as a whole can still run successfully.
The assumption is that the plugin architecture will develop and mature as more plugins are written. As that happens, and as new contributors join the project, the hope is that this wiki can serve as the home of documentation for future developers.
The basic Gourmet plugin architecture is based in two main sets of classes:
- The base Plugin classes, defined in
plugin.py
. All plugins must subclass the base plugins.
- The base Pluggable class, defined in
plugin_loader.py
.
Pluggable
. For example, the recipe card view is a pluggable, the database masterclass is a plugin, and so on. The Pluggable classes all access plugins through the singleton MasterLoader
class (plugin_gui.PluginChooser
provides a dialog that users use to enable and disable plugins). Note that each Plugin class provides a plugin to one aspect of Gourmet. However, what a user thinks of as a plugin will often comprise many different Plugin classes -- the nutrition plugin, for example, must provide different classes to plug in to the database, (DatabasePlugin
), the recipe card (RecipeCardPlugin
), the base exporters (BaseExporterPlugin
), etc.
Plugins that ship with Gourmet are located in the gourmet/plugins/
directory. If you want to experiment with a plugin, you can add the plugin to your local gourmet directory in the plugins
subdirectory (e.g. ~/.gourmet/plugins
on Linux and %APPDATA%\gourmet
on Windows).
Inevitably the best way to learn about plugins will be to find a plugin that does something similar to what you want to do, and read the code. However, it's also a good idea to read the below to get an overview of how it all works.
Each plugin is described in a generated file that ends in a .gourmet-plugin
suffix. Gourmet will search the default plugin directories for files with the .gourmet-plugin
suffix to learn about plugins. The .gourmet-plugin
file provides the info that will be provided to the user about the plugin and provides the name of the module that will be imported to initialize the plugin.
Below is the content of key_editor.gourmet-plugin
[Gourmet Plugin] Module=key_editor Version=1.0 API_Version=1.0 Name=Key Editor Comment=Assign and edit ingredient keys (unique identifiers for ingredients, used for consolidating items on shopping lists). Category=Tools Authors=Thomas M. Hinkle <[email protected]>
Module - The name of the python module to be imported,
Version - The plugin version number,
API_Version - The API_Version field is there as a precaution -- if the Gourmet plugin API develops in ways that aren't backward-compatible, we'll update that number and it will let us warn users about plugins that are no longer compatible. At present, we don't do anything with it,
Name - The name of the plugin displayed under Setting -> Plugins,
Comment - A brief description of the plugin shown under Setting -> Plugins,
Category - The name of the tab where the plugin is located under Setting -> Plugins (i.e. Main, Tools, Importer/Exporter),
Authors - The plugin's authors' name and contact information.
Create a file with a .gourmet-plugin.in
extension that includes the above content. Mark the fields that are to be translated with a proceeding "_". The translated .gourmet-plugin
files are generated from .gourmet-plugin.in
files by intltool
when localized files are built by executing the following command from the root of the Gourmet source tree:
$ python setup.py build_i18n -m
Below is the content of email.gourmet-plugin.in
[Gourmet Plugin] Module=key_editor Version=1.0 API_Version=1.0 _Name=Key Editor _Comment=Assign and edit ingredient keys (unique identifiers for ingredients, used for consolidating items on shopping lists). _Category=Tools Authors=Thomas M. Hinkle <[email protected]>
Gourmet will import the module named in the .gourmet-plugin
file (the module should be in the same directory). The module needs to define one variable -- the plugins
variable, which is a list of plugin classes that make up this plugin. As mentioned above, what the user experiences as a plugin may actually be more than one plugin. Here are some examples.
plugins = [emailer_plugin.EmailRecipePlugin]
The email_plugin
package (gourmet/plugins/email_plugin/__init.py__
) defines a plugin list with a single item:
emailer_plugin
emailer_plugin
class is a subclass of the MainPlugin
and UIPlugin
plugins = [keyEditorPlugin.KeyEditorPlugin, recipeEditorPlugin.IngredientKeyEditorPlugin, recipeEditorPlugin.KeyEditorIngredientControllerPlugin, ]
The key_editor
package defines a plugin list with multiple items:
keyEditorPlugin
recipeEditorPlugin
KeyEditorPlugin
class is a subclass of the ToolPlugin
-- a plugin that is added to the Tools menu of the recipe card and recipe index views. In this case, the plugin simply provides a dialog that allows the user to edit ingredient keys en masse.
plugins = [data_plugin.NutritionDataPlugin, main_plugin.NutritionMainPlugin, reccard_plugin.NutritionDisplayPlugin, export_plugin.NutritionBaseExporterPlugin, shopping_plugin.ShoppingNutritionalInfoPlugin, nutPrefsPlugin.NutritionPrefs, ]Adding nutritional information to Gourmet is a much more complicated matter:
-
data_plugin
adds tables to the Gourmet database to handle nutritional information -
reccard_plugin
adds nutritional information to the recipe card display. -
export_plugin
writes nutritional information into all exports (so that nutritional information shows up in printouts, PDFs, etc), and so on.
The classes listed in the modules actually do all the work of being a plugin. In each case, you're subclassing one of the classes in gourmet/plugin.py
, so you start by importing that base class and then creating the subclass.
The basics are as follows:
The plugin class is instantiated when the plugin is first activated.
For each pluggable the plugin plugs into, the activate()
method is called with the pluggable as its argument -- this allows the plugin to access the pluggable and do things to it. This is called once per pluggable instance either when the plugin is activated by the user or when the pluggable is instantiated (if the pluggable is already turned on).
from gourmet.plugin import BaseClassPlugin class MyPlugin(BaseClassPlugin) def activate(self, pluggable): # Activate plugin
Different plugin base classes provide convenience methods to make simple plugins easier to do. For example, for a number of classes you can use UIManager's convenience to add menu items and actions simply by creating the write XML string with the menu description and creating an ActionManager with the actions described.
Plugin base classes allow plugin writers to access virtually every aspect of the code, including:
- The database (adding tables, accessing and storing data, etc)
- The menus
- The base interfaces (it's easy to add a tab with new functionality to the main interface or the recipe card)
- import/export & print functionality
- A simple plugin that provides a "tool" -- see
unit_converter
plugin. - A plugin that modifies every textbox in a recipe card -- see
spellcheck
plugin - A plugin that provides print support -- see
import_export/pdf_plugin
plugin - A plugin that adds a tab to the main interface -- see
browse_recipes
plugin - A complex plugin that modifies nearly everything -- see
nutritional_information
plugin.