Syntax Highlighting

Saturday, 13 April 2013

Hello World Plugin for Spyder 2

The following is a set of instructions for writing a simple Hello World plugin for Spyder. Hopefully it will serve as a template for future plugins.
Note that this is for Spyder 2, which is not available on squeeze, but is available on wheezy. Be very careful if you have to upgrade your distribution. Alternatively, you can try to use the latest source code, which is what I ended up doing.
Note that some files also include directory structure; most files referred to here are all relative to the <source path>/spyderlib/spyderplugins directory.

Specification

The plugin will provide a plain window (that can be docked and resized as per the other windows), simply with a button and a label. The label will be blank until the button is pressed.
The plugin architecture will consist of three files helloengine, hellogui and p_hello, corresponding roughly to model, gui and plugin (interface to spyder).


Brief Walk-through

After Spyder has loaded its core components such Sift, Project Explorer, Variable Explorer, Object Inspector, Find in Files, etc,by the function setup in the MainWindow class (spyder.py), it then creates a list of third-party plugins
# this is an annotated extract from Mainwindow.setup()

# Third-party plugins
for mod in get_spyderplugins_mods(prefix='p_', extension='.py'):
    # get list of the mods in the plugins directory, 
    # which is found in the otherplugins.py file
    # each mod variable is a module, for example, spyderplugins.p_pylint
    try:
        # the module spyderplugins.p_pylint has a special variable, PLUGIN_CLASS
        # which is literally a reference the class you want to use as the Plugin
        plugin = mod.PLUGIN_CLASS(self)
        # note that plugin variable, which gets updated every time through the loop is 
        # is an initialisation of the 


        self.thirdparty_plugins.append(plugin)
        plugin.register_plugin()
    except AttributeError, error:
        print >>STDERR, "%s: %s" % (mod, str(error))

Setup

First of all I don't want to mess with the standard Debian settings, so I'll be getting the recommended packages for Spyder  and setting up a play directory for Spyder in my home directory.
Using aptitude:
  • Initially install spyder, include all the recommended packages.
  • Uninstall spyder and spyderlib. Aptitude may make a list of other packages that are going to be uninstalled (docutils, pygments, pep8, etc) as they aren't used by any other packages (I have very bare bones system). Manually mark them for installation, and then get the packages - which should uninstall spyder and spyderlib, but leave all the other packages.

    I should now have mercurial installed on my system because it was one of the recommended packages for Spyder.

    Make a new directory to play with and clone the repository:
    mkdir ~/usr/spyder2
    hg clone https://code.google.com/p/spyderlib/ 

    Hello Plugin for Spyder

    And now, we get to the walk-through for the Hello Plugin for Spyder. Spyder, as noted above, loops through all the plugins in the spyder plugin directory, and tries loading them.

    Spyder Loading our Plugin

    In the Hello plugin case, it will try:
    1. loading the module spyderplugins.p_hello
    2. finding the class name to be used as the plugin in the module variable PLUGIN_CLASS, which will be class HelloPlugin.
    3. Initializing the HelloPlugin (__init__(self, Parent))class with the Spyder MainWindow class being the parent.

    p_hello.py

    This file provides the final class definition that is used by Spyder for a plugin. Our plugin class (HelloPlugin) has the following variable and methods:
    • CONF_SECTION (= 'hello'): Not sure where this is used.
    • __init__(self, parent=None): the initialization method for the class. Our example calls a secondary initialisation method, but I don't think that this is strictly necessary.
    • get_plugin_title(self): <SpyderPluginWidget API> Only basic functionality defined.
    • get_plugin_icon(self): <SpyderPluginWidget API> Only basic functionality defined
    • get_focus_widget(self):<SpyderPluginWidget API> Only basic functionality defined
    • get_plugin_actions(self): <SpyderPluginWidget API> Only basic functionality defined
    • register_plugin(self): <SpyderPluginWidget API> I did not change any of the code in this method, except to change all the relevant parts to "hello". Note also, that one of registered actions is the method run_hello.
    • refresh_plugin(self): <SpyderPluginWidget API> Only basic functionality defined
    • closing_plugin(self, cancelable=False): <SpyderPluginWidget API> Only basic functionality defined
    • apply_plugin_settings(self, options): <SpyderPluginWidget API> Only basic functionality defined
    • initialize_plugin(self): <Public API> This method did not actually do anything, but does provide a hook to separate some of the initialisation code from the __init__method.
    • run_hello(self):  <Public API> This is the method that actually prints out the greetings. It calls the  method say_greetings()  inherited from HelloWidget.

    widgets/hellogui.py

    This file will provide the user interface description that is used by Spyder. By separating out the user interface from the main work module we create the option of obtaining workings of the hello module from other user interfaces.The main part of this file is the class HelloWidget which contains:
    • __init__(self, parent): basic initialisation of the inherited classes and creates the layout of GUI 
    • say_greetings(self): the triggered function for the button.

    widgets/helloengine.py

    The first file (model) simply provides the backend of the plugin, where all the work is actually done, (in this case providing the string "Hello"). In a useful plugin it might provide a database access, API connection to web resource or some kind of data transformation.
     # This is the entire working part of the file.
    class Hello():
        def __init__(self):
            self.greetings = "hello"
    The greetings variable is passed into the HelloWidget as the text to display.


    Summary Checks

    The following is a list of checks required to ensure that a plugin will be correctly implemented:
    • Spyder looks for plugins in the spyderplugins directory with filenames that start with "p_" and end with ".py". Ensure that the plugin that interfaces with Spyder meets these requirements.
    • In your plugin file, there is a variable PLUGIN_CLASS at the module level. This variable is the class that you wish to use as the plugin. I haven't tried it, but I assume that you could wrap that in a conditional, so that different plugin could be loaded depending on the conditional.
    • The plugin class __init__ function takes exactly two arguments: self and Parent. The parent is used by Qt to keep track of the widget hierarchy, i.e. the plugin becomes a widget of Spyder. Additional arguments could theoretically be used when initializing the plugin class, however, Spyder won't call them during the plugin plugging in.

    No comments:

    Post a Comment