Construindo uma arquitetura mínima de plugins em Python

190

Eu tenho um aplicativo, escrito em Python, que é usado por um público bastante técnico (cientistas).

Estou procurando uma boa maneira de tornar o aplicativo extensível pelos usuários, ou seja, uma arquitetura de script / plugin.

Estou procurando por algo extremamente leve . A maioria dos scripts ou plug-ins não será desenvolvida e distribuída por terceiros e instalada, mas será criada por um usuário em alguns minutos para automatizar uma tarefa repetida, adicionar suporte para um formato de arquivo, etc. Portanto, os plug-ins devem ter o código mínimo padrão absoluto e não requerem 'instalação' além de copiar para uma pasta (algo como setuptools pontos de entrada ou a arquitetura de plugins do Zope parece demais).

Existe algum sistema como este já existente, ou algum projeto que implemente um esquema semelhante ao qual eu deveria procurar idéias / inspiração?

dF.
fonte

Respostas:

150

O meu é, basicamente, um diretório chamado "plug-ins" que o aplicativo principal pode pesquisar e, em seguida, usar o imp.load_module para coletar arquivos, procurar um ponto de entrada conhecido possivelmente com parâmetros de configuração no nível do módulo e partir daí. Eu uso coisas de monitoramento de arquivos para uma certa quantidade de dinamismo em que os plug-ins estão ativos, mas é bom ter isso.

Obviamente, qualquer requisito que surgir dizendo "Não preciso de [coisa grande e complicada] X; só quero algo leve" corre o risco de reimplementar o X de um requisito descoberto de cada vez. Mas isso não quer dizer que você não pode se divertir fazendo isso de qualquer maneira :)

TJG
fonte
26
Muito obrigado! Eu escrevi um pequeno tutorial com base em seu post: lkubuntu.wordpress.com/2012/10/02/writing-a-python-plugin-api
MiJyn
9
O impmódulo está a ser preterida em favor importlibda partir de python 3.4
b0fh
1
Em muitos casos de uso, você pode usar importlib.import_module como um substituto para imp.load_module.
31919 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 é "mínimo", não tem absolutamente nenhuma verificação de erro, provavelmente inúmeros problemas de segurança, não é muito flexível - mas deve mostrar como um sistema de plug-in no Python pode ser simples.

Você provavelmente também deseja examinar o módulo imp , embora possa fazer muito com apenas __import__, os.listdire alguma manipulação de string.

dbr
fonte
4
Eu acho que você pode querer mudar def call_plugin(name, *args)para def call_plugin(name, *args, **kwargs), em seguida, plugin.plugin_main(*args)paraplugin.plugin_main(*args, **kwargs)
Ron Klein
12
No python 3, impé preterido em favor deimportlib
Adam Baxter
25

Embora essa pergunta seja realmente interessante, acho que é bastante difícil responder, sem mais detalhes. Que tipo de aplicação é essa? Possui uma GUI? É uma ferramenta de linha de comando? Um conjunto de scripts? Um programa com um ponto de entrada exclusivo, etc ...

Dada a pouca informação que tenho, responderei de uma maneira muito genérica.

Quais meios você tem para adicionar plugins?

  • Você provavelmente precisará adicionar um arquivo de configuração, que listará os caminhos / diretórios a serem carregados.
  • Outra maneira seria dizer "todos os arquivos nesse plug-in / diretório serão carregados", mas é inconveniente exigir que seus usuários movam os arquivos.
  • Uma última opção intermediária seria exigir que todos os plug-ins estivessem no mesmo plug-in / pasta e, em seguida, ativá-los / desativá-los usando caminhos relativos em um arquivo de configuração.

Em uma prática pura de código / design, você precisará determinar claramente que comportamento / ações específicas deseja que seus usuários estendam. Identifique o ponto de entrada comum / um conjunto de funcionalidades que sempre serão substituídas e determine grupos dentro dessas ações. Feito isso, será fácil estender seu aplicativo,

Exemplo de uso de hooks , inspirado no MediaWiki (PHP, mas a linguagem 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)

