Создание минимальной архитектуры плагинов в Python


190

У меня есть приложение, написанное на Python, которое используется довольно технической аудиторией (учеными).

Я ищу хороший способ сделать приложение расширяемым пользователями, то есть архитектуру сценариев / плагинов.

Я ищу что-то чрезвычайно легкое . Большинство сценариев, или плагинов, не будут разрабатываться и распространяться сторонними разработчиками и устанавливаться, но через несколько минут пользователь собирается что-то сделать, чтобы автоматизировать повторяющуюся задачу, добавить поддержку формата файла, и т.д. Таким образом, плагины должны иметь абсолютный минимальный шаблонный код и не требовать «установки», кроме копирования в папку (поэтому что-то вроде точек входа setuptools или архитектуры плагинов Zope выглядит слишком много.)

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

Ответы:


150

Мой, в основном, каталог, называемый «плагинами», который основное приложение может опрашивать, а затем использовать imp.load_module для получения файлов, поиска общеизвестной точки входа, возможно, с параметрами конфигурации уровня модуля, и перехода оттуда. Я использую материал для мониторинга файлов для определенного динамизма, при котором плагины активны, но это приятно иметь.

Разумеется, любое требование, которое гласит: «Мне не нужен [большой, сложный предмет] X; я просто хочу что-нибудь легковесное», рискует повторно реализовать X по одному обнаруженному требованию за раз. Но это не значит, что вы все равно не можете повеселиться, делая это :)


26
Большое спасибо! Я написал небольшое руководство на основе вашего поста: lkubuntu.wordpress.com/2012/10/02/writing-a-python-plugin-api
MiJyn

9
impМодуль является устаревшим в пользу importlibначиная с питоном 3.4
b0fh

1
Во многих случаях вы можете использовать importlib.import_module в качестве замены imp.load_module.
Крис Арндт

58

module_example.py:

def plugin_main(*args, **kwargs):
    print args, kwargs

loader.py:

def load_plugin(name):
    mod = __import__("module_%s" % name)
    return mod

def call_plugin(name, *args, **kwargs):
    plugin = load_plugin(name)
    plugin.plugin_main(*args, **kwargs)

call_plugin("example", 1234)

Он, безусловно, «минимальный», в нем нет абсолютно никакой проверки ошибок, возможно, множество проблем с безопасностью, он не очень гибкий - но он должен показать вам, насколько простой может быть система плагинов в Python.

Вы , вероятно , хотите , чтобы заглянуть в имп модуль тоже, хотя вы можете сделать много с просто __import__, os.listdirи некоторые строки манипуляции.


4
Я думаю , вы можете захотеть изменить def call_plugin(name, *args)к def call_plugin(name, *args, **kwargs), а затем plugin.plugin_main(*args)вplugin.plugin_main(*args, **kwargs)
Рон Клейн

12
В питоне 3 impустарела в пользуimportlib
Адам Бакстер


25

Хотя этот вопрос действительно интересный, я думаю, что на него довольно сложно ответить без подробностей. Что это за приложение? У него есть графический интерфейс? Это инструмент командной строки? Набор скриптов? Программа с уникальной точкой входа и т.д ...

Учитывая небольшую информацию, которую я имею, я отвечу в очень общей форме.

Что значит добавить плагины?

  • Вам, вероятно, придется добавить файл конфигурации, в котором будут перечислены пути / каталоги для загрузки.
  • Другим способом было бы сказать «любые файлы в этом каталоге плагина / будут загружены», но неудобно требовать от ваших пользователей перемещаться по файлам.
  • Последним, промежуточным вариантом было бы требовать, чтобы все плагины были в одной и той же папке / плагинах, а затем активировать / деактивировать их, используя относительные пути в файле конфигурации.

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

Пример использования хуков , вдохновленных MediaWiki (PHP, но действительно ли язык имеет значение?):

import hooks

# In your core code, on key points, you allow user to run actions:
def compute(...):
    try:
        hooks.runHook(hooks.registered.beforeCompute)
    except hooks.hookException:
        print('Error while executing plugin')

    # [compute main code] ...

    try:
        hooks.runHook(hooks.registered.afterCompute)
    except hooks.hookException:
        print('Error while executing plugin')

