Собрать приложение Python в один файл для поддержки надстроек или расширений? - PullRequest
7 голосов
/ 20 мая 2010

Существует несколько утилит - все с различными процедурами, ограничениями и целевыми операционными системами - для получения пакета Python и всех его зависимостей и превращения их в одну двоичную программу, которую легко доставить клиентам:

Моя ситуация идет еще дальше: сторонние разработчики захотят написать плагины, расширения или дополнения для моего приложения. Это, конечно, непростой вопрос, как пользователи на платформах, таких как Windows, могли бы легче устанавливать плагины или дополнения таким образом, чтобы мое приложение могло легко обнаружить, что они были установлены. Но помимо этого основного вопроса другой: как сторонний разработчик может связать свое расширение с любыми библиотеками, в которых нуждается само расширение (которые могут быть двоичными модулями, такими как lxml) таким образом, что зависимости плагина становятся доступными для импорта одновременно время, когда плагин становится доступным.

Как к этому можно подойти? Нужно ли моему приложению иметь собственную область плагинов на диске и собственный реестр плагинов, чтобы сделать это доступным? Или есть общие механизмы, которые я мог бы не писать сам, которые позволили бы приложению, которое распространяется как один исполняемый файл, просматривать и находить плагины, которые также устанавливаются как отдельные файлы?

Ответы [ 2 ]

7 голосов
/ 04 июня 2010

У вас должна быть директория плагинов, которую ваше приложение сканирует во время выполнения (или позже) для импорта соответствующего кода. Вот пример, который должен работать с обычным кодом .py или .pyc, который работает даже с плагинами, хранящимися в zip-файлах (так что пользователи могут просто вставить someplugin.zip в каталог 'plugins' и заставить его работать волшебным образом):

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])

Допустим, у меня есть плагин с именем "foo.py" в каталоге с именем "плагины" (который находится в базовом каталоге моего приложения), который добавит новую возможность в мое приложение. Содержимое может выглядеть так:

from plugin_stuff import Plugin

class Foo(Plugin):
    """An example plugin."""
    self.menu_entry = {'Tools': {'Foo': self.bar}}
    def bar(self):
        return "foo plugin!"

Я мог бы инициализировать свои плагины при запуске приложения следующим образом:

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

Это должно работать, пока плагин представляет собой файл .py или .pyc (при условии, что он скомпилирован для рассматриваемой платформы байтово). Это может быть автономный файл или внутри каталога с init .py или внутри zip-файла с такими же правилами.

Откуда я знаю, что это работает? Так я реализовал плагины в PyCI . PyCI - это веб-приложение, но нет никаких причин, почему этот метод не будет работать для обычного старого GUI. В приведенном выше примере я решил использовать воображаемую функцию add_menu_entries () в сочетании с переменной объекта Plugin, которую можно использовать для добавления методов плагина в меню вашего графического интерфейса.

Надеюсь, этот ответ поможет вам построить собственную систему плагинов. Если вы хотите точно увидеть, как это реализовано, я рекомендую вам загрузить исходный код PyCI и посмотреть plugin_utils.py и плагин Example в каталоге plugins_enabled.

0 голосов
/ 16 июня 2010

Вот еще один пример приложения Python, использующего плагины: OpenSTV . Здесь плагинами могут быть только модули Python.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...