Costruire un'architettura minimale di plugin in Python


190

Ho un'applicazione, scritta in Python, che viene utilizzata da un pubblico abbastanza tecnico (scienziati).

Sto cercando un buon modo per rendere l'applicazione estensibile dagli utenti, ovvero un'architettura di script / plugin.

Sto cercando qualcosa di estremamente leggero . La maggior parte degli script o plug-in non sarà sviluppata e distribuita da terzi e installata, ma sarà qualcosa che verrà montata da un utente in pochi minuti per automatizzare un'attività ripetuta, aggiungere il supporto per un formato di file, ecc. Quindi i plugin dovrebbero avere il minimo assoluto di codice boilerplate e non richiedono alcuna 'installazione' oltre alla copia in una cartella (quindi qualcosa come punti di ingresso setuptools o l'architettura del plugin Zope sembra troppo.)

Esistono già sistemi come questo o progetti che implementano uno schema simile a cui dovrei cercare idee / ispirazione?

Risposte:


150

La mia è, fondamentalmente, una directory chiamata "plug-in" su cui l'app principale può eseguire il polling e quindi utilizzare imp.load_module per raccogliere i file, cercare un punto di ingresso noto possibilmente con parametri di configurazione a livello di modulo e andare da lì. Uso roba di monitoraggio dei file per un certo dinamismo in cui i plugin sono attivi, ma è un piacere avere.

Ovviamente, qualsiasi requisito che si presenta dicendo "Non ho bisogno di [cosa grande e complicata] X; voglio solo qualcosa di leggero" corre il rischio di reimplementare X un requisito scoperto alla volta. Ma questo non vuol dire che non puoi divertirti a farlo comunque :)


26
Molte grazie! Ho scritto un piccolo tutorial basato sul tuo post: lkubuntu.wordpress.com/2012/10/02/writing-a-python-plugin-api
MiJyn,

9
Il impmodulo è deprecato a importlibpartire da python 3.4
b0fh

1
In molti casi d'uso è possibile utilizzare importlib.import_module in sostituzione di imp.load_module.
Chris Arndt,

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)

È certamente "minimale", non ha assolutamente alcun controllo degli errori, probabilmente innumerevoli problemi di sicurezza, non è molto flessibile - ma dovrebbe mostrarti quanto può essere semplice un sistema di plugin in Python.

Probabilmente si desidera esaminare la imp modulo di troppo, anche se si può fare molto con poco __import__, os.listdire qualche manipolazione di stringhe.


4
Penso che potresti voler passare def call_plugin(name, *args)a def call_plugin(name, *args, **kwargs), e poi plugin.plugin_main(*args)aplugin.plugin_main(*args, **kwargs)
Ron Klein,

12
In Python 3, impè deprecato a favore diimportlib
Adam Baxter il


25

Sebbene questa domanda sia davvero interessante, penso che sia abbastanza difficile rispondere, senza ulteriori dettagli. Che tipo di applicazione è questa? Ha una GUI? È uno strumento da riga di comando? Una serie di script? Un programma con un punto di accesso unico, ecc ...

Date le poche informazioni che ho, risponderò in modo molto generico.

Cosa significa che devi aggiungere plugin?

  • Probabilmente dovrai aggiungere un file di configurazione, che elencherà i percorsi / directory da caricare.
  • Un altro modo sarebbe quello di dire "verranno caricati tutti i file in quel plugin / directory", ma ha l'inconveniente richiedere agli utenti di spostarsi tra i file.
  • Un'ultima opzione intermedia sarebbe quella di richiedere che tutti i plugin si trovino nella stessa cartella / plugin e quindi di attivarli / disattivarli usando i percorsi relativi in ​​un file di configurazione.

In base a una pratica di codice / progettazione pura, dovrai determinare chiaramente quale comportamento / azioni specifiche desideri che i tuoi utenti estendano. Identificare il punto di ingresso comune / un insieme di funzionalità che verranno sempre ignorate e determinare i gruppi all'interno di queste azioni. Fatto ciò, dovrebbe essere facile estendere l'applicazione,

