Membangun arsitektur plugin minimal dengan Python

190

Saya memiliki aplikasi, ditulis dengan Python, yang digunakan oleh audiens yang cukup teknis (ilmuwan).

Saya mencari cara yang baik untuk membuat aplikasi dapat dikembangkan oleh pengguna, yaitu arsitektur scripting / plugin.

Saya mencari sesuatu yang sangat ringan . Sebagian besar skrip, atau plugin, tidak akan dikembangkan dan didistribusikan oleh pihak ketiga dan diinstal, tetapi akan menjadi sesuatu yang dikocok oleh pengguna dalam beberapa menit untuk mengotomatiskan tugas yang berulang, menambah dukungan untuk format file, dll. Jadi plugin harus memiliki kode boilerplate minimum absolut, dan tidak memerlukan 'instalasi' selain menyalin ke folder (jadi sesuatu seperti setuptools entry point, atau arsitektur plugin Zope sepertinya terlalu banyak.)

Apakah sudah ada sistem seperti ini di luar sana, atau proyek apa pun yang menerapkan skema serupa yang harus saya perhatikan untuk ide / inspirasi?

dF.
sumber

Jawaban:

150

Milik saya, pada dasarnya, adalah direktori yang disebut "plugins" yang dapat disurvei aplikasi utama dan kemudian menggunakan imp.load_module untuk mengambil file, mencari titik masuk yang terkenal mungkin dengan param konfigurasi tingkat modul, dan pergi dari sana. Saya menggunakan file-file pemantauan untuk sejumlah dinamisme di mana plugin aktif, tapi itu bagus untuk dimiliki.

Tentu saja, setiap persyaratan yang datang mengatakan, "Saya tidak perlu [hal besar, rumit] X; Saya hanya ingin sesuatu yang ringan" beresiko menerapkan kembali X satu persyaratan yang ditemukan pada suatu waktu. Tapi itu bukan berarti Anda tidak bisa bersenang-senang melakukannya :)

TJG
sumber
26
Terima kasih banyak! Saya menulis sedikit tutorial berdasarkan pada postingan Anda: lkubuntu.wordpress.com/2012/10/02/writing-a-python-plugin-api
MiJyn
9
The impModul sedang usang dalam mendukung importlibmulai dari python 3.4
b0fh
1
Dalam banyak kasus penggunaan, Anda dapat menggunakan importlib.import_module sebagai pengganti 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)

Ini tentu saja "minimal", sama sekali tidak ada pengecekan kesalahan, mungkin masalah keamanan yang tak terhitung jumlahnya, ini tidak terlalu fleksibel - tetapi harus menunjukkan kepada Anda betapa sederhananya sebuah sistem plugin dengan Python bisa ..

Anda mungkin ingin melihat ke modul imp juga, meskipun Anda dapat melakukan banyak hal dengan adil __import__, os.listdirdan beberapa manipulasi string.

dbr
sumber
4
Saya pikir Anda mungkin ingin berubah def call_plugin(name, *args)menjadi def call_plugin(name, *args, **kwargs), dan kemudian plugin.plugin_main(*args)keplugin.plugin_main(*args, **kwargs)
Ron Klein
12
Dalam python 3, imptidak digunakan lagiimportlib
Adam Baxter
25

Meskipun pertanyaan itu sangat menarik, saya pikir itu cukup sulit untuk dijawab, tanpa perincian lebih lanjut. Aplikasi macam apa ini? Apakah ada GUI? Apakah ini alat baris perintah? Seperangkat skrip? Program dengan titik masuk unik, dll ...

Mengingat sedikit informasi yang saya miliki, saya akan menjawab dengan cara yang sangat umum.

Apa artinya Anda harus menambahkan plugin?

  • Anda mungkin harus menambahkan file konfigurasi, yang akan mencantumkan path / direktori untuk memuat.
  • Cara lain adalah dengan mengatakan "file apa pun di plugin / direktori itu akan dimuat", tetapi tidak nyaman untuk mengharuskan pengguna Anda untuk berpindah file.
  • Opsi terakhir, antara adalah mewajibkan semua plugin berada di plugin / folder yang sama, dan kemudian untuk mengaktifkan / menonaktifkannya menggunakan jalur relatif dalam file konfigurasi.