# The idea is to insert possibilities for users to extend the behavior 
# where it matters.
# If you need to, pass context parameters to runHook. Remember that
# runHook can be defined as a runHook(*args, **kwargs) function, not
# requiring you to define a common interface for *all* hooks. Quite flexible :)

# --------------------

# And in the plugin code:
# [...] plugin magic
def doStuff():
    # ....
# and register the functionalities in hooks

# doStuff will be called at the end of each core.compute() call
hooks.registered.afterCompute.append(doStuff)

Еще один пример, вдохновленный ртутью. Здесь расширения только добавляют команды к исполняемый файл командной строки hg , расширяя поведение.

def doStuff(ui, repo, *args, **kwargs):
    # when called, a extension function always receives:
    # * an ui object (user interface, prints, warnings, etc)
    # * a repository object (main object from which most operations are doable)
    # * command-line arguments that were not used by the core program

    doMoreMagicStuff()
    obj = maybeCreateSomeObjects()

# each extension defines a commands dictionary in the main extension file
commands = { 'newcommand': doStuff }

Для обоих подходов вам может понадобиться общий инициализация и финализация вашего расширения. Вы можете либо использовать общий интерфейс, который нужно реализовать всем вашим расширением (лучше подходит для второго подхода; Mercurial использует reposetup (ui, repo), который вызывается для всех расширений), либо использовать подход типа ловушки с hooks.setup hook.

Но опять же, если вы хотите получить более полезные ответы, вам придется сузить свой вопрос;)


11

Простая структура плагинов Марти Аллчина - это база, которую я использую для своих нужд. Я действительно рекомендую взглянуть на это, я думаю, что это действительно хорошее начало, если вы хотите что-то простое и легко взломанное. Вы также можете найти его как Django Snippets .


Я пытаюсь сделать что-то подобное с Pyduck в качестве основы.
Эдомавр

Это очень специфично для Джанго из того, что я могу сказать.
Зоран Павлович

3
@ZoranPavlovic: нет, некоторые строки стандартного Python, вам не нужно использовать Django.
Эдомавр

11

Я биолог в отставке, который занимался цифровыми микрографами и обнаружил, что ему нужно написать пакет обработки и анализа изображений (технически это не библиотека) для работы на машине SGi. Я написал код на C и использовал Tcl для языка сценариев. GUI, каким бы он ни был, был сделан с использованием Tk. Команды, которые появились в Tcl, имели форму «extensionName commandName arg0 arg1 ... param0 param1 ...», то есть простые разделенные пробелом слова и числа. Когда Tcl увидел подстроку extensionName, управление было передано пакету C. Это, в свою очередь, запускало команду через лексер / парсер (сделанный в lex / yacc) и затем вызывало подпрограммы Си по мере необходимости.

Команды для работы с пакетом можно запускать одну за другой через окно в графическом интерфейсе, но пакетные задания выполнялись путем редактирования текстовых файлов, которые были действительными сценариями Tcl; Вы выбрали шаблон, который выполнял ту операцию на уровне файлов, которую вы хотели выполнить, и затем отредактировали копию, чтобы она содержала действительные имена каталогов и файлов, а также команды пакета. Оно работало завораживающе. До того как ...

1) Мир превратился в ПК и 2) сценарии получили более 500 строк, когда неудобные организационные возможности Tcl начали становиться настоящим неудобством. Время прошло ...

Я вышел на пенсию, Python был изобретен, и это выглядело как идеальный преемник Tcl. Теперь я никогда не занимался портом, потому что никогда не сталкивался с проблемами компиляции (довольно больших) программ на C на ПК, расширения Python с помощью пакета C и создания GUI на Python / Gt? / Tk? /? ?. Тем не менее, старая идея наличия редактируемых шаблонных скриптов кажется все еще работоспособной. Кроме того, ввод команд пакета в нативной форме Python не должен быть слишком тяжелым, например:

packageName.command (arg0, arg1, ..., param0, param1, ...)

Несколько лишних точек, паренов и запятых, но это не шоу-стопперы.

Я помню, что кто-то делал версии lex и yacc на Python (попробуйте: http://www.dabeaz.com/ply/ ), поэтому, если они все еще нужны, они есть.

Суть этого бессвязного в том, что мне показалось, что сам Python - это желаемый «легкий» интерфейс, используемый учеными. Мне любопытно узнать, почему вы думаете, что это не так, и я имею в виду это серьезно.


добавлено позже: Gedit приложения ожидает добавления плагинов, и на их сайте есть самое ясное объяснение простой процедуры плагинов, которую я нашел за несколько минут осмотра. Пытаться:

https://wiki.gnome.org/Apps/Gedit/PythonPluginHowToOld

Я все еще хотел бы лучше понять ваш вопрос. Мне неясно, хотите ли вы, 1), чтобы ученые могли использовать ваше (Python) приложение довольно просто различными способами, или 2) хотите, чтобы ученые добавили новые возможности в ваше приложение. Выбор № 1 - это ситуация, с которой мы столкнулись с изображениями, и которая заставила нас использовать общие сценарии, которые мы изменили в соответствии с потребностями момента. Это вариант № 2, который приводит вас к идее плагинов, или это какой-то аспект вашего приложения, который делает невыполнимым выполнение команд?



1
Это прекрасный пост, потому что он ясно и кратко показывает, насколько удачливы мы, современные биологи. Для него / нее python - это модульный язык сценариев, используемый для придания некоторой абстракции разработчикам модулей, чтобы им не нужно было разбирать основной код C. В настоящее время, однако, немногие биологи когда-либо изучают C, вместо этого все делают на Python. Как мы абстрагируемся от сложностей наших основных программ на Python при написании модулей? Через 10 лет, возможно, программы будут написаны на Emoji, а модули будут просто аудиофайлами, содержащими серию ворчаний. И, возможно, это нормально.
JJ

10

Когда я искал Python Decorators, нашел простой, но полезный фрагмент кода. Это может не соответствовать вашим потребностям, но очень вдохновляет.

Scipy Advanced Python # Система регистрации плагинов

class TextProcessor(object):
    PLUGINS = []

    def process(self, text, plugins=()):
        if plugins is ():
            for plugin in self.PLUGINS:
                text = plugin().process(text)
        else:
            for plugin in plugins:
                text = plugin().process(text)
        return text

    @classmethod
    def plugin(cls, plugin):
        cls.PLUGINS.append(plugin)
        return plugin


@TextProcessor.plugin
class CleanMarkdownBolds(object):
    def process(self, text):
        return text.replace('**', '')

Использование:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")

1
Примечание. В этом примере WordProcessor.pluginничего не возвращается ( None), поэтому, импортируя CleanMdashesExtensionкласс позже, вы просто импортируете None. Если классы плагинов полезны сами по себе, создайте .pluginметод class return plugin.
jkmacc

@jkmacc Вы правы. Я изменил фрагмент через 13 дней после вашего комментария. Спасибо.
guneysus

7

Мне понравилось приятное обсуждение различных архитектур плагинов, которое д-р Андре Роберге дал на Pycon 2009. Он дает хороший обзор различных способов реализации плагинов, начиная с чего-то действительно простого.

Это доступно как подкаст (вторая часть после объяснения исправления обезьяны), сопровождаемый серией шести записей в блоге .

Я рекомендую внимательно выслушать его, прежде чем принять решение.


4

Я приехал сюда в поисках минимальной архитектуры плагинов и обнаружил много вещей, которые мне показались излишними. Итак, я реализовал Super Simple Python Plugins . Чтобы использовать его, вы создаете один или несколько каталогов и добавляете специальный __init__.pyфайл в каждый. Импортирование этих каталогов приведет к загрузке всех других файлов Python в виде подмодулей, и их имена будут помещены в __all__список. Затем вы должны проверить / инициализировать / зарегистрировать эти модули. В файле README есть пример.


4

На самом деле setuptools работает с «каталогом плагинов», как показано в следующем примере из документации проекта: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Пример использования:

plugin_dirs = ['foo/plugins'] + sys.path
env = Environment(plugin_dirs)
distributions, errors = working_set.find_plugins(env)
map(working_set.add, distributions)  # add plugins+libs to sys.path
print("Couldn't load plugins due to: %s" % errors)

В долгосрочной перспективе setuptools является гораздо более безопасным выбором, поскольку он может загружать плагины без конфликтов или отсутствующих требований.

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


3

