Construyendo una arquitectura de plugin mínima en Python

190

Tengo una aplicación, escrita en Python, que es utilizada por un público bastante técnico (científicos).

Estoy buscando una buena manera de hacer que la aplicación sea extensible por los usuarios, es decir, una arquitectura de scripting / plugin.

Estoy buscando algo extremadamente ligero . La mayoría de los scripts, o complementos, no serán desarrollados y distribuidos por un tercero e instalados, sino que serán creados por un usuario en unos minutos para automatizar una tarea repetitiva, agregar soporte para un formato de archivo, Por lo tanto, los complementos deben tener el código mínimo absoluto y no requieren 'instalación' aparte de copiarlos en una carpeta (por lo que algo como los puntos de entrada de las herramientas de configuración o la arquitectura del complemento de Zope parece demasiado).

¿Hay algún sistema como este ya disponible, o algún proyecto que implemente un esquema similar al que debería buscar ideas / inspiración?

dF.
fuente

Respuestas:

150

El mío es, básicamente, un directorio llamado "plugins" que la aplicación principal puede sondear y luego usar imp.load_module para recoger archivos, buscar un punto de entrada conocido posiblemente con parámetros de configuración a nivel de módulo, y partir de allí. Utilizo cosas de monitoreo de archivos para una cierta cantidad de dinamismo en el que los complementos están activos, pero eso es bueno de tener.

Por supuesto, cualquier requisito que aparezca diciendo "No necesito [cosa grande y complicada] X; solo quiero algo liviano" corre el riesgo de volver a implementar X un requisito descubierto a la vez. Pero eso no quiere decir que no puedas divertirte haciéndolo de todos modos :)

TJG
fuente
26
¡Muchas gracias! Escribí un pequeño tutorial basado en tu publicación: lkubuntu.wordpress.com/2012/10/02/writing-a-python-plugin-api
MiJyn
9
El impmódulo está en desuso a favor de importlibcomenzar desde Python 3.4
b0fh
1
En muchos casos de uso, puede usar importlib.import_module como reemplazo de 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)

Ciertamente es "mínimo", no tiene absolutamente ninguna comprobación de errores, probablemente innumerables problemas de seguridad, no es muy flexible, pero debería mostrarle cuán simple puede ser un sistema de complementos en Python ...

Es probable que desee ver en la imp módulo también, aunque se puede hacer mucho con solo __import__, os.listdiry algunos de manipulación de cadenas.

dbr
fuente
44
Creo que es posible que desee cambiar def call_plugin(name, *args)a def call_plugin(name, *args, **kwargs), y luego plugin.plugin_main(*args)aplugin.plugin_main(*args, **kwargs)
Ron Klein
12
En python 3, impestá en desuso a favor deimportlib
Adam Baxter
25

Si bien esa pregunta es realmente interesante, creo que es bastante difícil de responder, sin más detalles. ¿Qué tipo de aplicación es esta? ¿Tiene una GUI? ¿Es una herramienta de línea de comandos? ¿Un conjunto de guiones? Un programa con un punto de entrada único, etc.

Dada la poca información que tengo, responderé de manera muy genérica.

¿Qué medios tienes para agregar complementos?

  • Probablemente tendrá que agregar un archivo de configuración, que enumerará las rutas / directorios para cargar.
  • Otra forma sería decir "cualquier archivo en ese plugin / directorio se cargará", pero tiene el inconveniente de requerir que sus usuarios muevan los archivos.
  • Una última opción intermedia sería requerir que todos los complementos estén en el mismo complemento / carpeta, y luego activarlos / desactivarlos utilizando rutas relativas en un archivo de configuración.

En una práctica de código / diseño puro, tendrá que determinar claramente qué comportamiento / acciones específicas desea que amplíen sus usuarios. Identifique el punto de entrada común / un conjunto de funcionalidades que siempre se anularán y determine los grupos dentro de estas acciones. Una vez hecho esto, debería ser fácil extender su aplicación,

Ejemplo usando ganchos , inspirados en MediaWiki (PHP, pero ¿el lenguaje realmente importa?):

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)