Pada praktik kode / desain murni, Anda harus menentukan dengan jelas perilaku / tindakan spesifik apa yang Anda inginkan untuk diperluas oleh pengguna Anda. Identifikasi titik masuk umum / serangkaian fungsi yang akan selalu ditimpa, dan tentukan kelompok dalam tindakan ini. Setelah ini selesai, seharusnya mudah untuk memperpanjang aplikasi Anda,

Contoh menggunakan kait , terinspirasi dari MediaWiki (PHP, tetapi apakah bahasa benar-benar penting?):

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)

Contoh lain, terinspirasi dari lincah. Di sini, ekstensi hanya menambahkan perintah ke hg commandline yang dapat dieksekusi, memperluas perilaku.

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 }

Untuk kedua pendekatan, Anda mungkin perlu menginisialisasi dan memfinalisasi untuk ekstensi Anda. Anda bisa menggunakan antarmuka umum yang harus diimplementasikan oleh semua ekstensi Anda (lebih cocok dengan pendekatan kedua; mercurial menggunakan reposetup (ui, repo) yang dipanggil untuk semua ekstensi), atau menggunakan jenis pendekatan kait, dengan hooks.setup hook.

Tetapi sekali lagi, jika Anda menginginkan jawaban yang lebih bermanfaat, Anda harus mempersempit pertanyaan Anda;)

Nicolas Dumazet
sumber
11

Kerangka kerja plugin sederhana Marty Allchin adalah basis yang saya gunakan untuk kebutuhan saya sendiri. Saya benar-benar merekomendasikan untuk melihatnya, saya pikir ini benar-benar awal yang baik jika Anda menginginkan sesuatu yang sederhana dan mudah diretas. Anda dapat menemukannya juga sebagai Potongan Django .

edomaur
sumber
Saya mencoba melakukan sesuatu seperti itu dengan pyduck sebagai dasar.
edomaur
Ini sangat spesifik Django dari apa yang bisa saya katakan.
Zoran Pavlovic
3
@ZoranPavlovic: sama sekali tidak, beberapa baris standar Python, Anda tidak harus menggunakan Django.
edomaur
11

Saya seorang pensiunan ahli biologi yang berurusan dengan micrograqphs digital dan mendapati dirinya harus menulis paket pemrosesan dan analisis gambar (bukan secara teknis perpustakaan) untuk dijalankan pada mesin SGi. Saya menulis kode dalam C dan menggunakan Tcl untuk bahasa scripting. GUI, seperti itu, dilakukan dengan menggunakan Tk. Perintah-perintah yang muncul di Tcl adalah dalam bentuk "extensionName commandName arg0 arg1 ... param0 param1 ...", yaitu, kata-kata dan angka yang dipisahkan dengan spasi sederhana. Ketika Tcl melihat substring "extensionName", kontrol dilewatkan ke paket C. Itu pada gilirannya menjalankan perintah melalui lexer / parser (dilakukan pada lex / yacc) dan kemudian memanggil rutinitas C seperlunya.

Perintah untuk mengoperasikan paket dapat dijalankan satu per satu melalui jendela di GUI, tetapi pekerjaan batch dilakukan dengan mengedit file teks yang merupakan skrip Tcl yang valid; Anda akan memilih templat yang melakukan jenis operasi tingkat file yang ingin Anda lakukan dan kemudian mengedit salinan untuk berisi direktori aktual dan nama file plus perintah paket. Itu bekerja seperti pesona. Sampai ...

1) Dunia beralih ke PC dan 2) skrip mendapatkan lebih dari 500 baris, ketika kemampuan organisasi Tcl yang rapuh mulai menjadi ketidaknyamanan yang nyata. Waktu berlalu ...