Outro exemplo, inspirado em mercurial. Aqui, as extensões adicionam apenas comandos ao executável da linha de comando hg , estendendo o 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 }

Para ambas as abordagens, pode ser necessário inicializar e finalizar comummente sua extensão. Você pode usar uma interface comum que toda a sua extensão precisará implementar (se encaixa melhor na segunda abordagem; o mercurial usa um reposetup (ui, repo) chamado para toda a extensão) ou usar uma abordagem do tipo gancho, com um hooks.setup hook.

Mas, novamente, se você quiser respostas mais úteis, terá que refinar sua pergunta;)

Nicolas Dumazet
fonte
11

A estrutura simples de plug-ins de Marty Allchin é a base que eu uso para minhas próprias necessidades. Eu realmente recomendo dar uma olhada, acho que é realmente um bom começo se você quiser algo simples e fácil de hackear. Você pode encontrá-lo também como um Django Snippets .

edomaur
fonte
Estou tentando fazer algo assim com pyduck como base.
11126 Edomaur
É muito específico do Django, pelo que posso dizer.
Zoran Pavlovic
3
@ZoranPavlovic: de maneira alguma, algumas linhas do Python padrão, você não precisa usar o Django.
edomaur
11

Eu sou um biólogo aposentado que lidou com micrografos digitais e teve que escrever um pacote de processamento e análise de imagens (não tecnicamente uma biblioteca) para rodar em uma máquina SGi. Eu escrevi o código em C e usei Tcl para a linguagem de script. A GUI, como era, foi feita usando Tk. Os comandos que apareceram no Tcl tinham o formato "extensionName commandName arg0 arg1 ... param0 param1 ...", ou seja, palavras e números simples separados por espaço. Quando Tcl viu a substring "extensionName", o controle foi passado para o pacote C. Por sua vez, ele executou o comando através de um lexer / analisador (feito em lex / yacc) e, em seguida, chamou rotinas C, conforme necessário.

Os comandos para operar o pacote podiam ser executados um a um por meio de uma janela na GUI, mas os trabalhos em lotes eram executados editando arquivos de texto que eram scripts Tcl válidos; você escolheria o modelo que executava o tipo de operação em nível de arquivo que desejava executar e, em seguida, editaria uma cópia para conter o diretório e os nomes dos arquivos reais, além dos comandos do pacote. Funcionou como um encanto. Até ...

1) O mundo voltou-se para os PCs e 2) os scripts ultrapassaram as 500 linhas, quando os duvidosos recursos organizacionais do Tcl começaram a se tornar um verdadeiro inconveniente. Tempo passou ...

Eu me aposentei, o Python foi inventado e parecia o sucessor perfeito do Tcl. Agora, nunca fiz a porta, porque nunca enfrentei os desafios de compilar (muito grandes) programas C em um PC, estendendo o Python com um pacote C e fazendo GUIs em Python / Gt? / Tk? /? ? No entanto, a velha idéia de ter scripts de modelo editável ainda parece viável. Além disso, não deve ser muito difícil inserir comandos de pacote em um formato nativo de Python, por exemplo:

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

Alguns pontos extras, parênteses e vírgulas, mas esses não são limitadores de exibição.

Lembro-me de ver que alguém fez versões do lex e do yacc no Python (tente: http://www.dabeaz.com/ply/ ); portanto, se elas ainda são necessárias, elas estão por aí.

O ponto desta divagação é que me pareceu que o próprio Python é o front-end "leve" desejado, utilizável pelos cientistas. Estou curioso para saber por que você acha que não é, e quero dizer isso seriamente.


adicionado mais tarde: O aplicativo gedit antecipa a inclusão de plug-ins e o site deles tem a explicação mais clara de um procedimento simples de plug-in que encontrei em alguns minutos olhando ao redor. Experimentar:

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

Ainda gostaria de entender melhor sua pergunta. Não estou claro se você 1) deseja que os cientistas possam usar seu aplicativo (Python) de maneira bastante simples de várias maneiras ou 2) deseja permitir que os cientistas adicionem novos recursos ao seu aplicativo. A escolha 1 é a situação que enfrentamos com as imagens e que nos levou a usar scripts genéricos que modificamos para atender à necessidade do momento. É a opção 2 que leva à idéia de plug-ins ou é algum aspecto do seu aplicativo que inviabiliza a emissão de comandos?

por trás da queda
fonte
2
Fazer a ligação reparo rot: Gedit plugin é agora - wiki.gnome.org/Apps/Gedit/PythonPluginHowTo
ohhorob
1
Este é um post bonito, porque mostra de forma clara e concisa como temos sorte os biólogos modernos. Para ele, python é a linguagem de script modular usada para fornecer alguma abstração aos desenvolvedores de módulos, para que eles não precisem analisar o código C principal. Hoje em dia, no entanto, poucos biólogos aprenderão C, em vez disso, fazendo tudo em Python. Como abstraímos as complexidades de nossos principais programas python ao escrever módulos? Daqui a 10 anos, talvez os programas sejam escritos em Emoji e os módulos sejam apenas arquivos de áudio contendo uma série de resmungos. E talvez tudo bem.
JJ
10

Ao pesquisar por Decoradores Python, encontrei um trecho de código simples, mas útil. Pode não se encaixar nas suas necessidades, mas muito inspirador.

Sistema de Registro Scipy Advanced Python # Plugin

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
fonte
1
Nota: Neste exemplo, WordProcessor.pluginnão retorna nada ( None); portanto, importar a CleanMdashesExtensionclasse posteriormente apenas importa None. Se as classes de plug-in são úteis por si só, crie o .pluginmétodo de classe return plugin.
Jkmacc
@jkmacc Você está certo. Modifiquei o trecho 13 dias após o seu comentário. Obrigado.
guneysus
7

Gostei da agradável discussão sobre as diferentes arquiteturas de plugins, oferecida pelo Dr. Andre Roberge no Pycon 2009. Ele fornece uma boa visão geral das diferentes maneiras de implementar plugins, começando por algo realmente simples.

Está disponível como um podcast (segunda parte após uma explicação sobre o uso de patches), acompanhado por uma série de seis entradas no blog .

Eu recomendo ouvi-lo rapidamente antes de tomar uma decisão.

Jon Mills
fonte
4

Cheguei aqui procurando uma arquitetura mínima de plugins e encontrei muitas coisas que pareciam um exagero para mim. Então, eu implementei plugins Python super simples . Para usá-lo, você cria um ou mais diretórios e solta um __init__.pyarquivo especial em cada um. A importação desses diretórios fará com que todos os outros arquivos Python sejam carregados como sub-módulos, e seus nomes serão colocados na __all__lista. Cabe a você validar / inicializar / registrar esses módulos. Há um exemplo no arquivo README.

samwyse
fonte
4

Na verdade, o setuptools funciona com um "diretório de plug-ins", como o exemplo a seguir, retirado da documentação do projeto: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Exemplo 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 longo prazo, o setuptools é uma opção muito mais segura, pois pode carregar plug-ins sem conflitos ou sem requisitos.

Outro benefício é que os próprios plug-ins podem ser estendidos usando o mesmo mecanismo, sem que os aplicativos originais tenham que se preocupar com isso.

ankostis
fonte
3

Como uma outra abordagem ao sistema de plug-ins, você pode verificar o projeto Estender-me .

Por exemplo, vamos definir classe simples e sua extensão

# 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 tente usá-lo:

>>> 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 mostre o que está oculto nos bastidores:

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

A biblioteca extend_me manipula o processo de criação de classe por meio de metaclasses, assim no exemplo acima, ao criar uma nova instância, MyCoolClassobtemos uma instância de nova classe que é subclasse de ambos MyCoolClassExtensione que MyCoolClasspossui funcionalidade de ambos, graças à herança múltipla do Python

