溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在自己的應(yīng)用上使用Stevedore實(shí)現(xiàn)插件的動(dòng)態(tài)管理

發(fā)布時(shí)間:2021-11-15 17:33:58 來源:億速云 閱讀:174 作者:柒染 欄目:云計(jì)算

如何在自己的應(yīng)用上使用Stevedore實(shí)現(xiàn)插件的動(dòng)態(tài)管理,針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。

一步一步地演示如何定義插件,然后在您的應(yīng)用程序中使用裝載和使用這些插件。

命名插件指南

Stevedore使用setuptools入口點(diǎn)來定義和加載插件。 entry point是指在Python模塊或包中定義的命名對象的標(biāo)準(zhǔn)方法。 名稱可以是對任何類、函數(shù)或?qū)嵗囊茫灰窃趯?dǎo)入包含模塊的時(shí)候創(chuàng)建的。 ( 即:它需要一個(gè)模塊級的全局 )

名稱和命名空間

entry point使用命名空間中的名稱進(jìn)行注冊。

entry point名稱通常被認(rèn)為是用戶可見的。  例如:們經(jīng)常出現(xiàn)在配置文件中,這是啟動(dòng)驅(qū)動(dòng)程序的地方。 因?yàn)樗鼈兪枪_的,所以在保持描述性的同時(shí),名稱通常盡可能短。 如: 數(shù)據(jù)庫驅(qū)動(dòng)程序插件名可能是mysql、postgresql、sqlite等。

另一方面,名稱空間是一個(gè)實(shí)現(xiàn)細(xì)節(jié),雖然它們是開發(fā)人員知道的,但它們通常不會(huì)公開給用戶。 名稱空間的命名語法看起來很像Python的包語法(a.b.c),但是名稱空間并不對應(yīng)于Python包。 為entry point命名空間使用Python包名是一種簡單的方法來確保唯一的名稱,但這完全不是必需的。 entry point 的主要特征是可以跨包發(fā)現(xiàn)它們。 這意味著一個(gè)插件可以與應(yīng)用程序完全分開開發(fā)和安裝, 只要他們同意命名空間和API。

每個(gè)名稱空間都由使用該插件的代碼所擁有,并用于搜索 entry points 。 entry point的名稱通常是由插件所擁有的,但是它們也可以通過命名鉤子的代碼來定義(看 HookManager) 。 在給定的 distribution 中,entry point的名稱必須是唯一的,但在名稱空間中不一定是唯一的 。

創(chuàng)建插件

經(jīng)過反復(fù)試驗(yàn),我發(fā)現(xiàn)定義API的最簡單方法是遵循以下步驟:

  1. 使用abc模塊創(chuàng)建一個(gè)基本抽象類來定義API插件所需的行為。 開發(fā)人員不需要從基類中子類化,但是它提供了一種方便的方式來記錄API,并且使用抽象基類使代碼保持可靠。

  2. 通過子類化基類并實(shí)現(xiàn)所需的方法來創(chuàng)建插件。

  3. 通過結(jié)合應(yīng)用程序的名稱(或庫)和API的名稱,為每個(gè)API定義一個(gè)惟一的名稱空間。 保持簡單, 例如: “cliff.formatters” or “ceilometer.pollsters.compute” 。

示例插件集

本教程中的示例程序?qū)?chuàng)建一個(gè)帶有幾個(gè)數(shù)據(jù)格式化程序的插件集,比如命令行程序可以使用什么來準(zhǔn)備將數(shù)據(jù)打印到控制臺。 每個(gè)格式化程序都將作為輸入,使用字符串鍵和內(nèi)置的數(shù)據(jù)類型作為值。 它將返回作為輸出的迭代器,該迭代器根據(jù)所使用的特定格式化程序的規(guī)則生成數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)結(jié)構(gòu)。 formatter的構(gòu)造函數(shù)允許調(diào)用者指定輸出應(yīng)該具有的最大寬度。

一個(gè)插件的基類

上面的步驟1是為需要由每個(gè)插件實(shí)現(xiàn)的API定義一個(gè)抽象基類。

# stevedore/example/base.py
import abc

import six


@six.add_metaclass(abc.ABCMeta)
class FormatterBase(object):
    """Base class for example plugin used in the tutorial.
    """

    def __init__(self, max_width=60):
        self.max_width = max_width

    @abc.abstractmethod
    def format(self, data):
        """Format the data and return unicode text.

        :param data: A dictionary with string keys and simple types as
                     values.
        :type data: dict(str:?)
        :returns: Iterable producing the formatted text.
        """

構(gòu)造函數(shù)是一個(gè)具體的方法,因?yàn)樽宇惒恍枰采w它,但是format()方法沒有做任何有用的事情,因?yàn)闆]有可用的默認(rèn)實(shí)現(xiàn)。