Saya pensiun, Python ditemukan, dan itu tampak seperti penerus sempurna Tcl. Sekarang, saya belum pernah melakukan port, karena saya tidak pernah menghadapi tantangan mengkompilasi (cukup besar) program C pada PC, memperluas Python dengan paket C, dan melakukan GUI dengan Python / Gt? / Tk? /? ? Namun, gagasan lama untuk memiliki skrip templat yang dapat diedit tampaknya masih dapat diterapkan. Juga, seharusnya tidak terlalu berat untuk memasukkan perintah paket dalam bentuk Python asli, misalnya:

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

Beberapa titik, paren, dan koma tambahan, tetapi itu bukan penghalang.

Saya ingat melihat seseorang telah melakukan versi lex dan yacc dengan Python (coba: http://www.dabeaz.com/ply/ ), jadi jika itu masih diperlukan, mereka ada di sekitar.

Maksud dari ocehan ini adalah bahwa bagi saya tampaknya Python sendiri adalah ujung depan "ringan" yang diinginkan yang dapat digunakan oleh para ilmuwan. Saya ingin tahu mengapa Anda berpikir itu tidak benar, dan maksud saya serius.


ditambahkan kemudian: Aplikasi gedit mengantisipasi plugin yang ditambahkan dan situs mereka memiliki penjelasan paling jelas tentang prosedur plugin sederhana yang saya temukan dalam beberapa menit mencari-cari. Mencoba:

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

Saya masih ingin memahami pertanyaan Anda dengan lebih baik. Saya tidak jelas apakah Anda 1) ingin para ilmuwan dapat menggunakan aplikasi (Python) Anda cukup sederhana dalam berbagai cara atau 2) ingin memungkinkan para ilmuwan untuk menambahkan kemampuan baru ke aplikasi Anda. Pilihan # 1 adalah situasi yang kita hadapi dengan gambar dan yang menyebabkan kita menggunakan skrip generik yang kita modifikasi agar sesuai dengan kebutuhan saat ini. Apakah itu Pilihan # 2 yang mengarahkan Anda ke gagasan plugin, atau apakah itu beberapa aspek dari aplikasi Anda yang membuat mengeluarkan perintah padanya menjadi tidak praktis?

di belakang panggilan
sumber
2
Tautan perbaikan busuk: plugin Gedit sekarang - wiki.gnome.org/Apps/Gedit/PythonPluginHowTo
ohhorob
1
Ini adalah pos yang indah, karena menunjukkan dengan jelas dan ringkas betapa beruntungnya kita para ahli biologi modern. Baginya, python adalah bahasa scripting modular yang digunakan untuk memberikan abstraksi kepada pengembang modul sehingga mereka tidak perlu menguraikan kode C utama. Namun sekarang-a-hari, beberapa ahli biologi akan pernah belajar C, bukannya melakukan semuanya dengan Python. Bagaimana kita mengabstraksi kompleksitas program python utama kita saat menulis modul? Dalam 10 tahun dari sekarang, mungkin program akan ditulis dalam Emoji dan modul hanya akan menjadi file audio yang berisi serangkaian gerutuan. Dan mungkin itu baik-baik saja.
JJ
10

Ketika saya mencari Python Decorators, menemukan potongan kode yang sederhana namun bermanfaat. Ini mungkin tidak sesuai dengan kebutuhan Anda tetapi sangat menginspirasi.

Sistem Pendaftaran 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('**', '')

Pemakaian:

processor = TextProcessor()
processed = processor.process(text="**foo bar**", plugins=(CleanMarkdownBolds, ))
processed = processor.process(text="**foo bar**")
guneysus
sumber
1
Catatan: Dalam contoh ini, WordProcessor.plugintidak mengembalikan apa pun ( None), jadi mengimpor CleanMdashesExtensionkelas nanti hanya mengimpor None. Jika kelas plugin berguna sendiri, buat .pluginmetode kelas return plugin.
jkmacc
@ jkmacc Anda benar. Saya telah memodifikasi cuplikan 13 hari setelah komentar Anda. Terima kasih.
guneysus
7

Saya menikmati diskusi yang bagus tentang arsitektur plugin yang berbeda yang diberikan oleh Dr Andre Roberge di Pycon 2009. Dia memberikan tinjauan yang baik tentang berbagai cara menerapkan plugin, mulai dari sesuatu yang sangat sederhana.