Para melhor controle sobre a criação de classes, existem poucas metaclasses definidas nesta lib:

  • ExtensibleType - permite extensibilidade simples subclassificando

  • ExtensibleByHashType - semelhante ao ExtensibleType, mas com capacidade de criar versões especializadas de classe, permitindo a extensão global da classe base e a extensão de versões especializadas da classe

Essa lib é usada no OpenERP Proxy Project e parece estar funcionando suficientemente bem!

Para um exemplo real de uso, consulte a extensão 'field_datetime' do 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

Recordaqui está um objeto extesível. RecordDateTimeé extensão.

Para habilitar a extensão, apenas importe o módulo que contém a classe de extensão e (no caso acima) todos os Recordobjetos criados depois dela terão classe de extensão nas classes base, possuindo assim toda a sua funcionalidade.

A principal vantagem dessa biblioteca é que, o código que opera objetos extensíveis, não precisa saber sobre extensão e as extensões podem mudar tudo em objetos extensíveis.

Mago do fogo
fonte
Eu acho que você quer dizer para instanciar a partir da subclasse, isto é my_cool_obj = MyCoolClassExtension1(), em vez demy_cool_obj = MyCoolClass()
pylang
não, a classe Extensible substituiu o __new__método, portanto, encontra automaticamente todas as subclasses e constrói uma nova classe, que é a subclasse de todas elas, e retorna uma nova instância dessa classe criada. Portanto, o aplicativo original não precisa conhecer todas as extensões. Essa abordagem é útil ao criar a biblioteca, para permitir que o usuário final modifique ou estenda seu comportamento facilmente. no exemplo acima, MyCoolClass pode ser definido na biblioteca e usado por ele, e MyCoolClassExtension pode ser definido pelo usuário final.
FireMage 23/05
Mais um exemplo foi adicionado à resposta
FireMage 23/05
3

O setuptools possui um EntryPoint :

Os pontos de entrada são uma maneira simples de as distribuições “anunciarem” objetos Python (como funções ou classes) para serem usadas por outras distribuições. Aplicativos e estruturas extensíveis podem procurar pontos de entrada com um nome ou grupo específico, a partir de uma distribuição específica ou de todas as distribuições ativas no sys.path, e depois inspecionar ou carregar os objetos anunciados à vontade.

AFAIK, este pacote estará sempre disponível se você usar pip ou virtualenv.

guettli
fonte
2

Expandindo a resposta do @ edomaur, posso sugerir dar uma olhada em simple_plugins (plug descarado), que é uma estrutura de plug-ins simples inspirada no trabalho de Marty Alchin .

Um pequeno exemplo de uso baseado no README do projeto:

# 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ć
fonte
2

Passei um tempo lendo este tópico enquanto procurava uma estrutura de plugins no Python de vez em quando. Eu usei alguns, mas havia deficiências com eles. Aqui está o que eu proponho para o seu escrutínio em 2017, um sistema de gerenciamento de plug-ins gratuito e sem interface: Carregue-me mais tarde . Aqui estão os tutoriais sobre como usá-lo.

chfw
fonte
2

Você pode usar o pluginlib .

Os plugins são fáceis de criar e podem ser carregados de outros pacotes, caminhos de arquivos ou pontos de entrada.

Crie uma classe pai do plug-in, definindo quaisquer métodos necessários:

import pluginlib

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

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

Crie um plugin herdando uma classe pai:

import json

class JSON(Parser):
    _alias_ = 'json'

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

Carregue os plugins:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))
aviso
fonte
1
Obrigado pelo exemplo. Eu tenho lutado com uma pergunta. Como você mencionou a possibilidade de carregar os plugins de diferentes pacotes, talvez você já tenha pensado nisso. Gostaria de saber onde a classe pai deve residir. Normalmente, é recomendável tê-lo no pacote do aplicativo (presumivelmente um repositório de código-fonte separado), mas como herdaríamos dele na base de código do plug-in? Importamos todo o aplicativo para isso? Ou é necessário ter o código de interface como a classe Parser ou abstrações semelhantes em um terceiro pacote (que seria um terceiro repositório de código)?
JAponte
1
A classe pai deve residir na mesma base de código que o aplicativo, mas provavelmente em seu próprio módulo. Portanto, para um pacote chamado foo, você pode ter um módulo chamado foo.parentsonde você define as classes pai. Então seus plugins importariam foo.parents. Isso funciona bem para a maioria dos casos de uso. Como o próprio 'foo' também é importado, para evitar a possibilidade de importações circulares, muitos projetos deixam a raiz do módulo vazia e usam um __main__.pyarquivo ou pontos de entrada para iniciar o aplicativo.
aviso
1