Otro ejemplo, inspirado en mercurial. Aquí, las extensiones solo agregan comandos al ejecutable de la línea de comandos hg , extendiendo el comportamiento.

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 }

Para ambos enfoques, es posible que necesite una inicialización y finalización comunes para su extensión. Puede usar una interfaz común que toda su extensión tendrá que implementar (se adapta mejor con un segundo enfoque; mercurial usa un reposetup (ui, repo) que se llama para todas las extensiones), o usar un enfoque de tipo gancho, con un ganchos.conjunto de gancho.

Pero, de nuevo, si desea respuestas más útiles, tendrá que limitar su pregunta;)

Nicolas Dumazet
fuente
11

El marco de plugins simple de Marty Allchin es la base que uso para mis propias necesidades. Realmente recomiendo echarle un vistazo, creo que es realmente un buen comienzo si quieres algo simple y fácilmente pirateable. Puede encontrarlo también como fragmentos de Django .

edomaur
fuente
Estoy tratando de hacer algo así con pyduck como base.
edomaur
Es muy específico de Django por lo que puedo decir.
Zoran Pavlovic
3
@ZoranPavlovic: en absoluto, algunas líneas de Python estándar, no tienes que usar Django.
edomaur
11

Soy un biólogo retirado que se ocupó de micrograqphs digitales y se encontró con que tenía que escribir un paquete de procesamiento y análisis de imágenes (no técnicamente una biblioteca) para ejecutarlo en una máquina SGi. Escribí el código en C y usé Tcl para el lenguaje de script. La GUI, tal como estaba, se realizó utilizando Tk. Los comandos que aparecían en Tcl eran de la forma "extensionName commandName arg0 arg1 ... param0 param1 ...", es decir, palabras y números simples separados por espacios. Cuando Tcl vio la subcadena "extensionName", el control se pasó al paquete C. Eso a su vez ejecutó el comando a través de un lexer / parser (hecho en lex / yacc) y luego llamó a las rutinas C según sea necesario.

Los comandos para operar el paquete se podían ejecutar uno por uno a través de una ventana en la GUI, pero los trabajos por lotes se realizaban editando archivos de texto que eran scripts Tcl válidos; elegiría la plantilla que realizaba el tipo de operación a nivel de archivo que deseaba hacer y luego editaría una copia para contener el directorio real y los nombres de archivo más los comandos del paquete. Funcionó a las mil maravillas. Hasta ...

1) El mundo recurrió a las PC y 2) las secuencias de comandos se hicieron más largas de aproximadamente 500 líneas, cuando las capacidades organizativas dudosas de Tcl comenzaron a convertirse en un verdadero inconveniente. El tiempo pasó ...

Me retiré, Python se inventó y parecía el sucesor perfecto de Tcl. Ahora, nunca he hecho el puerto, porque nunca he enfrentado los desafíos de compilar (bastante grandes) programas C en una PC, extender Python con un paquete C y hacer GUI en Python / Gt? / Tk? /? ?. Sin embargo, la vieja idea de tener scripts de plantillas editables todavía parece viable. Además, no debería ser una carga demasiado pesada ingresar los comandos del paquete en una forma nativa de Python, por ejemplo:

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

Algunos puntos extra, parens y comas, pero esos no son showstoppers.

Recuerdo haber visto que alguien ha hecho versiones de lex y yacc en Python (prueba: http://www.dabeaz.com/ply/ ), por lo que si todavía son necesarias, están disponibles.

El punto de esta divagación es que me ha parecido que Python ES EL front-end "ligero" deseado por los científicos. Tengo curiosidad por saber por qué piensas que no es así, y lo digo en serio.


agregado más tarde: la aplicación gedit anticipa que se agregarán complementos y su sitio tiene la explicación más clara de un procedimiento de complemento simple que he encontrado en unos minutos de mirar alrededor. Tratar:

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

Todavía me gustaría entender mejor tu pregunta. No tengo claro si usted 1) quiere que los científicos puedan usar su aplicación (Python) de manera bastante simple de varias maneras o 2) quiere permitir que los científicos agreguen nuevas capacidades a su aplicación. La opción n. ° 1 es la situación que enfrentamos con las imágenes y que nos llevó a utilizar secuencias de comandos genéricas que modificamos para adaptarlas a las necesidades del momento. ¿Es la Opción # 2 la que lo lleva a la idea de los complementos, o es algún aspecto de su aplicación que hace que la emisión de comandos sea impracticable?