Esempio di hook , ispirati a MediaWiki (PHP, ma la lingua conta davvero?):

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)

Un altro esempio, ispirato al mercuriale. Qui, le estensioni aggiungono solo comandi all'eseguibile della riga di comando hg , estendendo il comportamento.

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 }

Per entrambi gli approcci, potrebbe essere necessario inizializzare e finalizzare comuni per l'estensione. Puoi utilizzare un'interfaccia comune che tutta la tua estensione dovrà implementare (si adatta meglio al secondo approccio; mercurial utilizza un reposetup (ui, repo) chiamato per tutte le estensioni) o utilizzare un tipo di approccio hook, con un hooks.setup hook.

Ma ancora una volta, se vuoi risposte più utili, dovrai restringere la tua domanda;)


11

Il semplice framework di plugin di Marty Allchin è la base che utilizzo per le mie esigenze. Consiglio vivamente di dare un'occhiata, penso che sia davvero un buon inizio se vuoi qualcosa di semplice e facilmente hackerabile. Puoi trovarlo anche come frammenti di Django .


Sto cercando di fare qualcosa del genere con Pyduck come base.
Edomaur,

È molto specifico di Django da quello che posso dire.
Zoran Pavlovic,

3
@ZoranPavlovic: niente affatto, alcune linee di Python standard, non devi usare Django.
Edomaur,

11

Sono un biologo in pensione che si è occupato di micrografi digitali e si è trovato a dover scrivere un pacchetto di elaborazione e analisi delle immagini (non tecnicamente una biblioteca) per funzionare su una macchina SGi. Ho scritto il codice in C e ho usato Tcl per il linguaggio di scripting. La GUI, come era, è stata fatta usando Tk. I comandi che apparivano in Tcl erano nella forma "extensionName commandName arg0 arg1 ... param0 param1 ...", cioè semplici parole e numeri separati da spazi. Quando Tcl ha visto la sottostringa "extensionName", il controllo è stato passato al pacchetto C. Che a sua volta ha eseguito il comando attraverso un lexer / parser (fatto in lex / yacc) e poi ha chiamato le routine C come necessario.

I comandi per far funzionare il pacchetto potevano essere eseguiti uno ad uno tramite una finestra nella GUI, ma i lavori batch venivano eseguiti modificando file di testo che erano script Tcl validi; sceglieresti il ​​modello che ha fatto il tipo di operazione a livello di file che volevi fare e poi modificheresti una copia per contenere la directory effettiva e i nomi dei file più i comandi del pacchetto. Ha funzionato come un fascino. Fino a ...

1) Il mondo si è rivolto ai PC e 2) gli script sono diventati più lunghi di circa 500 righe, quando le incerte capacità organizzative di Tcl hanno iniziato a diventare un vero inconveniente. Il tempo passò ...

Mi sono ritirato, Python è stato inventato e sembrava il perfetto successore di Tcl. Ora, non ho mai fatto il port, perché non ho mai affrontato le sfide della compilazione di programmi C (piuttosto grandi) su un PC, l'estensione di Python con un pacchetto C e l'esecuzione di GUI in Python / Gt? / Tk? /? ?. Tuttavia, la vecchia idea di avere script modello modificabili sembra ancora praticabile. Inoltre, non dovrebbe essere un onere eccessivo inserire i comandi del pacchetto in una forma nativa di Python, ad esempio:

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

Alcuni punti extra, parentesi e virgole, ma quelli non sono showtopper.

Ricordo di aver visto che qualcuno ha realizzato versioni di lex e yacc in Python (prova: http://www.dabeaz.com/ply/ ), quindi se sono ancora necessarie, sono in giro.

Il punto di questa confusione è che mi è sembrato che lo stesso Python sia il front-end "leggero" desiderato utilizzabile dagli scienziati. Sono curioso di sapere perché pensi che non lo sia, e lo dico sul serio.


aggiunto in seguito: l'applicazione gedit prevede l'aggiunta di plug-in e il loro sito contiene la spiegazione più chiara di una semplice procedura di plug-in che ho trovato in pochi minuti guardando. Provare:

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

Mi piacerebbe ancora capire meglio la tua domanda. Non sono chiaro se 1) desideri che gli scienziati siano in grado di utilizzare la tua applicazione (Python) semplicemente in vari modi o 2) consentire agli scienziati di aggiungere nuove funzionalità alla tua applicazione. La scelta n. 1 è la situazione che abbiamo dovuto affrontare con le immagini e che ci ha portato a utilizzare script generici che abbiamo modificato per soddisfare le necessità del momento. È la scelta n. 2 che ti porta all'idea dei plugin o è un aspetto della tua applicazione che rende impraticabile l'invio di comandi?


