Difference between revisions of "PluginArchitecture"

From PyMOLWiki
Jump to: navigation, search
(PluginDirectory)
(document current architecture)
 
(3 intermediate revisions by the same user 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():
* Save user-specific information
+
    from pymol.Qt import QtWidgets
* Support proxy information/setup for downloading scripts
+
    QtWidgets.QMessageBox.information(None, 'My Plugin Title', 'Hello World')
* Support a [[PluginDirectory|personal plugin directory]], so users can administer their own plugins in situations where the PyMOL installation is read-only and shared among many users.
+
</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:
 
  
<source lang="Python">
+
PyMOL offers a basic API to store custom settings in <code>~/.pymolpluginsrc.py</code>.
sys.path.append('/blahblah/Pymol/plugins/MyMod.zip')
+
Only basic types (<code>str</code>,
from MyMod import Foo
+
<code>int</code>,
Foo.doit()
+
<code>float</code>,
</source>
+
<code>list</code>,
 +
<code>tuple</code>,
 +
<code>dict</code>,
 +
etc.)
 +
are suppored (those who produce executable code with <code>repr()</code>).
  
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.
+
'''Store:'''
  
Then we need to formally spell out the API by which the Plugin is launched.
+
<syntaxhighlight lang="python">
 +
pymol.plugins.pref_set(key, value)
  
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.
+
# needed if pymol.plugins.pref_get("instantsave") == False
 +
pymol.plugins.pref_save()
 +
</syntaxhighlight>
  
== Config files ==
+
'''Load:'''
  
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">
 +
value = pymol.plugins.pref_get(key, default=None)
 +
</syntaxhighlight>
  
== Meeting Notes ==
+
'''Example:'''
=== May 3, 2010 ===
 
In attendance: Nathan Baker, Mike Beachy, Greg Landrum, David Gohara, Yong Huang, Jay Ponder, Jason Vertrees
 
  
Additional ideas people wanted to see implemented:
+
<syntaxhighlight lang="python">
* As scripts will be accessing internal data for computation, improved access to PyMOL internal data structures, like DX maps, is required.
+
apbsbinary = pymol.plugins.pref_get("APBS_BINARY_LOCATION", "/usr/bin/apbs")
** I/O for maps is the rate limiting step;
+
</syntaxhighlight>
** data structures can be large, so skipping the disk would also be nice
 
* Skeleton code for plugins would help people write new ones; good tutorial as well; we need to make things easier for the novice
 
** Make a new site for plugins: plugins dot pymol dot org?
 
  
= 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