繼承并實(shí)現(xiàn)插件基類

下一步是創(chuàng)建幾個(gè)帶有format()的具體實(shí)現(xiàn)的插件類。 一個(gè)簡單的示例格式化程序在一行中使用每個(gè)變量名和值生成輸出。

# stevedore/example/simple.py
from stevedore.example import base


class Simple(base.FormatterBase):
    """A very basic formatter.
    """

    def format(self, data):
        """Format the data and return unicode text.

        :param data: A dictionary with string keys and simple types as
                     values.
        :type data: dict(str:?)
        """
        for name, value in sorted(data.items()):
            line = '{name} = {value}\n'.format(
                name=name,
                value=value,
            )
            yield line

還有很多其他的格式化選項(xiàng),但是這個(gè)例子將給我們提供足夠的工作來演示注冊和使用插件。

注冊插件

要使用setuptools entry point,必須使用setuptools打包應(yīng)用程序或庫。 構(gòu)建和打包過程生成元數(shù)據(jù),這些元數(shù)據(jù)可以在安裝之后找到,以找到每個(gè)python發(fā)行版提供的插件。

entry point必須被聲明為屬于特定的名稱空間,因此我們需要在進(jìn)行下一步之前選擇一個(gè)。 這些插件是來自于 stevedore 的例子, 因此我將使用 “stevedore.example.formatter”名稱空間。 現(xiàn)在可以在包裝說明中提供所有必要的信息:

# stevedore/example/setup.py
from setuptools import setup, find_packages

setup(
    name='stevedore-examples',
    version='1.0',

    description='Demonstration package for stevedore',

    author='Doug Hellmann',
    author_email='doug@doughellmann.com',

    url='http://git.openstack.org/cgit/openstack/stevedore',

    classifiers=['Development Status :: 3 - Alpha',
                 'License :: OSI Approved :: Apache Software License',
                 'Programming Language :: Python',
                 'Programming Language :: Python :: 2',
                 'Programming Language :: Python :: 2.7',
                 'Programming Language :: Python :: 3',
                 'Programming Language :: Python :: 3.4',
                 'Intended Audience :: Developers',
                 'Environment :: Console',
                 ],

    platforms=['Any'],

    scripts=[],

    provides=['stevedore.examples',
              ],

    packages=find_packages(),
    include_package_data=True,

    entry_points={
        'stevedore.example.formatter': [
            'simple = stevedore.example.simple:Simple',
            'plain = stevedore.example.simple:Simple',
        ],
    },

    zip_safe=False,
)

底部最重要的地方就是給setup()設(shè)置entry point。 該值是一個(gè)字典,將插件的名稱空間映射到它們的定義列表。 列表中的每一項(xiàng)都應(yīng)該是一個(gè)帶有name=module:importable的形式,此名對用戶是可見的, module 是模塊的Python導(dǎo)入引用 , importable 是可以從模塊內(nèi)部導(dǎo)入的名稱 ,如下:

'simple = stevedore.example.simple:Simple',
            'plain = stevedore.example.simple:Simple',
        ],
    },

    zip_safe=False,
)

在本例中,有兩個(gè)插件注冊了。 上面定義的簡單插件和一個(gè)普通的插件,它只是簡單插件的別名。

setuptools元數(shù)據(jù)

在構(gòu)建期間, setuptools  為軟件包復(fù)制 entry point定義 到 “.egg-info” 目錄的文件中。例如,stevedore例子的entry point 位于 stevedore.egg-info/entry_points.txt ,內(nèi)容如下:

[stevedore.example.formatter]
simple = stevedore.example.simple:Simple
plain = stevedore.example.simple:Simple

[stevedore.test.extension]
t2 = stevedore.tests.test_extension:FauxExtension
t1 = stevedore.tests.test_extension:FauxExtension

pkg_resources 使用 entry_points.txt 從已安裝到環(huán)境中的所有軟件中查找插件。 你不應(yīng)該修改這些文件, 除了更改setup.py中的entry point列表。

在其他包中添加插件

插件的吸引力除了 entry points 之外 ,還有就是它們可以獨(dú)立于應(yīng)用程序進(jìn)行分發(fā)。 setuptools   命名空間用來區(qū)分插件與Python源代碼名稱空間不同。 通常使用一個(gè)插件名稱空間,前綴是加載插件的應(yīng)用程序或庫的名稱,以確保它是惟一的,但是這個(gè)名稱對于Python包的代碼應(yīng)該如何生存沒有任何影響。

例如, 我們可以添加一個(gè)formatter插件的另一個(gè)實(shí)現(xiàn),它可以生成一個(gè) reStructuredText field list