2
Link rot repair: il plugin Gedit è ora - wiki.gnome.org/Apps/Gedit/PythonPluginHowTo
ohhorob,

1
Questo è un bel post, perché mostra chiaramente e in modo conciso quanto siamo fortunati noi biologi moderni. Per lui / lei, python è il linguaggio di scripting modulare usato per dare qualche astrazione agli sviluppatori di moduli in modo che non debbano analizzare il codice C principale. Al giorno d'oggi, tuttavia, pochi biologi impareranno mai la C, facendo invece tutto in Python. Come riusciamo a sottrarre le complessità ai nostri principali programmi Python durante la scrittura di moduli? Tra 10 anni, forse i programmi saranno scritti in Emoji e i moduli saranno solo file audio contenenti una serie di grugniti. E forse va bene.
JJ,

10

Durante la ricerca di Decoratori Python, ho trovato uno snippet di codice semplice ma utile. Potrebbe non adattarsi alle tue esigenze ma molto stimolante.

Scipy Advanced Python # Plugin Registration System

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('**', '')

Uso:

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

1
Nota: in questo esempio, WordProcessor.pluginnon restituisce nulla ( None), quindi l'importazione della CleanMdashesExtensionclasse in seguito importa solo None. Se le classi del plugin sono utili da sole, crea il .pluginmetodo class return plugin.
jkmacc,

@jkmacc Hai ragione. Ho modificato lo snippet 13 giorni dopo il tuo commento. Grazie.
guneiso

7

Mi è piaciuta la bella discussione su diverse architetture di plugin fornita dal dott. Andre Roberge al Pycon 2009. Offre una buona panoramica dei diversi modi di implementare i plugin, partendo da qualcosa di veramente semplice.

È disponibile come podcast (seconda parte dopo una spiegazione del patching delle scimmie) accompagnato da una serie di sei post di blog .

Ti consiglio di ascoltarlo rapidamente prima di prendere una decisione.


4

Sono arrivato qui alla ricerca di un'architettura minimale di plugin e ho trovato molte cose che mi sono sembrate eccessive. Quindi, ho implementato plugin Super Simple Python . Per usarlo, si creano una o più directory e si rilascia un __init__.pyfile speciale in ciascuna. L'importazione di tali directory causerà il caricamento di tutti gli altri file Python come sottomoduli e il loro nome verrà inserito __all__nell'elenco. Quindi sta a te convalidare / inizializzare / registrare quei moduli. C'è un esempio nel file README.


4

In realtà setuptools funziona con una "directory di plugin", come il seguente esempio tratto dalla documentazione del progetto: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Esempio di utilizzo:

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)

A lungo termine, setuptools è una scelta molto più sicura poiché può caricare plugin senza conflitti o requisiti mancanti.

Un altro vantaggio è che i plugin stessi possono essere estesi usando lo stesso meccanismo, senza che le applicazioni originali debbano preoccuparsene.


3

Come un altro approccio al sistema di plugin, è possibile controllare il progetto Extend Me .

Ad esempio, definiamo la classe semplice e la sua estensione

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

E prova ad usarlo:

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

E mostra ciò che è nascosto dietro la scena:

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

La libreria extension_me manipola il processo di creazione della classe tramite metaclass, quindi nell'esempio sopra, quando si crea una nuova istanza di MyCoolClassabbiamo un'istanza di nuova classe che è una sottoclasse di entrambi MyCoolClassExtensione MyCoolClasscon funzionalità di entrambi, grazie all'eredità multipla di Python