Passei muito tempo tentando encontrar um pequeno sistema de plugins para Python, que atendesse às minhas necessidades. Mas então pensei: se já existe uma herança, natural e flexível, por que não usá-la?

O único problema com o uso de herança para plug-ins é que você não sabe quais são as classes de plug-in mais específicas (a mais baixa na árvore de herança).

Mas isso pode ser resolvido com a metaclasse, que monitora a herança da classe base, e possivelmente pode criar a classe, que herda dos plugins mais específicos ('Root extended' na figura abaixo)

insira a descrição da imagem aqui

Então, eu vim com uma solução codificando essa 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

Portanto, quando você tem uma base Root, feita com metaclasse e possui uma árvore de plugins que herdam dela, você pode obter automaticamente a classe, que herda dos plugins mais específicos, apenas subclassificando:

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

A base de código é bem pequena (~ 30 linhas de código puro) e tão flexível quanto a herança permite.

Se você estiver interessado, envolva-se em https://github.com/thodnev/pluginlib

thodnev
fonte
1

Você também pode dar uma olhada no Groundwork .

A idéia é criar aplicativos em torno de componentes reutilizáveis, chamados padrões e plugins. Plugins são classes que derivam GwBasePattern. Aqui está um exemplo 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

Existem também padrões mais avançados para lidar, por exemplo, com interfaces de linha de comando, sinalização ou objetos compartilhados.

O Groundwork encontra seus plugins vinculando-os programaticamente a um aplicativo, como mostrado acima, ou automaticamente via setuptools. Pacotes Python contendo plug-ins devem declará-los usando um ponto de entrada especial groundwork.plugin.

Aqui estão os documentos .

Disclaimer : Eu sou um dos autores de Groundwork.

ub_marco
fonte
0

Em nosso produto de assistência médica atual, temos uma arquitetura de plug-in implementada com a classe de interface. Nossa pilha tecnológica é o Django no topo do Python para API e o Nuxtjs no topo do nodejs para o front-end.

Temos um aplicativo gerenciador de plugins criado para o nosso produto, que é basicamente o pacote pip e npm em conformidade com o Django e o Nuxtjs.

Para o desenvolvimento de novos plugins (pip e npm), criamos o gerenciador de plugins como dependência.

No pacote Pip: com a ajuda do setup.py, você pode adicionar o ponto de entrada do plug-in para fazer algo com o gerenciador de plug-ins (registro, iniciações, ... etc.) Https://setuptools.readthedocs.io/en/latest/setuptools .html # criação de script automática

No pacote npm: Semelhante ao pip, existem ganchos nos scripts npm para lidar com a instalação. https://docs.npmjs.com/misc/scripts

Nosso caso:

A equipe de desenvolvimento de plug-ins está separada da equipe de desenvolvimento principal agora. O escopo do desenvolvimento de plug-ins é a integração com aplicativos de terceiros definidos em qualquer uma das categorias do produto. As interfaces de plugins são categorizadas, por exemplo: - Fax, telefone, email ... etc, o gerenciador de plugins pode ser aprimorado para novas categorias.

No seu caso: talvez você possa ter um plug-in escrito e reutilizar o mesmo para fazer coisas.

Se os desenvolvedores de plug-ins precisarem usar objetos principais de reutilização, esse objeto poderá ser usado executando um nível de abstração no gerenciador de plug-ins, para que qualquer plug-in possa herdar esses métodos.

Compartilhar apenas como implementamos nosso produto, espero que dê uma pequena idéia.

Shankar Ganesh Jayaraman
fonte