# stevedore/example2/fields.py
import textwrap

from stevedore.example import base


class FieldList(base.FormatterBase):
    """Format values as a reStructuredText field list.

    For example::

      : name1 : value
      : name2 : value
      : name3 : a long value
          will be wrapped with
          a hanging indent
    """

    def format(self, data):
        """Format the data and return unicode text.

        :param data: A dictionary with string keys and simple types as
                     values.
        :type data: dict(str:?)
        """
        for name, value in sorted(data.items()):
            full_text = ': {name} : {value}'.format(
                name=name,
                value=value,
            )
            wrapped_text = textwrap.fill(
                full_text,
                initial_indent='',
                subsequent_indent='    ',
                width=self.max_width,
            )
            yield wrapped_text + '\n'

新的插件可以使用 setup.py來打包

# stevedore/example2/setup.py
from setuptools import setup, find_packages

setup(
    name='stevedore-examples2',
    version='1.0',

    description='Demonstration package for stevedore',

    author='Doug Hellmann',
    author_email='doug@doughellmann.com',

    url='http://git.openstack.org/cgit/openstack/stevedore',

    classifiers=['Development Status :: 3 - Alpha',
                 'License :: OSI Approved :: Apache Software License',
                 'Programming Language :: Python',
                 'Programming Language :: Python :: 2',
                 'Programming Language :: Python :: 2.7',
                 'Programming Language :: Python :: 3',
                 'Programming Language :: Python :: 3.4',
                 'Intended Audience :: Developers',
                 'Environment :: Console',
                 ],

    platforms=['Any'],

    scripts=[],

    provides=['stevedore.examples2',
              ],

    packages=find_packages(),
    include_package_data=True,

    entry_points={
        'stevedore.example.formatter': [
            'field = stevedore.example2.fields:FieldList',
        ],
    },

    zip_safe=False,
)

  新插件是在一個(gè)叫stevedore-examples2的獨(dú)立包

setup(
    name='stevedore-examples2',

這個(gè)插件也是注冊在stevedore.example.formatter命名空間里的

'stevedore.example.formatter': [
            'field = stevedore.example2.fields:FieldList',
        ],
    },

當(dāng)插件名稱空間被掃描時(shí),所有在當(dāng)前 PYTHONPATH 的軟件包會(huì)被檢測到, 來自第二個(gè)軟件包的 entry point 能夠被找到并加載,而應(yīng)用程序并不需要知道插件具體安裝在哪里。

加載插件

在使用插件時(shí)有幾種不同的啟用調(diào)用方式,依賴于你的需要。

加載驅(qū)動(dòng)

最常用的方式是插件作用單獨(dú)的驅(qū)動(dòng)程序。這種情況,一般是有多個(gè)插件選擇但是只有一個(gè)需要加載和調(diào)用。 DriverManager 能夠支持這種方式。下面例子就是使用 DriverManager 方式來實(shí)現(xiàn)

# stevedore/example/load_as_driver.py
from __future__ import print_function

import argparse

from stevedore import driver


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        'format',
        nargs='?',
        default='simple',
        help='the output format',
    )
    parser.add_argument(
        '--width',
        default=60,
        type=int,
        help='maximum output width for text',
    )
    parsed_args = parser.parse_args()

    data = {
        'a': 'A',
        'b': 'B',
        'long': 'word ' * 80,
    }

    mgr = driver.DriverManager(
        namespace='stevedore.example.formatter',
        name=parsed_args.format,
        invoke_on_load=True,
        invoke_args=(parsed_args.width,),
    )
    for chunk in mgr.driver.format(data):
        print(chunk, end='')

manager 接收插件的命名空間和名字作為參數(shù),用他們來找插件。因?yàn)?invoke_on_load 調(diào)用true,它會(huì)調(diào)用對象加載。在這種情況對象是被注冊為 formatter 的插件類。 invoke_args 是會(huì)被傳進(jìn)類構(gòu)造器的可選參數(shù), 用于設(shè)置最大寬度參數(shù)。

mgr = driver.DriverManager(
        namespace='stevedore.example.formatter',
        name=parsed_args.format,
        invoke_on_load=True,
        invoke_args=(parsed_args.width,),
    )

manager 被創(chuàng)建之后, 它通過調(diào)用注冊為插件的代碼返回一個(gè)對象。 該對象是實(shí)際的驅(qū)動(dòng)程序,在本例中是來自插件的formatter類的實(shí)例。 可以通過管理器的驅(qū)動(dòng)程序訪問單個(gè)驅(qū)動(dòng)程序,然后可以直接調(diào)用它的方法 。

for chunk in mgr.driver.format(data):
        print(chunk, end='')