Per un migliore controllo sulla creazione di classi ci sono alcune metaclasse definite in questa libreria:

  • ExtensibleType - consente una semplice estensibilità tramite la sottoclasse

  • ExtensibleByHashType - simile a ExtensibleType, ma con la possibilità di creare versioni specializzate della classe, consentendo l'estensione globale della classe base e l'estensione delle versioni specializzate della classe

Questa libreria è utilizzata nel progetto proxy OpenERP e sembra funzionare abbastanza bene!

Per un vero esempio di utilizzo, guarda l'estensione 'field_datetime' del proxy OpenERP :

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

Recordqui è un oggetto estensibile. RecordDateTimeè estensione.

Per abilitare l'estensione, basta importare il modulo che contiene la classe di estensione e (nel caso sopra) tutti gli Recordoggetti creati dopo avranno classe di estensione nelle classi di base, avendo così tutte le sue funzionalità.

Il vantaggio principale di questa libreria è che, il codice che gestisce oggetti estensibili, non ha bisogno di conoscere l'estensione e le estensioni potrebbero cambiare tutto in oggetti estensibili.


Penso che tu intenda istanziare dalla sottoclasse, cioè my_cool_obj = MyCoolClassExtension1()invece dimy_cool_obj = MyCoolClass()
pylang

no, la classe Extensible ha il __new__metodo override , quindi trova automaticamente tutte le sottoclassi e costruisce una nuova classe, che è una sottoclasse di tutte, e ritorna alla nuova istanza di questa classe creata. Pertanto, l'applicazione originale non deve conoscere tutte le estensioni. questo approccio è utile quando si crea una libreria, per consentire all'utente finale di modificare o estendere facilmente il suo comportamento. nell'esempio precedente, MyCoolClass può essere definito in libreria e utilizzato da esso e MyCoolClassExtension può essere definito dall'utente finale.
FireMage,

Un altro esempio è stato aggiunto per rispondere
FireMage

3

setuptools ha un EntryPoint :

I punti di ingresso sono un modo semplice per le distribuzioni di "pubblicizzare" oggetti Python (come funzioni o classi) per l'uso da parte di altre distribuzioni. Applicazioni e framework estensibili possono cercare punti di ingresso con un nome o un gruppo particolare, da una distribuzione specifica o da tutte le distribuzioni attive su sys.path, quindi ispezionare o caricare gli oggetti pubblicizzati a piacimento.

AFAIK questo pacchetto è sempre disponibile se usi pip o virtualenv.


2

Espandendo la risposta di @ edomaur, posso suggerire di dare un'occhiata a simple_plugins (plug shameless), che è un semplice framework di plug-in ispirato al lavoro di Marty Alchin .

Un breve esempio di utilizzo basato sul README del progetto:

# 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

Puoi usare pluginlib .

I plug-in sono facili da creare e possono essere caricati da altri pacchetti, percorsi di file o punti di ingresso.

Crea una classe padre del plugin, definendo tutti i metodi richiesti:

import pluginlib

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

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

Crea un plug-in ereditando una classe genitore:

import json

class JSON(Parser):
    _alias_ = 'json'

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

Carica i plugin:

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

1
Grazie per l'esempio Ho avuto problemi con 1 domanda. Dato che hai menzionato la possibilità di caricare i plugin da diversi pacchetti, forse ci hai già pensato. Mi chiedo dove dovrebbe risiedere la classe genitore. Di solito si consiglia di averlo nel pacchetto dell'applicazione (presumibilmente un repository di codice sorgente separato), ma come erediteremmo da esso nella base di codice del plugin? Importiamo l'intera applicazione per questo? O è necessario avere il codice dell'interfaccia come la classe Parser o astrazioni simili in un terzo pacchetto (che sarebbe un repository di terzo codice)?
JAponte,