В качестве одного из подходов к системе плагинов, Вы можете проверить проект «Расширяй меня» .

Например, давайте определим простой класс и его расширение

# Define base class for extensions (mount point)
class MyCoolClass(Extensible):
    my_attr_1 = 25
    def my_method1(self, arg1):
        print('Hello, %s' % arg1)

# Define extension, which implements some aditional logic
# or modifies existing logic of base class (MyCoolClass)
# Also any extension class maby be placed in any module You like,
# It just needs to be imported at start of app
class MyCoolClassExtension1(MyCoolClass):
    def my_method1(self, arg1):
        super(MyCoolClassExtension1, self).my_method1(arg1.upper())

    def my_method2(self, arg1):
        print("Good by, %s" % arg1)

И попробуйте использовать это:

>>> my_cool_obj = MyCoolClass()
>>> print(my_cool_obj.my_attr_1)
25
>>> my_cool_obj.my_method1('World')
Hello, WORLD
>>> my_cool_obj.my_method2('World')
Good by, World

И покажи, что скрыто за кулисами:

>>> my_cool_obj.__class__.__bases__
[MyCoolClassExtension1, MyCoolClass]

Библиотека extend_me управляет процессом создания класса с помощью метаклассов, таким образом, в примере выше, при создании нового экземпляра MyCoolClassмы получили экземпляр нового класса, который является подклассом обоих MyCoolClassExtensionи MyCoolClassобладает функциональностью обоих из них, благодаря множественному наследованию Python

Для лучшего контроля над созданием классов в этой библиотеке есть несколько метаклассов:

  • ExtensibleType - допускает простую расширяемость путем создания подклассов

  • ExtensibleByHashType - похож на ExtensibleType, но имеет возможность создавать специализированные версии класса, позволяя глобальное расширение базового класса и расширение специализированных версий класса

Эта библиотека используется в проекте OpenERP Proxy и, кажется, работает достаточно хорошо!

Для реального примера использования посмотрите в расширении 'field_datetime' OpenERP Proxy :

from ..orm.record import Record
import datetime

class RecordDateTime(Record):
    """ Provides auto conversion of datetime fields from
        string got from server to comparable datetime objects
    """

    def _get_field(self, ftype, name):
        res = super(RecordDateTime, self)._get_field(ftype, name)
        if res and ftype == 'date':
            return datetime.datetime.strptime(res, '%Y-%m-%d').date()
        elif res and ftype == 'datetime':
            return datetime.datetime.strptime(res, '%Y-%m-%d %H:%M:%S')
        return res

Recordвот расширяемый объект. RecordDateTimeэто расширение.

Чтобы включить расширение, просто импортируйте модуль, который содержит класс расширения, и (в случае выше) все Recordобъекты, созданные после него, будут иметь класс расширения в базовых классах, таким образом, имея всю свою функциональность.

Основным преимуществом этой библиотеки является то, что код, который управляет расширяемыми объектами, не должен знать о расширении, и расширения могут изменить все в расширяемых объектах.


Я думаю, что вы хотите создать экземпляр из подкласса, то есть my_cool_obj = MyCoolClassExtension1()вместоmy_cool_obj = MyCoolClass()
pylang

нет, в расширяемом классе есть переопределенный __new__метод, поэтому он автоматически находит все подклассы и создает новый класс, который является подклассом для всех них, и возвращает новый экземпляр этого созданного класса. Таким образом, оригинальному приложению не нужно знать обо всех расширениях. этот подход полезен при создании библиотеки, чтобы позволить конечному пользователю легко изменять или расширять его поведение. в приведенном выше примере MyCoolClass может быть определен в библиотеке и использован ею, а MyCoolClassExtension может быть определен конечным пользователем.
FireMage

В ответ был добавлен еще один пример
FireMage

3

У setuptools есть EntryPoint :

Точки входа - это простой способ для «рекламы» объектов Python (таких как функции или классы) для использования другими дистрибутивами. Расширяемые приложения и платформы могут искать точки входа с определенным именем или группой либо из определенного дистрибутива, либо из всех активных дистрибутивов в sys.path, а затем проверять или загружать рекламируемые объекты по своему желанию.

AFAIK этот пакет всегда доступен, если вы используете pip или virtualenv.