運(yùn)行示例程序會(huì)產(chǎn)生這個(gè)輸出

$ python -m stevedore.example.load_as_driver a = A
b = B
long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

$ python -m stevedore.example.load_as_driver field
: a : A
: b : B
: long : word word word word word word word word word word
    word word word word word word word word word word word
    word word word word word word word word word word word
    word word word word word word word word word word word
    word word word word word word word word word word word
    word word word word word word word word word word word
    word word word word word word word word word word word
    word word word word

$ python -m stevedore.example.load_as_driver field --width 30
: a : A
: b : B
: long : word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word

加載擴(kuò)展

另一個(gè)常見的用例是一次加載多個(gè)擴(kuò)展, 然后調(diào)用他們。 其他幾個(gè)管理器類支持這種調(diào)用模式 ,包括: ExtensionManager, NamedExtensionManager, and EnabledExtensionManager.

# stevedore/example/load_as_extension.py
from __future__ import print_function

import argparse

from stevedore import extension


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--width',
        default=60,
        type=int,
        help='maximum output width for text',
    )
    parsed_args = parser.parse_args()

    data = {
        'a': 'A',
        'b': 'B',
        'long': 'word ' * 80,
    }

    mgr = extension.ExtensionManager(
        namespace='stevedore.example.formatter',
        invoke_on_load=True,
        invoke_args=(parsed_args.width,),
    )

    def format_data(ext, data):
        return (ext.name, ext.obj.format(data))

    results = mgr.map(format_data, data)

    for name, result in results:
        print('Formatter: {0}'.format(name))
        for chunk in result:
            print(chunk, end='')
        print('')

ExtensionManager與 DriverManager 的創(chuàng)建略有不同,因?yàn)樗恍枰崆爸酪虞d哪個(gè)插件。 它加載了它找到的所有插件。

mgr = extension.ExtensionManager(
        namespace='stevedore.example.formatter',
        invoke_on_load=True,
        invoke_args=(parsed_args.width,),
    )

調(diào)用插件,使用 map() 方法,傳遞一個(gè)回調(diào)方法來調(diào)用每個(gè)插件。 format_data() 接收兩個(gè)參數(shù)。

def format_data(ext, data):
        return (ext.name, ext.obj.format(data))

    results = mgr.map(format_data, data)

被傳進(jìn) format_data()Extension 參數(shù)由 stevedore 定義的一個(gè)類。它包含插件的名字,由 pkg_resources 返回的 EntryPoint 和插件自己。當(dāng) invoke_on_load 是true時(shí), Extension 將會(huì)有個(gè)對象屬性, 該屬性包含在調(diào)用插件時(shí)返回的值。 map() 返回回調(diào)函數(shù)返回的值序列。 在這個(gè)案例上, format_data() 返回一個(gè)元組,此元組包含一個(gè)插件名和一個(gè) iterable。 在處理結(jié)果時(shí),每個(gè)插件的名稱都被打印出來,然后是格式化的數(shù)據(jù)。

for name, result in results:
        print('Formatter: {0}'.format(name))
        for chunk in result:
            print(chunk, end='')
        print('')

加載插件的順序是未定義的,并且依賴于在導(dǎo)入路徑上找到的訂單包以及讀取元數(shù)據(jù)文件的方式。 如果使用了訂單擴(kuò)展名 ,嘗試用 NamedExtensionManager。

$ python -m stevedore.example.load_as_extension --width 30
Formatter: simple
a = A
b = B
long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

Formatter: field
: a : A
: b : B
: long : word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word word word word word
    word

Formatter: plain
a = A
b = B
long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

為什么不直接調(diào)用插件

使用一個(gè)單獨(dú)的可調(diào)用的參數(shù) map() ,而不是直接調(diào)用插件,這將導(dǎo)致應(yīng)用程序代碼和插件之間的分離。 這種分離的好處體現(xiàn)在應(yīng)用程序代碼設(shè)計(jì)和插件API設(shè)計(jì)中。

如果map()直接調(diào)用插件,每個(gè)插件都必須是可調(diào)用的。 這將意味著一個(gè)單獨(dú)的命名空間,它實(shí)際上只是一個(gè)插件的方法。 通過使用一個(gè)單獨(dú)的可調(diào)用的參數(shù),插件API不需要匹配應(yīng)用程序中任何特定的用例。 這使您可以創(chuàng)建一個(gè)更細(xì)粒度的API,使用更多的方法可以以不同的方式調(diào)用以實(shí)現(xiàn)不同的目標(biāo)。

關(guān)于如何在自己的應(yīng)用上使用Stevedore實(shí)現(xiàn)插件的動(dòng)態(tài)管理問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI