Prinzipieller Aufbau eines erweiterten Pluginsystems

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Vortec
User
Beiträge: 52
Registriert: Dienstag 10. Dezember 2002, 11:54

Dienstag 23. November 2004, 22:54

Hallo,

ich versuche gerade ein etwas größeres Pluginsystem auf die Beine zu stellen, wobei mir die Strukturierung des Codes nicht wirklich klar ist. Also dachte ich, frage ich hier mal nach...
Zu meiner Idee, erst einmal der Aufbau im Filesystem:

Code: Alles auswählen

/script
    /script/libraries
        /script/libraries/library1.py
        /script/libraries/library2.py
        /script/libraries/plugin_manager.py
    /plugins
        /script/plugins/plugin1.py
        /script/plugins/plugin2.py
  /script/mainscript.py
mainscript.py ist das Haupscript, welches eine Klasse enthält. Diese soll die Instanzen von jeder Library erstellen (jede libraryX.py enthält eine Klasse die letztendlich instanziiert werden soll). Jede Library ist bekannt und wird hardcoded aufgerufen. Welche Daten diese beinhalten ist an dieser Stelle egal. Wichtig ist der Pluginmanager, dieser soll die Dateien im Pluginordner durchgehen und die Plugins laden (jede Datei stellt ein Plugin dar). So weit, so gut. Jetzt stoße ich aber auf Probleme, denn meine Anforderungen lauten:
- Jede Library soll auf die Funktionen der anderen Libraries zugreifen können
- Jedes Plugin soll eine Funktion registerPlugin besitzen, welche als Parameter eine Klasse übergeben bekommt (das eigentliche Plugin)
- Jedes Plugin soll auf die Funktionen der Libraries zugreifen können
- Es darf nur mit Referenzen gearbeitet werden, da eine Library, sagen wir mal, ein geöffnetes Fileobjekt beinhaltet
Um das Ganze in einer Art "Use Case Code" zu verdeutlichen:

Code: Alles auswählen

# /script/mainscript.py
class MainScript:
    def __init__(self):
        self.library1 = libraries.library1.Library1()
        self.library2 = libraries.library2.Library2()
        self.plugin_manager = libraries.plugin_manager.PluginManager()

Code: Alles auswählen

 # /script/libraries/library1.py
class Library1:
    def foo(self):
        return "foo"
    def bar(self, args):
        return "bar"

Code: Alles auswählen

 # /script/libraries/library2.py
class Library2:
    def foo2(self):
        return "foo2"
    def bar2(self, args):
        return self.library1.foo()  # An dieser Stelle Referenz auf library1.py

Code: Alles auswählen

 # /script/libraries/plugin_manager.py
class PluginManager:
    def __init__(self):
        for s in plugin_folder:
            plugin = imp.load_module()  # Wenn das Plugin geladen wird, wie wird registerPlugin() aufgerufen, wie komm ich an die übergebende Klasse ran?

Code: Alles auswählen

 # /script/plugins/plugin1.py
class AnyPlugin:
    def pFoo(self):
        return self.library1.foo()  # Hier wieder Referenz auf library1.py
    def pBar(self, args):
        return "pBar"
registerPlugin(AnyPlugin)
Uff, ein ziemlich schwer zu erklärender Sachverhalt.
Imports und dergleichen habe ich absichtlich ausgelassen, da diese meiner Meinung nach nur (noch mehr) verwirren würden.
Hoffe mein Anliegen ist einigermaßen verständlich geworden, meine Frage war: Wie kann ich eine solche Struktur am besten verwirklichen, bzw. wie würdet ihr eine solche Struktur verwirklichen? Ich und andere Leute haben sich an der Geschichte schon die Zähne ausgebissen.

Help appreciated...

Grüße,
Vortec
| [url=http://www.sourceforge.net/projects/propolice/]propolice[/url] | [url=http://del.icio.us/vortec/]bookmarks[/url] | [url=http://www.BlowIRC.net/]irc[/url] | [url=irc://irc.BlowIRC.net/python]#python[/url] |
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Dienstag 23. November 2004, 23:59

Hi Vortec,

Bei deinem Beispiel würd ich einfach im mainscrtipt.py dem Pythonpfad in sys.path, /script/libraies/ hinzufügen. Dann haben schonmal alle Module (auch die Plugins) deiner Anwendung auf die dort abgelegten librariescripts zugriff bzw. können diese importieren.

Zum Pluginmanager, da sehe ich 2 Möglichkeiten.
A: Einfach den aus plugin_manager.py die Funktion registerPlugin importieren und dann einfach aufrufen lassen im Plugin-Modul.

Code: Alles auswählen

# /script/plugins/plugin1.py
from plugin_manager import registerPlugin
import library1 # kann einfach importiert werden.

class AnyPlugin:
    def pFoo(self):
        return library1.foo()  # Hier wieder Referenz auf library1.py
    def pBar(self, args):
        return "pBar"
registerPlugin(AnyPlugin)
oder B: Mit einer Basisklasse für Plugins arbeiten und für diese eine Metaklasse basteln.

Code: Alles auswählen

# /script/libraries/plugin_manager.py

class PluginType(type):
    registered_plugins = {}

    def __init__(cls, name, bases, dict):
        super(PluginType, cls).__init__(name, bases, dict)
        cls.registered_plugins[name] = cls
        

class BasePlugin(object):
    __metaclass__ = PluginType

class PluginManager:
    def __init__(self):
        for s in plugin_folder:
            plugin = imp.load_module()

Code: Alles auswählen

 # /script/plugins/plugin1.py

import library1
from pluginmanager import BasePlugin

class AnyPlugin(BasePlugin):
    def pFoo(self):
        return library1.foo()
    def pBar(self, args):
        return "pBar"

#register_Plugin wird nicht mehr gebraucht, das macht die BasePlugin Klasse
A ist zwar sehr einfach, dafür ist B aber um einiges mächtiger und IMHO auch eleganter, es könnte praktisch der ganze Pluginmanager auch in die Metaklasse PluginType integriert werden.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Dookie
Python-Forum Veteran
Beiträge: 2010
Registriert: Freitag 11. Oktober 2002, 18:00
Wohnort: Salzburg
Kontaktdaten:

Mittwoch 24. November 2004, 12:18

Hi nochmal,

ich hab da mal ein Gerüst für einen Pluginsmanager gemacht:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
    Modul:          PluginManager
    Description:    Manager for Pluginscripts
    Version:        0.1
    Copyright:      2004 by Fritz Cizmarov fritz@sol.at
    Created:        24. Nov. 2004
    Last modified:  24. Nov. 2004
    License:        GPL
    Requirements:   Python2.3
"""

import os
import sys
import imp

class PluginType(type):
    """ Metaclass for BasePlugin """
    registered_plugins = {}

    def __init__(cls, name, bases, dict):
        super(PluginType, cls).__init__(name, bases, dict)
        cls.registered_plugins[name] = cls
    

class BasePlugin(object):
    """ Baseclass for plugins """
    __metaclass__ = PluginType
    __slots__ = []


def add_plugin(name, ppath):
    """ Add a single pluginmodule named <name> from path <ppath> """
    f, fname, description = imp.find_module(name, ppath)
    try:
        m = imp.load_module(mname, f, fname, description)
    finally:
        f.close()
        

def get_plugin(name):
    """ Get plugin-class <name> """
    return PluginType.registered_plugins[name]
    

def add_plugins(ppath):
    """ add all plugins form additional pluginpath <ppath> """
    for modul in os.listdir(ppath):
        mname = os.path.splitext(modul)[0]
        try:
            f, fname, description = imp.find_module(mname, ppath)
        except ImportError:
            continue
        try:
            m = imp.load_module(mname, f, fname, description)
        finally:
            f.close()


def _plugin_path():
    """ 
        generate pluginpaths: $APPHOME/plugins and $USERHOME/.$APPNAME/plugins
    """
    my_path = os.path.dirname(os.path.abspath(__file__))
    app_path = os.path.split(my_path)[0]
    app_name = os.path.split(app_path)[1]
    path1 = os.path.join(app_path,"plugins")
    path2 = os.path.join(os.path.expanduser("~"),"."+app_name,"plugins")
    return tuple([x for x in (path1, path2) if os.path.exists(x)])


for ppath in _plugin_path(): # add all plugins from basical pluginpaths
    _addplugins(ppath)
Es können natürlich noch mehr Initialisierungen in der Metaclasse PluginType erfolgen. Plugins aus /pfad/zur/Anwendung/plugins und $HOME/.Anwendungsname/plugins werden, falls sie existieren, automatisch eingebunden beim ersten Import des Moduls. Pluginklassen werden von PluginManager.BasePlugin abgeleitet und so automatisch registriert.
Das Modul muss zum korrekten funktionieren in /pfad/zur/Anwendung/libraries/ oder einem anderen Unterverzeichnis innerhalb von /pfad/zur/Anwendung/ liegen. Anwendung ist dann der Name der Anwendung und kann z.B. in /usr/share oder /usr/local/share oder sonstwo liegen.


Gruß

Dookie
[code]#!/usr/bin/env python
import this[/code]
Vortec
User
Beiträge: 52
Registriert: Dienstag 10. Dezember 2002, 11:54

Mittwoch 24. November 2004, 20:22

Vielen Dank, in einem ersten Test läuft dein Beispiel einwandfrei.
Muss ich mich bei Gelegenheit mal näher mit befassen :)
| [url=http://www.sourceforge.net/projects/propolice/]propolice[/url] | [url=http://del.icio.us/vortec/]bookmarks[/url] | [url=http://www.BlowIRC.net/]irc[/url] | [url=irc://irc.BlowIRC.net/python]#python[/url] |
Antworten