PluginArchitecture: Difference between revisions

From PyMOLWiki
Jump to navigation Jump to search
(document current architecture)
 
(9 intermediate revisions by 3 users not shown)
Line 1: Line 1:
This page concerns the development for new PyMOL Plugin Architecture. We are requesting ideas, code, etc on how to best implement a successful plug in system.
This page describes PyMOL's plugin architechture since version 1.5.0.6.


= Overview =
== API Entry Points ==
PyMOL enjoys widespread use as 3D molecular visualization program with strengths in rendering and visualization.  PyMOL at current does not have a robust extension mechanism for adding new plug-in based features to PyMOL.  It only allows a script of one file and has very limited support for installation and removal.  This project aims to correct that lacking functionality.  We aim for simplicity and robustness especially in a cross-platform manner.


A '''Plug-In''' (or '''Plugin''') is a modular piece of software that extends PyMOL's functionality.
A plugin is a Python module which uses PyMOL's API. The following entrypoints add functionality to PyMOL:


== Wanna' Help? ==
# [[extend|<code>pymol.cmd.extend(func)</code>]] registers a function as a PyMOL command
If you want to take part in helping out with this, please send Jason Vertrees an email letting him know as such.
# <code>pymol.plugins.addmenuitemqt(label, callback)</code> adds a plugin menu item, should be called inside [[#__init__plugin__|<code>__init_plugin__</code>]]
# [[#__init__plugin__|<code>MyPlugin.__init_plugin__(app)</code>]] is called during plugin initialization (if defined by the plugin)


= Review of Current Plugin System =
== __init_plugin__ ==


Essentially the current system inits all the scripts in the startup directory, which adds their entry to the Plugins menu.  When called, it fires off a command passing to it PyMOL's Tk menu. The instance can get access to pymol's cmd layer with, <source lang="python">from pymol import cmd</source>.  Also, as it's in Python, the user can extend Python (even in C) or call low-level functions utilizing the web or touching the file system.
A plugin can implement an <code>__init_plugin__(app)</code> function (''previously <code>__init__</code>, deprecated'') which is called during plugin initialization. It is passed an object which is compatible with the legacy <code>PMGApp</code> instance and provides access to the Tkinter parent (<code>app.root</code>).


* For a tutorial on the current system. see [[Plugins_Tutorial]].
=== Example for a PyQt5 GUI (PyMOL 2.x) ===


= Ideas/Features for the New Plugin System =
''See also the [[Plugins Tutorial]]''
* Plugins must be ''safe''. That is, they must not destroy the users' data for file system.
 
* Plugins must be ''secure'' in that they do not transmit data the user doesn't want transmitted.
<syntaxhighlight lang="python">
** We can take an approach like Warren did with sessions--warn the user on sessions s/he didn't create and ask for trusting or not.
def __init_plugin__(app=None):
* Plugins should be able to query PyMOL for features
    from pymol.plugins import addmenuitemqt
* PyMOL should take care of unpacking, installing, uninstalling, updating (?) the plugins
    addmenuitemqt('My Qt Plugin', myqtdialog)
* '''MUST''' be backwards compatible with the current system
 
* '''MUST''' run cross platform
def myqtdialog():
    from pymol.Qt import QtWidgets
    QtWidgets.QMessageBox.information(None, 'My Plugin Title', 'Hello World')
</syntaxhighlight>
 
=== Example for a legacy Tkinter GUI (PyMOL 1.x) ===
 
It is important that all Tkinter objects are created with the <code>app.root</code> parent, otherwise legacy plugins won't work in PyMOL 2.0.
 
<syntaxhighlight lang="python">
def __init_plugin__(app):
    app.menuBar.addmenuitem('Plugin', 'command',
        label='My Tk Plugin',
        command=lambda: mytkdialog(app.root))
 
def mytkdialog(parent):
    try:
        import tkMessageBox  # Python 2
    except ImportError:
        import tkinter.messagebox as tkMessageBox  # Python 3
    tkMessageBox.showinfo(parent=parent, title='My Plugin Title', message='Hello World')