Ini tersedia sebagai podcast (bagian kedua setelah penjelasan tentang tambalan monyet) disertai dengan serangkaian enam entri blog .

Saya sarankan untuk mendengarkannya dengan cepat sebelum Anda membuat keputusan.

Jon Mills
sumber
4

Saya tiba di sini mencari arsitektur plugin minimal, dan menemukan banyak hal yang semuanya tampak seperti terlalu banyak bagi saya. Jadi, saya sudah menerapkan Plugin Python Super Sederhana . Untuk menggunakannya, Anda membuat satu atau lebih direktori dan meletakkan __init__.pyfile khusus di masing-masing. Mengimpor direktori tersebut akan menyebabkan semua file Python lainnya dimuat sebagai submodules, dan nama mereka akan ditempatkan dalam __all__daftar. Maka terserah Anda untuk memvalidasi / menginisialisasi / mendaftarkan modul-modul tersebut. Ada contoh di file README.

samwyse
sumber
4

Sebenarnya setuptools bekerja dengan "direktori plugins", seperti contoh berikut yang diambil dari dokumentasi proyek: http://peak.telecommunity.com/DevCenter/PkgResources#locating-plugins

Contoh penggunaan:

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)

Dalam jangka panjang, setuptools adalah pilihan yang jauh lebih aman karena dapat memuat plugin tanpa konflik atau persyaratan yang hilang.

Manfaat lain adalah bahwa plugin itu sendiri dapat diperpanjang menggunakan mekanisme yang sama, tanpa aplikasi asli harus peduli.

ankostis
sumber
3

Sebagai satu sama lain pendekatan untuk sistem plugin, Anda dapat memeriksa proyek Extend Me .

Sebagai contoh, mari kita mendefinisikan kelas sederhana dan ekstensinya

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

Dan cobalah untuk menggunakannya:

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

Dan tunjukkan apa yang tersembunyi di balik layar:

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

ext_me library memanipulasi proses pembuatan kelas melalui metaclasses, jadi dalam contoh di atas, ketika membuat instance baru MyCoolClasskita mendapat instance kelas baru yang merupakan subkelas keduanya MyCoolClassExtensiondan MyCoolClassmemiliki fungsionalitas keduanya, berkat pewarisan berganda Python

Untuk kontrol yang lebih baik atas pembuatan kelas ada beberapa metaclasses didefinisikan dalam lib ini:

  • ExtensibleType - memungkinkan ekstensibilitas sederhana dengan subclassing

  • ExtensibleByHashType - Mirip dengan ExtensibleType, tetapi memiliki kemampuan untuk membangun versi kelas khusus, memungkinkan ekstensi global kelas dasar dan ekstensi versi kelas khusus

Lib ini digunakan dalam Proyek Proxy OpenERP , dan tampaknya berfungsi cukup baik!

Untuk contoh nyata penggunaan, lihat di ekstensi OpenERP Proxy 'field_datetime' :

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

Recorddi sini adalah objek yang dapat diperpanjang. RecordDateTimeadalah ekstensi.

Untuk mengaktifkan ekstensi, cukup impor modul yang berisi kelas ekstensi, dan (dalam hal di atas) semua Recordobjek yang dibuat setelah itu akan memiliki kelas ekstensi di kelas dasar, sehingga memiliki semua fungsinya.

Keuntungan utama dari pustaka ini adalah bahwa, kode yang mengoperasikan objek yang dapat diperluas, tidak perlu tahu tentang ekstensi dan ekstensi dapat mengubah segalanya dalam objek yang dapat diperluas.

Penyihir api
sumber
Saya pikir Anda bermaksud instantiate dari subclass, yaitu my_cool_obj = MyCoolClassExtension1()bukannyamy_cool_obj = MyCoolClass()
pylang
tidak, kelas Extensible memiliki __new__metode yang diganti , sehingga secara otomatis dapat menemukan semua subclass, dan membangun kelas baru, yaitu subclass dari semuanya, dan mengembalikan instance baru dari kelas yang dibuat ini. Dengan demikian, aplikasi asli tidak perlu tahu tentang semua ekstensi. pendekatan ini berguna ketika membangun perpustakaan, untuk memungkinkan pengguna akhir untuk mengubah atau memperluas bechavior dengan mudah. dalam contoh di atas, MyCoolClass dapat didefinisikan di perpustakaan, dan digunakan olehnya, dan MyCoolClassExtension dapat didefinisikan oleh pengguna akhir.
FireMage
Satu lagi contoh ditambahkan untuk menjawab
FireMage
3

setuptools memiliki EntryPoint :

Titik masuk adalah cara sederhana untuk distribusi untuk "mengiklankan" objek Python (seperti fungsi atau kelas) untuk digunakan oleh distribusi lain. Aplikasi dan kerangka kerja yang dapat diperluas dapat mencari titik masuk dengan nama atau grup tertentu, baik dari distribusi tertentu atau dari semua distribusi aktif di sys.path, dan kemudian memeriksa atau memuat objek yang diiklankan sesuka hati.

AFAIK paket ini selalu tersedia jika Anda menggunakan pip atau virtualenv.

guettli
sumber
2

Memperluas jawaban @ edomaur mungkin saya sarankan melihat simple_plugins (plug shameless), yang merupakan kerangka kerja plugin sederhana yang terinspirasi oleh karya Marty Alchin .

Contoh penggunaan singkat berdasarkan README proyek:

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

Saya telah menghabiskan waktu membaca utas ini ketika saya sedang mencari kerangka kerja plugin di Python sekarang dan kemudian. Saya telah menggunakan beberapa tetapi ada kekurangan dengan mereka. Inilah yang saya temukan untuk pengamatan Anda pada tahun 2017, antarmuka gratis, sistem manajemen plugin yang digabungkan secara longgar: Muat saya nanti . Berikut ini tutorial tentang cara menggunakannya.

chfw
sumber
2

Anda dapat menggunakan pluginlib .

Plugin mudah dibuat dan dapat dimuat dari paket lain, jalur file, atau titik masuk.

Buat kelas induk plugin, yang mendefinisikan metode apa pun yang diperlukan:

import pluginlib

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

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

Buat plugin dengan mewarisi kelas induk:

import json

class JSON(Parser):
    _alias_ = 'json'

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

Muat plugin:

loader = pluginlib.PluginLoader(modules=['sample_plugins'])
plugins = loader.plugins
parser = plugins.parser.json()
print(parser.parse('{"json": "test"}'))
aviso
sumber
1
Terima kasih untuk contohnya. Saya telah berjuang dengan 1 pertanyaan. Karena Anda menyebutkan kemungkinan memuat plugin dari paket yang berbeda, mungkin Anda sudah memikirkannya. Saya bertanya-tanya di mana kelas induk harus berada. Biasanya direkomendasikan untuk memilikinya dalam paket aplikasi (mungkin repositori kode sumber terpisah), tetapi kemudian bagaimana kita akan mewarisinya dari itu di basis kode plugin? Apakah kita mengimpor seluruh aplikasi untuk ini? Atau perlukah memiliki kode antarmuka seperti kelas Parser atau abstraksi serupa dalam paket ke-3 (yang akan menjadi repositori kode ke-3)?
JAponte
1
Kelas induk harus berada di basis kode yang sama dengan aplikasi, tetapi mungkin dalam modul mereka sendiri. Jadi, untuk paket yang dipanggil foo, Anda mungkin memiliki modul bernama foo.parentstempat Anda mendefinisikan kelas induk. Maka plugin Anda, akan impor foo.parents. Itu bekerja dengan baik untuk sebagian besar kasus penggunaan. Karena 'foo' sendiri juga diimpor, untuk menghindari kemungkinan impor melingkar, banyak proyek membiarkan akar modul kosong dan menggunakan __main__.pyfile atau titik masuk untuk meluncurkan aplikasi.
aviso
1

