Свяжите приложение Python как единственный файл для поддержки дополнений или расширений?

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

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

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

7
задан Brandon Rhodes 20 May 2010 в 19:04
поделиться

2 ответа

У вас должна быть возможность иметь каталог плагинов, который ваше приложение сканирует во время выполнения (или позже) для импорта рассматриваемого кода. Вот пример, который должен работать с обычным кодом .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» в каталоге с именем «plugins» (то есть в базовом каталоге моего приложения), который добавит в мое приложение новые возможности. Его содержимое может выглядеть так:

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 - это веб-приложение, но нет причин, по которым этот метод не работал бы с обычным графическим интерфейсом. В приведенном выше примере я решил использовать воображаемую функцию add_menu_entries () в сочетании с объектной переменной Plugin, которую можно использовать для добавления методов плагина в меню вашего графического интерфейса.

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

7
ответ дан 7 December 2019 в 07:40
поделиться

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

0
ответ дан 7 December 2019 в 07:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: