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?

3voto

guettli Punkte 23426

setuptools hat einen EntryPoint :

Einstiegspunkte sind eine einfache Möglichkeit zur Verteilung von Objekte (wie Funktionen oder Klassen) zur Verwendung durch andere Distributionen "anzukündigen". Erweiterbare Anwendungen und Frameworks können nach Einstiegspunkten suchen mit einem bestimmten Namen oder einer bestimmten Gruppe, entweder von einer bestimmten Distribution oder von allen aktiven Distributionen auf sys.path, und dann nach Belieben die die beworbenen Objekte nach Belieben inspizieren oder laden.

AFAIK ist dieses Paket immer verfügbar, wenn Sie pip oder virtualenv verwenden.

3voto

FireMage Punkte 121

Eine weitere Möglichkeit, sich dem Plugin-System zu nähern, ist die Überprüfung Projekt "Extend Me .

Definieren wir zum Beispiel eine einfache Klasse und ihre Erweiterung

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

Und versuchen Sie, sie zu nutzen:

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

Und zeigen, was sich hinter den Kulissen verbirgt:

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

erweitern_ich Bibliothek manipuliert den Prozess der Klassenerstellung über Metaklassen, so dass im obigen Beispiel beim Erstellen einer neuen Instanz von MyCoolClass haben wir eine Instanz einer neuen Klasse, die eine Unterklasse von beiden ist MyCoolClassExtension y MyCoolClass mit der Funktionalität von beiden, dank Pythons Mehrfachvererbung

Zur besseren Kontrolle der Klassenerstellung sind in dieser Bibliothek einige Metaklassen definiert:

  • ExtensibleType - ermöglicht eine einfache Erweiterbarkeit durch Unterklassenbildung

  • ExtensibleByHashType - ähnlich wie ExtensibleType, aber mit der Fähigkeit spezialisierte Versionen von Klassen zu erstellen, die eine globale Erweiterung der Basisklasse und die Erweiterung von spezialisierten Versionen der Klasse

Diese Bibliothek wird verwendet in OpenERP Proxy Projekt und es scheint gut genug zu funktionieren!

Ein konkretes Beispiel für die Verwendung finden Sie unter OpenERP Proxy 'field_datetime' Erweiterung :

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 ist hier ein erweiterbares Objekt. RecordDateTime ist die Erweiterung.

Um die Erweiterung zu aktivieren, importieren Sie einfach das Modul, das die Erweiterungsklasse enthält, und (im obigen Fall) alle Record Objekte, die danach erstellt werden, haben die Erweiterungsklasse in ihren Basisklassen und verfügen somit über die gesamte Funktionalität.

Der Hauptvorteil dieser Bibliothek ist, dass Code, der erweiterbare Objekte bedient, nichts über Erweiterungen wissen muss und dass Erweiterungen alles in erweiterbaren Objekten ändern können.

3voto

Petar Marić Punkte 53

In Erweiterung der Antwort von @edomaur schlage ich vor, einen Blick auf einfache_plugins (schamlose Werbung), das ein einfaches Plugin-Framework ist, das von der Arbeit von Marty Alchin .

Ein kurzes Anwendungsbeispiel auf der Grundlage der README des Projekts:

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

3voto

aviso Punkte 1776

Sie können verwenden pluginlib .

Plugins sind einfach zu erstellen und können von anderen Paketen, Dateipfaden oder Einstiegspunkten geladen werden.

Erstellen Sie eine Plugin-Elternklasse und definieren Sie alle erforderlichen Methoden:

import pluginlib

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

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

Erstellen Sie ein Plugin durch Erben einer übergeordneten Klasse:

import json

class JSON(Parser):
    _alias_ = 'json'

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

Laden Sie die Plugins:

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

2voto

chfw Punkte 4382

Ich habe Zeit damit verbracht, diesen Thread zu lesen, während ich ab und zu nach einem Plugin-Framework in Python gesucht habe. Ich habe einige verwendet, aber es gab Unzulänglichkeiten mit ihnen. Hier ist, was ich mit für Ihre Prüfung im Jahr 2017 kommen, eine Schnittstelle frei, lose gekoppelte Plugin-Management-System: Lade mich später . Hier sind Tutorials wie es zu verwenden ist.

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