2

В качестве дополнения к ответу @ edomaur, я могу предложить взглянуть на simple_plugins (бесстыдный плагин), который представляет собой простую структуру плагинов, вдохновленную работой Марти Алчина .

Краткий пример использования на основе README проекта:

# All plugin info
>>> BaseHttpResponse.plugins.keys()
['valid_ids', 'instances_sorted_by_id', 'id_to_class', 'instances',
 'classes', 'class_to_id', 'id_to_instance']

# Plugin info can be accessed using either dict...
>>> BaseHttpResponse.plugins['valid_ids']
set([304, 400, 404, 200, 301])

# ... or object notation
>>> BaseHttpResponse.plugins.valid_ids
set([304, 400, 404, 200, 301])

>>> BaseHttpResponse.plugins.classes
set([<class '__main__.NotFound'>, <class '__main__.OK'>,
     <class '__main__.NotModified'>, <class '__main__.BadRequest'>,
     <class '__main__.MovedPermanently'>])

>>> BaseHttpResponse.plugins.id_to_class[200]
<class '__main__.OK'>

>>> BaseHttpResponse.plugins.id_to_instance[200]
<OK: 200>

>>> BaseHttpResponse.plugins.instances_sorted_by_id
[<OK: 200>, <MovedPermanently: 301>, <NotModified: 304>, <BadRequest: 400>, <NotFound: 404>]

# Coerce the passed value into the right instance
>>> BaseHttpResponse.coerce(200)
<OK: 200>

2

Я потратил время на чтение этой ветки, пока искал фреймворк для плагинов в Python. Я использовал некоторые, но были недостатки с ними. Вот что я придумываю для вашего исследования в 2017 году, свободной от интерфейса, слабо связанной системы управления плагинами: загрузите меня позже . Вот учебники о том, как его использовать.


2

Вы можете использовать pluginlib .

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

Создайте родительский класс плагина, определяя любые необходимые методы:

import pluginlib

@pluginlib.Parent('parser')
class Parser(object):

    @pluginlib.abstractmethod
    def parse(self, string):
        pass

Создайте плагин, унаследовав родительский класс:

import json

class JSON(Parser):
    _alias_ = 'json'

    def parse(self, string):
        return json.loads(string)

Загрузите плагины:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))

1
Спасибо за пример. Я боролся с 1 вопросом. Поскольку вы упомянули возможность загрузки плагинов из разных пакетов, возможно, вы уже думали об этом. Интересно, где должен находиться родительский класс? Обычно рекомендуется иметь его в пакете приложения (предположительно, в отдельном хранилище исходного кода), но тогда как бы мы унаследовали его от него в базе кода плагина? Мы импортируем целое приложение для этого? Или необходимо иметь интерфейсный код, такой как класс Parser или подобные абстракции в третьем пакете (который будет третьим хранилищем кода)?
JAponte

1
Родительский класс должен находиться в той же кодовой базе, что и приложение, но, вероятно, в своем собственном модуле. Таким образом, для вызываемого пакета у fooвас может быть модуль, в foo.parentsкотором вы определяете родительские классы. Тогда ваши плагины будут импортированы foo.parents. Это хорошо работает для большинства случаев использования. Поскольку «foo» также импортируется, чтобы избежать возможности циклического импорта, многие проекты оставляют корень модуля пустым и используют __main__.pyфайл или точки входа для запуска приложения.
Aviso

1

Я потратил много времени, пытаясь найти небольшую систему плагинов для Python, которая бы соответствовала моим потребностям. Но тогда я просто подумал, что если наследство уже есть, которое является естественным и гибким, почему бы не использовать его.

Единственная проблема с использованием наследования для плагинов состоит в том, что вы не знаете, какие классы плагинов наиболее специфичны (самые низкие в дереве наследования).

Но это можно решить с помощью метакласса, который отслеживает наследование базового класса, и, возможно, может создать класс, который наследуется от большинства конкретных плагинов («Root extended» на рисунке ниже)

введите описание изображения здесь

Итак, я пришел с решением, написав такой метакласс:

