Dovresti poter disporre di una directory di plug-in che l'applicazione esegue la scansione in fase di runtime (o successiva) per importare il codice in questione. Ecco un esempio che dovrebbe funzionare con .py regolare o codice .pyc che funziona anche con i plugin memorizzati all'interno dei file zip (così gli utenti possono semplicemente cadere someplugin.zip nella directory dei plugin '' e hanno magicamente funziona):
import re, os, sys
class Plugin(object):
"""
The base class from which all plugins are derived. It is used by the
plugin loading functions to find all the installed plugins.
"""
def __init__(self, foo):
self.foo = foo
# Any useful base plugin methods would go in here.
def get_plugins(plugin_dir):
"""Adds plugins to sys.path and returns them as a list"""
registered_plugins = []
#check to see if a plugins directory exists and add any found plugins
# (even if they're zipped)
if os.path.exists(plugin_dir):
plugins = os.listdir(plugin_dir)
pattern = ".py$"
for plugin in plugins:
plugin_path = os.path.join(plugin_dir, plugin)
if os.path.splitext(plugin)[1] == ".zip":
sys.path.append(plugin_path)
(plugin, ext) = os.path.splitext(plugin) # Get rid of the .zip extension
registered_plugins.append(plugin)
elif plugin != "__init__.py":
if re.search(pattern, plugin):
(shortname, ext) = os.path.splitext(plugin)
registered_plugins.append(shortname)
if os.path.isdir(plugin_path):
plugins = os.listdir(plugin_path)
for plugin in plugins:
if plugin != "__init__.py":
if re.search(pattern, plugin):
(shortname, ext) = os.path.splitext(plugin)
sys.path.append(plugin_path)
registered_plugins.append(shortname)
return registered_plugins
def init_plugin_system(cfg):
"""
Initializes the plugin system by appending all plugins into sys.path and
then using load_plugins() to import them.
cfg - A dictionary with two keys:
plugin_path - path to the plugin directory (e.g. 'plugins')
plugins - List of plugin names to import (e.g. ['foo', 'bar'])
"""
if not cfg['plugin_path'] in sys.path:
sys.path.insert(0, cfg['plugin_path'])
load_plugins(cfg['plugins'])
def load_plugins(plugins):
"""
Imports all plugins given a list.
Note: Assumes they're all in sys.path.
"""
for plugin in plugins:
__import__(plugin, None, None, [''])
if plugin not in Plugin.__subclasses__():
# This takes care of importing zipped plugins:
__import__(plugin, None, None, [plugin])
Quindi diciamo che ho un plugin chiamato "foo.py" in una directory chiamata "plugins" (che si trova nella directory di base della mia app) che aggiungerà una nuova funzionalità alla mia applicazione. Il contenuto potrebbe essere simile a questo:
from plugin_stuff import Plugin
class Foo(Plugin):
"""An example plugin."""
self.menu_entry = {'Tools': {'Foo': self.bar}}
def bar(self):
return "foo plugin!"
ho potuto inizializzare miei plugin quando lancio il mio app in questo modo:
plugin_dir = "%s/plugins" % os.getcwd()
plugin_list = get_plugins(plugin_dir)
init_plugin_system({'plugin_path': plugin_dir, 'plugins': plugin_list})
plugins = find_plugins()
plugin_menu_entries = []
for plugin in plugins:
print "Enabling plugin: %s" % plugin.__name__
plugin_menu_entries.append(plugin.menu_entry))
add_menu_entries(plugin_menu_entries) # This is an imaginary function
che dovrebbe funzionare fintanto che il plugin è o un .py o .pyc file (assumendo che sia byte-compilato per la piattaforma in questione). Può essere un file autonomo o all'interno di una directory con un init .py o all'interno di un file zip con le stesse regole.
Come faccio a sapere che funziona? È come ho implementato i plugin in PyCI. PyCI è un'applicazione web, ma non c'è motivo per cui questo metodo non funzioni per una normale GUI ol. Per l'esempio precedente ho scelto di utilizzare una funzione add_menu_entries() immaginaria in combinazione con una variabile dell'oggetto Plugin che potrebbe essere utilizzata per aggiungere i metodi di un plugin ai menu della GUI.
Speriamo che questa risposta ti aiuti a creare il tuo sistema di plugin. Se vuoi vedere esattamente come è implementato, ti consiglio di scaricare il codice sorgente PyCI e guardare plugin_utils.py e il plugin Example nella directory plugins_enabled.
Grazie per aver segnalato l'approccio di PyCl! Cosa fanno i suoi autori di plug-in quando vogliono raggruppare le dipendenze, come la libreria "lxml" che la mia domanda ha menzionato? Che cosa succede se entrambi i plugin hanno bisogno di "lxml" ma ognuno di questi raggruppa una versione leggermente diversa? E se "lxml" ha parti binarie che genereranno una DLL su Windows ma un .so su Linux? –
Gli autori di plug-in possono includere qualsiasi dipendenza di cui hanno bisogno nel percorso di base del proprio plug-in. Questo funziona perché il percorso del plugin viene aggiunto a sys.path subito prima che venga importato. L'unica avvertenza è che se un plugin richiede una dipendenza specifica della piattaforma, l'autore del plugin dovrebbe avere un pacchetto per ogni architettura che è disposto a supportare. Vorrei anche ricordare che PyCI include metodi (in Plugin()) per il recupero di file dalla directory del plugin. Quindi, se un plugin utilizza il contenuto statico, può essere pubblicato anche se il plugin si trova in un file zip. –