Skip to content

Plugins

saxon-s edited this page May 13, 2020 · 4 revisions

Table of Contents

Gourmet Recipe Manager plugin architecture

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 base classes

The basic Gourmet plugin architecture is based in two main sets of classes:

  1. The base Plugin classes, defined in plugin.py. All plugins must subclass the base plugins.
  2. The base Pluggable class, defined in plugin_loader.py.
Each Pluggable class accepts a certain subset of Plugin base classes as plugins. Any plugin registered with Gourmet of that class will be registered with the base class. Every feature of Gourmet that a plugin writer could want access will need to subclass 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.

What goes into a Plugin

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.

The .gourmet-plugin file

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 <thomas_hinkle@sf.net>

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 Gourmet Plugin

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 <thomas_hinkle@sf.net>

The module

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.

email_plugin

plugins = [emailer_plugin.EmailRecipePlugin]

The email_plugin package (gourmet/plugins/email_plugin/__init.py__) defines a plugin list with a single item:

  • emailer_plugin
The emailer_plugin class is a subclass of the MainPlugin and UIPlugin

key_editor

plugins = [keyEditorPlugin.KeyEditorPlugin,
           recipeEditorPlugin.IngredientKeyEditorPlugin,
           recipeEditorPlugin.KeyEditorIngredientControllerPlugin,
           ]

The key_editor package defines a plugin list with multiple items:

  • keyEditorPlugin
  • recipeEditorPlugin
The 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.

nutritional_information

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 plugin classes

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).

my_plugin.py

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.

Example plugins

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
If you are looking for examples, here are some pointers:
  • 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.