Saya telah menghabiskan banyak waktu mencoba menemukan sistem plugin kecil untuk Python, yang sesuai dengan kebutuhan saya. Tapi kemudian saya hanya berpikir, jika sudah ada warisan, yang alami dan fleksibel, mengapa tidak menggunakannya.

Satu-satunya masalah dengan menggunakan warisan untuk plugin adalah Anda tidak tahu apa yang paling spesifik (terendah pada pohon warisan) kelas plugin.

Tapi ini bisa diselesaikan dengan metaclass, yang melacak pewarisan kelas dasar, dan mungkin bisa membangun kelas, yang mewarisi dari plugin yang paling spesifik ('Root extended' pada gambar di bawah)

masukkan deskripsi gambar di sini

Jadi saya datang dengan solusi dengan mengkodekan metaclass seperti itu:

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

Jadi ketika Anda memiliki basis Root, dibuat dengan metaclass, dan memiliki pohon plugin yang mewarisi darinya, Anda bisa secara otomatis mendapatkan kelas, yang mewarisi dari plugin yang paling spesifik hanya dengan subklasifikasi:

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

Basis kode cukup kecil (~ 30 baris kode murni) dan sefleksibel yang dimungkinkan oleh warisan.

Jika Anda tertarik, libatkan @ https://github.com/thodnev/pluginlib

thodnev
sumber
1

Anda juga dapat melihat Groundwork .

Idenya adalah untuk membangun aplikasi di sekitar komponen yang dapat digunakan kembali, yang disebut pola dan plugin. Plugin adalah kelas yang berasal GwBasePattern. Inilah contoh dasar:

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

Ada juga pola yang lebih maju untuk menangani misalnya antarmuka baris perintah, pensinyalan atau objek bersama.

Groundwork menemukan plugin-nya baik dengan mengikatnya secara terprogram ke aplikasi seperti yang ditunjukkan di atas atau secara otomatis via setuptools. Paket python yang berisi plugin harus mendeklarasikannya menggunakan titik masuk khusus groundwork.plugin.

Ini dokumennya .

Penafian : Saya salah satu penulis Groundwork.

ub_marco
sumber
0

Dalam produk perawatan kesehatan kami saat ini, kami memiliki arsitektur plugin yang diimplementasikan dengan kelas antarmuka. Tumpukan teknologi kami adalah Django di atas Python untuk API dan Nuxtjs di atas nodejs untuk frontend.

Kami memiliki aplikasi manajer plugin yang ditulis untuk produk kami yang pada dasarnya adalah paket pip dan npm yang sesuai dengan Django dan Nuxtjs.

Untuk pengembangan plugin baru (pip dan npm) kami menjadikan pengelola plugin sebagai dependensi.

Dalam paket Pip: Dengan bantuan setup.py Anda dapat menambahkan titik masuk plugin untuk melakukan sesuatu dengan manajer plugin (registri, inisiasi, ... dll.) Https://setuptools.readthedocs.io/en/latest/setuptools .html # pembuatan skrip otomatis

Dalam paket npm: Mirip dengan pip ada kait dalam skrip npm untuk menangani instalasi. https://docs.npmjs.com/misc/scripts

Penggunaan kami:

Tim pengembangan plugin terpisah dari tim pengembang inti sekarang. Ruang lingkup pengembangan plugin adalah untuk berintegrasi dengan aplikasi pihak ke-3 yang didefinisikan dalam kategori produk apa pun. Antarmuka plugin dikategorikan untuk misalnya: - Faks, telepon, email ... dll. Plugin manager dapat ditingkatkan ke kategori baru.

Dalam kasus Anda: Mungkin Anda dapat memiliki satu plugin yang ditulis dan menggunakan kembali yang sama untuk melakukan hal-hal.

Jika pengembang plugin harus menggunakan kembali objek inti, objek tersebut dapat digunakan dengan melakukan tingkat abstraksi di dalam pengelola plugin agar plugin apa pun dapat mewarisi metode-metode tersebut.

Berbagi saja bagaimana kami menerapkan produk kami berharap itu akan memberikan sedikit ide.

Shankar Ganesh Jayaraman
sumber