2010-05-20 9 views
7

Ci sono diversi programmi di utilità - tutti con diverse procedure, limitazioni, e sistemi operativi di destinazione - per ottenere un pacchetto di Python e tutte le sue dipendenze e trasformarli in un unico programma binario che è facile da spedire ai clienti:Raggruppa un'applicazione Python come un singolo file per supportare componenti aggiuntivi o estensioni?

La mia situazione va oltre: gli sviluppatori di terze parti vorranno scrivere plug-in, estensioni o componenti aggiuntivi per la mia applicazione. È, ovviamente, una domanda scoraggiante in che modo gli utenti su piattaforme come Windows possano installare più facilmente plug-in o componenti aggiuntivi in ​​modo tale che la mia app possa facilmente scoprire che sono stati installati. Ma oltre a questa domanda di base c'è un altro: come può uno sviluppatore di terze parti impacchettare la propria estensione con le librerie di cui ha bisogno l'estensione stessa (che potrebbero essere moduli binari, come lxml) in modo tale che le dipendenze del plugin siano disponibili per l'importazione allo stesso tempo in cui il plug-in diventa disponibile.

Come può essere affrontato? La mia applicazione necessiterà della propria area plug-in su disco e del proprio registro plug-in per renderlo trattabile? O ci sono meccanismi generali, che potrei evitare di scrivere me stesso, che consentirebbero a un'app distribuita come un singolo eseguibile di guardarsi intorno e trovare plugin che vengono installati anche come file singoli?

risposta

7

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.

+0

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? –

+0

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

0

Ecco un altro esempio di un'applicazione Python che utilizza i plug-in: OpenSTV. Qui, i plugin possono essere solo moduli Python.

Problemi correlati