1
La classe genitore dovrebbe risiedere nella stessa base di codice dell'applicazione, ma probabilmente nel proprio modulo. Quindi, per un pacchetto chiamato foo, potresti avere un modulo chiamato in foo.parentscui definisci le classi principali. Quindi i tuoi plugin verrebbero importati foo.parents. Funziona bene per la maggior parte dei casi d'uso. Poiché anche lo stesso "pippo" viene importato, per evitare la possibilità di importazioni circolari, molti progetti lasciano vuota la radice del modulo e utilizzano un __main__.pyfile o punti di ingresso per avviare l'applicazione.
aviso,

1

Ho trascorso molto tempo a cercare un piccolo sistema di plugin per Python, che si adattasse alle mie esigenze. Ma poi ho pensato, se esiste già un'eredità, che è naturale e flessibile, perché non usarla.

L'unico problema con l'utilizzo dell'ereditarietà per i plugin è che non sai quali sono le classi di plugin più specifiche (la più bassa sull'ereditarietà).

Ma questo potrebbe essere risolto con la metaclasse, che tiene traccia dell'ereditarietà della classe base, e possibilmente potrebbe costruire la classe, che eredita dalla maggior parte dei plugin specifici ("Root extended" nella figura seguente)

inserisci qui la descrizione dell'immagine

Quindi sono arrivato con una soluzione codificando una tale metaclasse:

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

Quindi, quando si dispone di una base radice, realizzata con metaclasse e si ha un albero di plug-in che eredita da esso, è possibile ottenere automaticamente la classe, che eredita dai plug-in più specifici semplicemente eseguendo la sottoclasse:

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

La base di codice è piuttosto piccola (~ 30 righe di codice puro) e flessibile quanto l'ereditarietà consente.

Se sei interessato, fatti coinvolgere @ https://github.com/thodnev/pluginlib


1

Puoi anche dare un'occhiata a Groundwork .

L'idea è quella di creare applicazioni attorno a componenti riutilizzabili, chiamati pattern e plugin. I plugin sono classi da cui derivano GwBasePattern. Ecco un esempio di base:

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

Esistono anche modelli più avanzati per gestire, ad esempio, interfacce della riga di comando, segnalazioni o oggetti condivisi.

Groundwork trova i suoi plugin legandoli programmaticamente a un'app come mostrato sopra o automaticamente tramite setuptools. I pacchetti Python contenenti plug-in devono dichiararli utilizzando un punto di ingresso speciale groundwork.plugin.

Ecco i documenti .

Disclaimer : sono uno degli autori di Groundwork.


0

Nel nostro attuale prodotto sanitario abbiamo un'architettura plug-in implementata con classe di interfaccia. Il nostro stack tecnologico è Django in cima a Python per API e Nuxtjs in cima a nodejs per frontend.

Abbiamo un'app di gestione plugin scritta per il nostro prodotto che è fondamentalmente pacchetto pip e npm in aderenza a Django e Nuxtjs.

Per lo sviluppo di nuovi plugin (pip e npm) abbiamo creato il gestore plugin come dipendenza.

Nel pacchetto Pip: con l'aiuto di setup.py puoi aggiungere entrypoint del plugin per fare qualcosa con gestore plugin (registro, iniziazioni, ecc.) Https://setuptools.readthedocs.io/en/latest/setuptools .html # automatica-script-creazione

Nel pacchetto npm: simile al pip, ci sono hook negli script npm per gestire l'installazione. https://docs.npmjs.com/misc/scripts

Il nostro caso d'uso:

il team di sviluppo dei plug-in è separato dal team di sviluppo principale. Lo scopo dello sviluppo di plug-in è l'integrazione con app di terze parti definite in una qualsiasi delle categorie del prodotto. Le interfacce dei plug-in sono classificate per esempio: - Fax, telefono, e-mail ... ecc. Il gestore plug-in può essere migliorato in nuove categorie.

Nel tuo caso: forse puoi avere un plugin scritto e riutilizzare lo stesso per fare cose.

Se gli sviluppatori di plug-in devono utilizzare gli oggetti core di riutilizzo, quell'oggetto può essere utilizzato facendo un livello di astrazione all'interno del gestore plug-in in modo che qualsiasi plug-in possa ereditare tali metodi.

Condividere semplicemente il modo in cui abbiamo implementato il nostro prodotto spera che possa dare una piccola idea.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.