class PluginBaseMeta(type):
    def __new__(mcls, name, bases, namespace):
        cls = super(PluginBaseMeta, mcls).__new__(mcls, name, bases, namespace)
        if not hasattr(cls, '__pluginextensions__'):  # parent class
            cls.__pluginextensions__ = {cls}  # set reflects lowest plugins
            cls.__pluginroot__ = cls
            cls.__pluginiscachevalid__ = False
        else:  # subclass
            assert not set(namespace) & {'__pluginextensions__',
                                         '__pluginroot__'}     # only in parent
            exts = cls.__pluginextensions__
            exts.difference_update(set(bases))  # remove parents
            exts.add(cls)  # and add current
            cls.__pluginroot__.__pluginiscachevalid__ = False
        return cls

    @property
    def PluginExtended(cls):
        # After PluginExtended creation we'll have only 1 item in set
        # so this is used for caching, mainly not to create same PluginExtended
        if cls.__pluginroot__.__pluginiscachevalid__:
            return next(iter(cls.__pluginextensions__))  # only 1 item in set
        else:
            name = cls.__pluginroot__.__name__ + 'PluginExtended'
            extended = type(name, tuple(cls.__pluginextensions__), {})
            cls.__pluginroot__.__pluginiscachevalid__ = True
return extended

Поэтому, когда у вас есть Root-база, созданная с метаклассом, и у вас есть дерево плагинов, которые наследуют его, вы можете автоматически получить класс, который наследует от самых специфических плагинов, просто создав подклассы:

class RootExtended(RootBase.PluginExtended):
    ... your code here ...

Кодовая база довольно мала (~ 30 строк чистого кода) и настолько гибка, насколько позволяет наследование.

Если вам интересно, присоединяйтесь @ https://github.com/thodnev/pluginlib


1

Вы также можете взглянуть на Groundwork .

Идея состоит в том, чтобы создавать приложения вокруг повторно используемых компонентов, называемых шаблонами и плагинами. Плагины - это классы, которые являются производными от GwBasePattern. Вот основной пример:

from groundwork import App
from groundwork.patterns import GwBasePattern

class MyPlugin(GwBasePattern):
    def __init__(self, app, **kwargs):
        self.name = "My Plugin"
        super().__init__(app, **kwargs)

    def activate(self): 
        pass

    def deactivate(self):
        pass

my_app = App(plugins=[MyPlugin])       # register plugin
my_app.plugins.activate(["My Plugin"]) # activate it

Есть также более продвинутые шаблоны для обработки, например, интерфейсов командной строки, сигнализации или общих объектов.

Groundwork находит свои плагины либо программно, связывая их с приложением, как показано выше, либо автоматически через setuptools. Пакеты Python, содержащие плагины, должны объявлять их с помощью специальной точки входа groundwork.plugin.

Вот документы .

Отказ от ответственности : я один из авторов Groundwork.


0

В нашем текущем медицинском продукте у нас есть подключаемая архитектура с интерфейсом класса. Наш технический стек - это Django поверх Python для API и Nuxtjs поверх nodejs для внешнего интерфейса.

Для нашего продукта написано приложение менеджера плагинов, которое в основном состоит из пакетов pip и npm в соответствии с Django и Nuxtjs.

Для разработки нового плагина (pip и npm) мы сделали менеджер плагинов в качестве зависимости.

В пакете Pip: с помощью setup.py вы можете добавить точку входа плагина, чтобы что-то делать с менеджером плагинов (реестр, инициации, ... и т. Д.) Https://setuptools.readthedocs.io/en/latest/setuptools .html # автоматического сценария создания

В пакете npm: Как и в pip, в скриптах npm есть хуки для установки. https://docs.npmjs.com/misc/scripts

Наш случай использования:

Команда разработчиков плагинов теперь отделена от основной команды разработчиков. Сфера разработки плагинов предназначена для интеграции со сторонними приложениями, которые определены в любой из категорий продукта. Интерфейсы плагинов подразделяются, например, на: - Факс, телефон, электронная почта ... и т. Д. Менеджер плагинов может быть расширен до новых категорий.

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

Если разработчикам плагинов необходимо использовать повторно использовать основные объекты, этот объект можно использовать, выполнив уровень абстракции в менеджере плагинов, чтобы любые плагины могли наследовать эти методы.

Просто поделитесь, как мы внедрили в наш продукт, надеюсь, что это даст немного идеи.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.