213 Stimmen

Aufbau einer minimalen Plugin-Architektur in Python

Ich habe eine in Python geschriebene Anwendung, die von einer eher technischen Zielgruppe (Wissenschaftler) genutzt wird.

Ich suche nach einer guten Möglichkeit, die Anwendung durch die Benutzer erweiterbar zu machen, d.h. eine Skripting/Plugin-Architektur.

Ich bin auf der Suche nach etwas extrem leicht . Die meisten Skripte oder Plugins werden nicht von einem Drittanbieter entwickelt, vertrieben und installiert, sondern sind etwas, das ein Benutzer in wenigen Minuten erstellt, um eine sich wiederholende Aufgabe zu automatisieren, ein Dateiformat zu unterstützen usw. Plugins sollten also ein absolutes Minimum an Boilerplate-Code haben und keine andere "Installation" erfordern als das Kopieren in einen Ordner (so etwas wie setuptools-Einstiegspunkte oder die Zope-Plugin-Architektur scheint zu viel zu sein).

Gibt es bereits solche Systeme oder Projekte, die ein ähnliches Schema implementieren, die ich mir ansehen sollte, um Ideen und Anregungen zu erhalten?

1voto

thodnev Punkte 1474

Ich habe viel Zeit damit verbracht, ein kleines Plugin-System für Python zu finden, das für meine Bedürfnisse geeignet ist. Aber dann dachte ich nur, wenn es bereits eine Vererbung, die natürlich und flexibel ist, warum nicht verwenden Sie es.

Das einzige Problem bei der Verwendung von Vererbung für Plugins ist, dass Sie nicht wissen, was die spezifischsten (die niedrigste auf Vererbung Baum) Plugin-Klassen sind.

Dies könnte jedoch mit einer Metaklasse gelöst werden, die die Vererbung der Basisklasse verfolgt und möglicherweise eine Klasse erstellen könnte, die von den meisten spezifischen Plugins erbt ('Root extended' in der Abbildung unten)

enter image description here

Ich habe also eine Lösung gefunden, indem ich eine solche Metaklasse kodiert habe:

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

Wenn Sie also eine Root-Basis haben, die mit einer Metaklasse erstellt wurde, und einen Baum von Plugins haben, die davon erben, können Sie automatisch eine Klasse erhalten, die von den spezifischsten Plugins erbt, indem Sie einfach eine Unterklasse bilden:

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

Die Codebasis ist ziemlich klein (~30 Zeilen reiner Code) und so flexibel, wie es die Vererbung erlaubt.

Wenn Sie interessiert sind, machen Sie mit @ https://github.com/thodnev/pluginlib

1voto

ub_marco Punkte 130

Sie können auch einen Blick werfen auf Grundlagenarbeit .

Die Idee ist, Anwendungen um wiederverwendbare Komponenten herum aufzubauen, die als Patterns und Plugins bezeichnet werden. Plugins sind Klassen, die sich ableiten von GwBasePattern . Hier ist ein einfaches Beispiel:

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

Es gibt auch fortgeschrittenere Muster, um z.B. Befehlszeilenschnittstellen, Signalisierung oder gemeinsam genutzte Objekte zu behandeln.

Groundwork findet seine Plugins entweder durch programmatische Bindung an eine App wie oben gezeigt oder automatisch über setuptools . Python-Pakete, die Plugins enthalten, müssen diese über einen speziellen Einstiegspunkt deklarieren groundwork.plugin .

Hier sind die docs .

Haftungsausschluss : Ich bin einer der Autoren von Groundwork.

0voto

In unserem aktuellen Produkt für das Gesundheitswesen haben wir eine Plugin-Architektur mit einer Schnittstellenklasse implementiert. Unser Tech-Stack sind Django auf Python für API und Nuxtjs auf Nodejs für Frontend.

Wir haben ein Plugin-Manager-App für unser Produkt geschrieben, die im Grunde pip und npm-Paket in Übereinstimmung mit Django und Nuxtjs ist.

Für die Entwicklung neuer Plugins (pip und npm) haben wir den Plugin-Manager als Abhängigkeit eingerichtet.

Im Pip-Paket: Mit Hilfe von setup.py können Sie den Einstiegspunkt des Plugins hinzufügen, um etwas mit dem Plugin-Manager zu tun (Registrierung, Einleitungen, ...usw.) https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation

Im npm-Paket: Ähnlich wie bei pip gibt es Hooks in npm-Skripten, um die Installation zu handhaben. https://docs.npmjs.com/misc/scripts

Unser Anwendungsfall:

Das Plugin-Entwicklungsteam ist jetzt vom Kern-Entwicklungsteam getrennt. Der Umfang der Plugin-Entwicklung ist für die Integration mit Anwendungen von Drittanbietern, die in einer der Kategorien des Produkts definiert sind. Die Plugin-Schnittstellen sind kategorisiert, z. B. Fax, Telefon, E-Mail ... usw. Der Plugin-Manager kann um neue Kategorien erweitert werden.

In Ihrem Fall: Vielleicht können Sie ein Plugin geschrieben haben und dasselbe wiederverwenden, um Dinge zu tun.

Wenn Plugin-Entwickler Kernobjekte wiederverwenden müssen, kann dieses Objekt durch eine Abstraktionsebene im Plugin-Manager verwendet werden, so dass alle Plugins diese Methoden erben können.

Wir teilen nur mit, wie wir unser Produkt implementiert haben, und hoffen, dass es einen kleinen Einblick gibt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X