</syntaxhighlight>


== More than one file per plugin ==
== More than one file per plugin ==


I'd like plugins that behave like proper Python modules with the ability to span multiple files/directories. One simple way to do this would be to distribute plugins as zip files. E.g. if we have a directory Foo that contains
Plugins can be either a single Python file, or a directory with an <code>__init__.py</code> file.
 
'''Single file layout:'''
 
<syntaxhighlight lang="python">
MyPlugin.py (defines "__init_plugin__(app=None)" function)
</syntaxhighlight>
 
'''Directory layout:'''


<source lang="Python">
<source lang="Python">
MyMod
MyPlugin/
    __init__.py
├── data
          """Some documentation here"""
│   ├── datasheet.txt
    Foo.py
│   └── image.png
        def doit():
├── submodule1.py
            print "It is done!"
├── submodule2.py
└── __init__.py (defines "__init_plugin__(app=None)" function)
</source>
</source>


We can zip it up like
They can be zipped (<code>.zip</code>) for distribution, the [[Plugin Manager]] handles the unzipping during installation.


<source lang="bash">
<source lang="bash">
zip MyMod.zip Foo/*
zip -r MyPlugin-1.0.zip MyPlugin
</source>
</source>


If you stick MyMod.zip in a plugins directory like /blahblah/Pymol/plugins/MyMod.zip
== Config files ==
you can then say this:
 
PyMOL offers a basic API to store custom settings in <code>~/.pymolpluginsrc.py</code>.
Only basic types (<code>str</code>,
<code>int</code>,
<code>float</code>,
<code>list</code>,
<code>tuple</code>,
<code>dict</code>,
etc.)
are suppored (those who produce executable code with <code>repr()</code>).


<source lang="Python">
'''Store:'''
sys.path.append('/blahblah/Pymol/plugins/MyMod.zip')
 
from MyMod import Foo
<syntaxhighlight lang="python">
Foo.doit()
pymol.plugins.pref_set(key, value)
</source>


Python does not impose the restriction that the module/directory name (MyMod) should be the same as the zip file name (MyMod.zip), but we might want to.
# needed if pymol.plugins.pref_get("instantsave") == False
pymol.plugins.pref_save()
</syntaxhighlight>


Then we need to formally spell out the API by which the Plugin is launched.
'''Load:'''


This makes it very easy to install/uninstall plugins, and PyMOL can just have a simple loop that iterates over the plugins directory, adds the appropriate things to sys.path, verifies that the resulting objects support the API, and adds them to the menu.
<syntaxhighlight lang="python">
value = pymol.plugins.pref_get(key, default=None)
</syntaxhighlight>


== Config files ==
'''Example:'''


Some sort of persistent config files where users can store things like paths to relevant files, settings, etc. would be very useful. The APBS plugin, for instance, is a bit of a bother to use when you have APBS installed in a non-standard location. It would be very convenient if you could set the location once and have it persist.
<syntaxhighlight lang="python">
apbsbinary = pymol.plugins.pref_get("APBS_BINARY_LOCATION", "/usr/bin/apbs")
</syntaxhighlight>


= Liked Plugin Systems =
== PyMOL OS Fellowship Project 2011/2012 ==
* [https://addons.mozilla.org/en-US/developers FireFox Devel/Docs]
* Google Chrome -- URL?


= Disliked Plugin Systems =
[[User:Speleo3|Thomas Holder]] was working on the new plugin system as part of his [http://pymol.org/fellowship 2011-2012 Fellowship]. The improvements were incorporated into PyMOL 1.5.0.5.


= Links on the topic =
* graphical [[Plugin Manager]]
* [http://stackoverflow.com/questions/323202/designing-extensible-software-plugin-architecture StackOverflow Discussion]
* multiple files per plugin/module (see [[#More than one file per plugin]])
* [http://en.wikipedia.org/wiki/Open/closed_principle Dr. Meyers' Open/Closed Principle]
* user plugin directory
* [http://www.codeguru.com/cpp/misc/misc/plug-insadd-ins/article.php/c3879 Nice overview in C]
* rarely used plugins can be disabled for better performance and enabled on demand
* metadata support (version, author, description, citation, tags, ...)
* install from online repositories
* settings API for plugins (see [[#Config files]])


= The Name =
== See Also ==
* Plugins, Plug-Ins?
* Add-ons?
* Extensions?


* [[Plugin Manager]]
* [[Plugins Tutorial]]
* [[Plugins]]


[[Category:Plugins]]
[[Category:Development]]
[[Category:Development]]
[[Category:Developers]]
[[Category:Developers]]

Latest revision as of 12:43, 13 November 2018

This page describes PyMOL's plugin architechture since version 1.5.0.6.

API Entry Points

A plugin is a Python module which uses PyMOL's API. The following entrypoints add functionality to PyMOL:

  1. pymol.cmd.extend(func) registers a function as a PyMOL command
  2. pymol.plugins.addmenuitemqt(label, callback) adds a plugin menu item, should be called inside __init_plugin__
  3. MyPlugin.__init_plugin__(app) is called during plugin initialization (if defined by the plugin)

__init_plugin__

A plugin can implement an __init_plugin__(app) function (previously __init__, deprecated) which is called during plugin initialization. It is passed an object which is compatible with the legacy PMGApp instance and provides access to the Tkinter parent (app.root).

Example for a PyQt5 GUI (PyMOL 2.x)

See also the Plugins Tutorial

def __init_plugin__(app=None):
    from pymol.plugins import addmenuitemqt
    addmenuitemqt('My Qt Plugin', myqtdialog)

def myqtdialog():
    from pymol.Qt import QtWidgets
    QtWidgets.QMessageBox.information(None, 'My Plugin Title', 'Hello World')

Example for a legacy Tkinter GUI (PyMOL 1.x)

It is important that all Tkinter objects are created with the app.root parent, otherwise legacy plugins won't work in PyMOL 2.0.

def __init_plugin__(app):
    app.menuBar.addmenuitem('Plugin', 'command',
        label='My Tk Plugin',
        command=lambda: mytkdialog(app.root))

def mytkdialog(parent):
    try:
        import tkMessageBox  # Python 2
    except ImportError:
        import tkinter.messagebox as tkMessageBox  # Python 3
    tkMessageBox.showinfo(parent=parent, title='My Plugin Title', message='Hello World')

More than one file per plugin

Plugins can be either a single Python file, or a directory with an __init__.py file.

Single file layout:

MyPlugin.py (defines "__init_plugin__(app=None)" function)

Directory layout:

MyPlugin/
├── data
   ├── datasheet.txt
   └── image.png
├── submodule1.py
├── submodule2.py
└── __init__.py (defines "__init_plugin__(app=None)" function)

They can be zipped (.zip) for distribution, the Plugin Manager handles the unzipping during installation.

zip -r MyPlugin-1.0.zip MyPlugin

Config files

PyMOL offers a basic API to store custom settings in ~/.pymolpluginsrc.py. Only basic types (str, int, float, list, tuple, dict, etc.) are suppored (those who produce executable code with repr()).

Store:

pymol.plugins.pref_set(key, value)

# needed if pymol.plugins.pref_get("instantsave") == False
pymol.plugins.pref_save()

Load:

value = pymol.plugins.pref_get(key, default=None)

Example:

apbsbinary = pymol.plugins.pref_get("APBS_BINARY_LOCATION", "/usr/bin/apbs")

PyMOL OS Fellowship Project 2011/2012

Thomas Holder was working on the new plugin system as part of his 2011-2012 Fellowship. The improvements were incorporated into PyMOL 1.5.0.5.

  • graphical Plugin Manager
  • multiple files per plugin/module (see #More than one file per plugin)
  • user plugin directory
  • rarely used plugins can be disabled for better performance and enabled on demand
  • metadata support (version, author, description, citation, tags, ...)
  • install from online repositories
  • settings API for plugins (see #Config files)

See Also