detrás de la caída
fuente
2
Reparación de rotura de
enlaces
1
Esta es una publicación hermosa, porque muestra clara y concisa cuán afortunados somos los biólogos modernos. Para él / ella, python es el lenguaje de secuencias de comandos modular que se utiliza para dar una cierta abstracción a los desarrolladores de módulos para que no necesiten analizar el código C principal. Sin embargo, hoy en día, pocos biólogos alguna vez aprenderán C, en cambio harán todo en Python. ¿Cómo abstraemos las complejidades de nuestros principales programas de Python al escribir módulos? En 10 años a partir de ahora, quizás los programas se escribirán en Emoji y los módulos serán solo archivos de audio que contienen una serie de gruñidos. Y tal vez eso está bien.
JJ
10

Cuando busqué decoradores de Python, encontré un fragmento de código simple pero útil. Puede que no se ajuste a tus necesidades pero es muy inspirador.

Sistema de registro de plugin 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('**', '')

Uso:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")
guneysus
fuente
1
Nota: En este ejemplo, WordProcessor.pluginno devuelve nada ( None), por lo que importar la CleanMdashesExtensionclase más tarde solo importa None. Si las clases de complementos son útiles por sí mismas, cree el .pluginmétodo de clase return plugin.
jkmacc
@jkmacc Tienes razón. Modifiqué el fragmento 13 días después de tu comentario. Gracias.
guneysus
7

Disfruté de la agradable discusión sobre diferentes arquitecturas de complementos dada por el Dr. Andre Roberge en Pycon 2009. Da una buena visión general de las diferentes formas de implementar complementos, comenzando por algo realmente simple.

Está disponible como un podcast (segunda parte después de una explicación de parches de mono) acompañado de una serie de seis entradas de blog .

Recomiendo escucharlo rápidamente antes de tomar una decisión.

Jon Mills
fuente
4

Llegué aquí buscando una arquitectura mínima de complementos, y encontré muchas cosas que me parecieron excesivas. Por lo tanto, he implementado complementos de Python súper simples . Para usarlo, crea uno o más directorios y suelta un __init__.pyarchivo especial en cada uno. La importación de esos directorios hará que todos los demás archivos de Python se carguen como submódulos, y sus nombres se colocarán en la __all__lista. Entonces depende de usted validar / inicializar / registrar esos módulos. Hay un ejemplo en el archivo README.

samwyse
fuente
4

En realidad, setuptools funciona con un "directorio de complementos", como el siguiente ejemplo tomado de la documentación del proyecto: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Ejemplo de uso:

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 la larga, setuptools es una opción mucho más segura ya que puede cargar complementos sin conflictos ni requisitos faltantes.

Otro beneficio es que los complementos pueden ampliarse utilizando el mismo mecanismo, sin que las aplicaciones originales tengan que preocuparse por ello.

ankostis
fuente
3

Como otro enfoque para el sistema de complementos, puede marcar el proyecto Extenderme .

Por ejemplo, definamos una clase simple y su extensión

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

Y trata de 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

Y muestra lo que se esconde detrás de la escena:

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

La biblioteca extend_me manipula el proceso de creación de clases a través de metaclases, por lo tanto, en el ejemplo anterior, cuando creamos una nueva instancia de MyCoolClasstenemos una nueva clase que es subclase de ambos MyCoolClassExtensiony que MyCoolClasstiene funcionalidad de ambos, gracias a la herencia múltiple de Python

Para un mejor control sobre la creación de clases, hay pocas metaclases definidas en esta biblioteca:

  • ExtensibleType - permite una extensibilidad simple mediante subclases

  • ExtensibleByHashType - similar a ExtensibleType, pero con capacidad para construir versiones especializadas de clase, permitiendo la extensión global de la clase base y la extensión de versiones especializadas de clase

¡Esta lib se usa en OpenERP Proxy Project y parece estar funcionando lo suficientemente bien!

Para un ejemplo real de uso, busque en la extensión '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

RecordAquí hay un objeto extensible. RecordDateTimeEs extensión.

Para habilitar la extensión, solo importe el módulo que contiene la clase de extensión y (en el caso anterior) todos los Recordobjetos creados después de que tenga la clase de extensión en las clases base, teniendo así toda su funcionalidad.

La principal ventaja de esta biblioteca es que, el código que opera objetos extensibles, no necesita saber acerca de la extensión y las extensiones podrían cambiar todo en objetos extensibles.

Mago de fuego
fuente
Creo que te refieres a crear una instancia de la subclase, es decir, en my_cool_obj = MyCoolClassExtension1()lugar demy_cool_obj = MyCoolClass()
pylang
no, la clase extensible tiene un __new__método anulado , por lo que busca automáticamente todas las subclases y crea una nueva clase, es decir, una subclase de todas ellas, y devuelve una nueva instancia de esta clase creada. Por lo tanto, la aplicación original no necesita conocer todas las extensiones. Este enfoque es útil cuando se construye una biblioteca, para permitir que el usuario final modifique o extienda su comportamiento fácilmente. en el ejemplo anterior, MyCoolClass se puede definir en la biblioteca y utilizar, y MyCoolClassExtension puede ser definido por el usuario final.
FireMage
Se agregó un ejemplo más para responder
FireMage
3

setuptools tiene un EntryPoint :

Los puntos de entrada son una forma sencilla para que las distribuciones "anuncien" objetos de Python (como funciones o clases) para que otras distribuciones las utilicen. Las aplicaciones y los marcos extensibles pueden buscar puntos de entrada con un nombre o grupo particular, ya sea desde una distribución específica o desde todas las distribuciones activas en sys.path, y luego inspeccionar o cargar los objetos anunciados a voluntad.

AFAIK este paquete siempre está disponible si usa pip o virtualenv.

guettli
fuente
2

Ampliando la respuesta de @ edomaur, puedo sugerir que eche un vistazo a simple_plugins (conector descarado), que es un marco de complemento simple inspirado en el trabajo de Marty Alchin .

Un breve ejemplo de uso basado en el archivo README del proyecto:

# 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>
Petar Marić
fuente
2

He pasado tiempo leyendo este hilo mientras buscaba un marco de plugin en Python de vez en cuando. He usado algunos pero había defectos con ellos. Esto es lo que se me ocurrió para su escrutinio en 2017, un sistema de administración de complementos sin interfaz y libremente acoplado: cárgueme más tarde . Aquí hay tutoriales sobre cómo usarlo.

chfw
fuente
2

Puedes usar pluginlib .

Los complementos son fáciles de crear y se pueden cargar desde otros paquetes, rutas de archivos o puntos de entrada.

Cree una clase principal de complemento, definiendo los métodos necesarios:

import pluginlib

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

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

Cree un complemento heredando una clase padre:

import json

class JSON(Parser):
    _alias_ = 'json'

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

Cargue los complementos:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))
aviso
fuente
1
Gracias por el ejemplo He estado luchando con 1 pregunta. Como mencionó la posibilidad de cargar los complementos desde diferentes paquetes, tal vez ya lo pensó. Me pregunto dónde debería residir la clase principal. Por lo general, se recomienda tenerlo en el paquete de la aplicación (presumiblemente un repositorio de código fuente separado), pero entonces, ¿cómo heredaríamos de él en la base de código del complemento? ¿Importamos toda la aplicación para esto? ¿O es necesario tener el código de interfaz como la clase Parser o abstracciones similares en un tercer paquete (que sería un tercer repositorio de código)?
JAponte
1
La clase principal debe residir en la misma base de código que la aplicación, pero probablemente en su propio módulo. Entonces, para un paquete llamado foo, es posible que tenga un módulo llamado foo.parentsdonde defina las clases principales. Entonces sus complementos, importarían foo.parents. Eso funciona bien para la mayoría de los casos de uso. Debido a que 'foo' también se importa, para evitar la posibilidad de importaciones circulares, muchos proyectos dejan la raíz del módulo vacía y usan un __main__.pyarchivo o puntos de entrada para iniciar la aplicación.
aviso
1

He pasado mucho tiempo tratando de encontrar un pequeño sistema de complementos para Python, que se ajuste a mis necesidades. Pero luego pensé, si ya existe una herencia, que es natural y flexible, ¿por qué no usarla?

El único problema con el uso de la herencia para complementos es que no sabes cuáles son las clases de complementos más específicas (las más bajas en el árbol de herencia).

Pero esto podría resolverse con metaclase, que realiza un seguimiento de la herencia de la clase base, y posiblemente podría construir una clase, que hereda de los complementos más específicos ('Root extendido' en la figura a continuación)

ingrese la descripción de la imagen aquí

Así que llegué con una solución codificando una metaclase de este tipo:

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

Entonces, cuando tiene una base raíz, hecha con metaclase, y tiene un árbol de complementos que hereda de ella, puede obtener automáticamente la clase, que hereda de los complementos más específicos simplemente subclasificando:

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

La base de código es bastante pequeña (~ 30 líneas de código puro) y tan flexible como lo permite la herencia.

Si está interesado, participe @ https://github.com/thodnev/pluginlib

Thodnev
fuente
1

También puede echar un vistazo a Groundwork .

La idea es crear aplicaciones alrededor de componentes reutilizables, llamados patrones y complementos. Los complementos son clases que se derivan de GwBasePattern. Aquí hay un ejemplo básico:

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

También hay patrones más avanzados para manejar, por ejemplo, interfaces de línea de comandos, señalización u objetos compartidos.

Groundwork encuentra sus complementos al vincularlos mediante programación a una aplicación como se muestra arriba o automáticamente a través de setuptools. Los paquetes de Python que contienen complementos deben declararlos utilizando un punto de entrada especial groundwork.plugin.

Aquí están los documentos .

Descargo de responsabilidad : soy uno de los autores de Groundwork.

ub_marco
fuente
0

En nuestro producto sanitario actual, tenemos una arquitectura de complemento implementada con clase de interfaz. Nuestra pila tecnológica es Django sobre Python para API y Nuxtjs sobre nodejs para frontend.

Tenemos una aplicación de administrador de complementos escrita para nuestro producto que es básicamente un paquete pip y npm en cumplimiento con Django y Nuxtjs.

Para el desarrollo de nuevos complementos (pip y npm) creamos el administrador de complementos como dependencia.

En el paquete Pip: con la ayuda de setup.py puede agregar el punto de entrada del complemento para hacer algo con el administrador de complementos (registro, iniciaciones, etc.) https://setuptools.readthedocs.io/en/latest/setuptools .html # automatic-script-creation

En el paquete npm: Similar a pip, hay ganchos en los scripts npm para manejar la instalación. https://docs.npmjs.com/misc/scripts

Nuestro caso de uso:

El equipo de desarrollo de plugins ahora está separado del equipo central de desarrollo. El alcance del desarrollo de complementos es para integrarse con aplicaciones de terceros que se definen en cualquiera de las categorías del producto. Las interfaces del complemento se clasifican por ejemplo, por ejemplo: - El administrador de complementos de fax, teléfono, correo electrónico ... etc. se puede mejorar a nuevas categorías.

En su caso: tal vez pueda tener un plugin escrito y reutilizarlo para hacer cosas.

Si los desarrolladores de complementos tienen que usar reutilizar objetos centrales, ese objeto puede usarse haciendo un nivel de abstracción dentro del administrador de complementos para que cualquier complemento pueda heredar esos métodos.

Solo compartiendo cómo implementamos en nuestro producto espero que dé una pequeña idea.

Shankar Ganesh Jayaraman
fuente