アドオンや拡張機能をサポートするために、Python アプリを 1 つのファイルとしてバンドルしますか?

StackOverflow https://stackoverflow.com/questions/2876967

質問

Python パッケージとそのすべての依存関係を取得し、それらを顧客に簡単に出荷できる単一のバイナリ プログラムに変換するためのユーティリティがいくつかありますが、それぞれ手順、制限事項、対象オペレーティング システムが異なります。

私の状況はさらに一歩進んでいます:サードパーティの開発者は、私のアプリケーション用のプラグイン、拡張機能、またはアドオンを作成したいと考えているでしょう。もちろん、Windows のようなプラットフォーム上のユーザーが、プラグインやアドオンがインストールされていることをアプリが簡単に発見できるような方法で、どのようにして最も簡単にインストールするかは、気の遠くなるような問題です。しかし、その基本的な疑問の先には、次のような別の疑問があります。サードパーティ開発者は、プラグインが利用可能になると同時にプラグインの依存関係がインポートできるように、拡張機能自体に必要なライブラリ (lxml などのバイナリ モジュールの可能性があります) を拡張機能にバンドルするにはどうすればよいでしょうか。

これにはどのようにアプローチできますか?これを扱いやすくするには、アプリケーションにディスク上の独自のプラグイン領域と独自のプラグイン レジストリが必要ですか?それとも、単一の実行可能ファイルとして配布されるアプリが周囲を調べて、同様に単一のファイルとしてインストールされているプラ​​グインを見つけることを可能にする、自分で記述しなくても済む一般的なメカニズムはありますか?

役に立ちましたか?

解決

アプリケーションが実行時 (またはそれ以降) にスキャンして問題のコードをインポートするプラグイン ディレクトリを用意できる必要があります。以下は、zip ファイル内に保存されたプラグインでも動作する、通常の .py または .pyc コードで動作する例です (ユーザーは、someplugin.zip を「plugins」ディレクトリにドロップするだけで、魔法のように動作させることができます)。

import re, os, sys
class Plugin(object):
    """
    The base class from which all plugins are derived.  It is used by the
    plugin loading functions to find all the installed plugins.
    """
    def __init__(self, foo):
        self.foo = foo
    # Any useful base plugin methods would go in here.

def get_plugins(plugin_dir):
    """Adds plugins to sys.path and returns them as a list"""

    registered_plugins = []

    #check to see if a plugins directory exists and add any found plugins
    # (even if they're zipped)
    if os.path.exists(plugin_dir):
        plugins = os.listdir(plugin_dir)
        pattern = ".py$"
        for plugin in plugins:
            plugin_path = os.path.join(plugin_dir, plugin)
            if os.path.splitext(plugin)[1] == ".zip":
                sys.path.append(plugin_path)
                (plugin, ext) = os.path.splitext(plugin) # Get rid of the .zip extension
                registered_plugins.append(plugin)
            elif plugin != "__init__.py":
                if re.search(pattern, plugin):
                    (shortname, ext) = os.path.splitext(plugin)
                    registered_plugins.append(shortname)
            if os.path.isdir(plugin_path):
                plugins = os.listdir(plugin_path)
                for plugin in plugins:
                    if plugin != "__init__.py":
                        if re.search(pattern, plugin):
                            (shortname, ext) = os.path.splitext(plugin)
                            sys.path.append(plugin_path)
                            registered_plugins.append(shortname)
    return registered_plugins

def init_plugin_system(cfg):
    """
    Initializes the plugin system by appending all plugins into sys.path and
    then using load_plugins() to import them.

        cfg - A dictionary with two keys:
        plugin_path - path to the plugin directory (e.g. 'plugins')
        plugins - List of plugin names to import (e.g. ['foo', 'bar'])
    """
    if not cfg['plugin_path'] in sys.path:
        sys.path.insert(0, cfg['plugin_path'])
    load_plugins(cfg['plugins'])

def load_plugins(plugins):
    """
    Imports all plugins given a list.
    Note:  Assumes they're all in sys.path.
    """
    for plugin in plugins:
        __import__(plugin, None, None, [''])
        if plugin not in Plugin.__subclasses__():
            # This takes care of importing zipped plugins:
            __import__(plugin, None, None, [plugin])

したがって、アプリケーションに新しい機能を追加する「plugins」というディレクトリ(つまり、アプリのベースディレクトリ内)に「foo.py」という名前のプラグインがあるとします。内容は次のようになります。

from plugin_stuff import Plugin

class Foo(Plugin):
    """An example plugin."""
    self.menu_entry = {'Tools': {'Foo': self.bar}}
    def bar(self):
        return "foo plugin!"

次のようにアプリを起動するときにプラグインを初期化できます。

plugin_dir = "%s/plugins" % os.getcwd()
plugin_list = get_plugins(plugin_dir)
init_plugin_system({'plugin_path': plugin_dir, 'plugins': plugin_list})
plugins = find_plugins()
plugin_menu_entries = []
for plugin in plugins:
    print "Enabling plugin: %s" % plugin.__name__
    plugin_menu_entries.append(plugin.menu_entry))
add_menu_entries(plugin_menu_entries) # This is an imaginary function

これは、プラグインが .py または .pyc ファイルである限り機能します (問題のプラットフォーム用にバイトコンパイルされていると仮定します)。スタンドアロン ファイルにすることも、ディレクトリ内に置くこともできます。 初期化.py または 同じルールの zip ファイル内にあります。

これが機能することをどうやって確認できますか?これが私がプラグインを実装した方法です PyCI. 。PyCI は Web アプリケーションですが、この方法が通常の GUI で機能しない理由はありません。上の例では、GUI のメニューにプラグインのメソッドを追加するために使用できる Plugin オブジェクト変数と組み合わせて、架空の add_menu_entries() 関数を使用することにしました。

この回答が独自のプラグイン システムの構築に役立つことを願っています。どのように実装されているかを正確に知りたい場合は、PyCI ソース コードをダウンロードし、plugin_utils.py と plugins_enabled ディレクトリにあるサンプル プラグインを参照することをお勧めします。

他のヒント

プラグインを使用する Python アプリの別の例を次に示します。 OpenSTV. 。ここで、プラグインは Python モジュールのみです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top