Побудова мінімальної архітектури плагінів у 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
У python 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), який викликається для всіх розширень), або використовувати підхід типу гачка, з гачки. гачок.

Але знову ж таки, якщо ви хочете більше корисних відповідей, вам доведеться звузити своє питання;)


11

Проста рамка плагінів Марті Олчіна - це база, яку я використовую для власних потреб. Я дуже рекомендую поглянути на це, я думаю, що це дійсно вдалий початок, якщо ви хочете чогось простого і легко зламаного. Ви можете знайти його також як фрагменти Джанго .


Я намагаюся зробити щось подібне з підуком як основою.
edomaur

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

3
@ZoranPavlovic: зовсім не деякі рядки стандартного Python, вам не доведеться використовувати Django.
edomaur

11

Я - пенсіонер-біолог, який мав справу з цифровими мікрографами і виявив, що мені потрібно написати пакет обробки та аналізу зображень (технічно не бібліотеку) для роботи на машині SGi. Я написав код на C і використав Tcl для мови сценаріїв. Графічний інтерфейс, такий, як він був, робився за допомогою Tk. Команди, що з’явились у Tcl, мали форму "extensionName commandName arg0 arg1 ... param0 param1 ...", тобто прості слова та цифри, розділені пробілом. Коли Tcl побачив підрядку "extensionName", контроль було передано пакету C. Це, в свою чергу, пропустило команду через lexer / parser (виконано в lex / yacc), а потім викликало C підпрограми за потребою.

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

1) Світ звернувся до ПК і 2) сценарії отримали більше 500 рядків, коли незрозумілі організаційні можливості Tcl стали справжньою незручністю. Час минув ...

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

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

Кілька додаткових крапок, парен і коми, але це не стопори.

Я пам'ятаю, як хтось робив версії lex і yacc в Python (спробуйте: http://www.dabeaz.com/ply/ ), тож якщо вони все-таки потрібні, вони є навколо.

Сенс цієї суперечки полягає в тому, що мені здалося, що сам Пітон є бажаним "легким" переднім кінцем, який можна використовувати вченим. Мені цікаво дізнатися, чому ви вважаєте, що це не так, і я маю на увазі це серйозно.


додано пізніше: додаток gedit передбачає додавання плагінів, і на їхньому веб-сайті є чітке пояснення простої процедури плагіну, яку я знайшов за кілька хвилин огляду. Спробуйте:

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

Я все одно хотів би краще зрозуміти ваше запитання. Незрозуміло, чи ви 1) хочете, щоб вчені могли використовувати ваше (Python) додаток досить просто різними способами, або 2) хочете дозволити вченим додати нові можливості до вашої програми. Вибір №1 - це ситуація, з якою ми стикалися із зображеннями, і це призвело до використання загальних сценаріїв, які ми модифікували відповідно до потреб моменту. Це вибір 2, який підводить вас до ідеї плагінів, чи це якийсь аспект вашої програми, який робить видачу команд їй нездійсненним?


2
Ремонт посилань гнилі: плагін Gedit зараз - wiki.gnome.org/Apps/Gedit/PythonPluginHowTo
ohhorob

1
Це прекрасний пост, адже він чітко і стисло показує, наскільки щасливі нам сучасні біологи. Для нього python - це модульна мова сценаріїв, яка використовується для надання деякої абстракції розробникам модулів, так що їм не потрібно розбирати основний код C. Однак сьогодні мало хто з біологів навчиться вивчати C, а не робити все в 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 . Щоб його використовувати, ви створюєте один або декілька каталогів і видаляєте спеціальний __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

Як ще один підхід до системи плагінів, ви можете перевірити проект Extend Me .

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

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

Бібліотека exte_me маніпулює процесом створення класу через метакласи, таким чином, у прикладі вище, при створенні нового примірника MyCoolClassми отримали екземпляр нового класу, який є підкласом обох MyCoolClassExtensionі MyCoolClassмає функціонал обох, завдяки багатократному успадкуванню Python

Для кращого контролю над створенням класів у цьому розділі визначено кілька метакласів:

  • ExtensibleType - дозволяє просте розширення за допомогою підкласифікації

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

Ця вкладка використовується в OpenERP Proxy Project і, здається, працює досить добре!

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

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

ні, клас Extensible має перекритий __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

Можна використовувати плагінліб .

Плагіни легко створити і їх можна завантажувати з інших пакетів, шляхів до файлів або пунктів входу.

Створіть батьківський клас плагінів, визначивши необхідні методи:

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, або подібні абстракції в 3-му пакеті (який би був 3-м сховищем коду)?
JAponte

1
Батьківський клас повинен перебувати в тій же базі коду, що і додаток, але, ймовірно, у власному модулі. Отже, для пакету, який називається foo, у вас може бути модуль, який називається, foo.parentsде ви визначаєте батьківські класи. Тоді ваші плагіни імпортуватимуться foo.parents. Це добре працює для більшості випадків використання. Оскільки «foo» також імпортується, щоб уникнути можливості циркулярного імпорту, багато проектів залишають корінь модуля порожнім і використовують __main__.pyфайл або точки входу для запуску програми.
Aviso

1

Я витратив багато часу, намагаючись знайти маленьку систему плагінів для Python, яка б відповідала моїм потребам. Але тоді я просто подумав, якщо вже є спадщина, яка природна і гнучка, чому б не використати її.

Єдина проблема використання спадщини для плагінів - це те, що ви не знаєте, які саме специфічні (найнижчі на дереві спадкування) класи плагінів.

Але це можна вирішити за допомогою метакласу, який відслідковує успадкування базового класу, і, можливо, може створити клас, який успадковує від більшості конкретних плагінів ("Корінь розширений" на малюнку нижче)

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

Тому я прийшов з рішенням, кодуючи такий метаклас:

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 base, створена з метакласом, і є дерево плагінів, які успадковують її, ви можете автоматично отримати клас, який успадковує від найбільш конкретних плагінів просто підкласифікацією:

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.

Ось документи .

Відмова : Я один із авторів ґрунтових робіт.


0

У нашому теперішньому продукті охорони здоров'я у нас є архітектура плагінів, реалізована з класом інтерфейсів. Наш технологічний стек - Django на версії Python для API та Nuxtjs на вершині nodejs для frontend.

У нас є додаток менеджера плагінів, написане для нашого продукту, яке в основному є пакетом 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.