创建和发现插件

通常在创建Python应用程序或库时,您希望能够通过 插件 . 因为python包可以单独分布,所以您的应用程序或库可能希望自动 发现 所有插件都可用。

自动插件发现有三种主要方法:

  1. Using naming convention _.

  2. Using namespace packages _.

  3. Using package metadata _.

使用命名约定

如果应用程序的所有插件都遵循相同的命名约定,则可以使用 pkgutil.iter_modules() 以发现所有与命名约定匹配的顶级模块。例如, Flask 使用命名约定 flask_{{plugin_name}} . 如果要自动发现安装的所有烧瓶插件:

import importlib
import pkgutil

flask_plugins = {
    name: importlib.import_module(name)
    for finder, name, ispkg
    in pkgutil.iter_modules()
    if name.startswith('flask_')
}

如果你同时拥有 Flask-SQLAlchemyFlask-Talisman 然后安装插件 flask_plugins 将是:

{
    'flask_sqlachemy': <module: 'flask_sqlalchemy'>,
    'flask_talisman': <module: 'flask_talisman'>,
}

使用插件的命名约定还允许您查询python包索引 simple API 适用于符合命名约定的所有包。

使用命名空间包

Namespace packages 可用于提供在何处放置插件的约定,并提供执行发现的方法。例如,如果制作子包 myapp.plugins 名称空间包,然后是其他包 distributions 可以向该命名空间提供模块和包。安装后,您可以使用 pkgutil.iter_modules() 要发现安装在该命名空间下的所有模块和包,请执行以下操作:

import importlib
import pkgutil

import myapp.plugins

def iter_namespace(ns_pkg):
    # Specifying the second argument (prefix) to iter_modules makes the
    # returned name an absolute name instead of a relative one. This allows
    # import_module to work without having to do additional modification to
    # the name.
    return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")

myapp_plugins = {
    name: importlib.import_module(name)
    for finder, name, ispkg
    in iter_namespace(myapp.plugins)
}

指定 myapp.plugins.__path__iter_modules() 使其仅查找该命名空间下的模块。例如,如果您安装了提供模块的发行版 myapp.plugin.amyapp.plugin.b 然后 myapp_plugins 在这种情况下:

{
    'a': <module: 'myapp.plugins.a'>,
    'b': <module: 'myapp.plugins.b'>,
}

此示例使用子包作为命名空间包 (myapp.plugin ,但也可以为此目的使用顶级包(例如 myapp_plugins )如何选择要使用的名称空间是一个优先事项,但不建议将项目的主要顶级包( myapp 在本例中)一个用于插件的名称空间包,因为一个坏的插件可能会导致整个名称空间中断,这反过来会使项目变得不重要。对于“名称空间子包”方法,插件包必须省略 __init__.py 用于顶级包目录 (myapp 在本例中)并包括命名空间包样式 __init__.py 在命名空间子包目录中 (myapp/plugins )这也意味着插件需要显式地将包列表传递给 setup()packages 参数而不是使用 setuptools.find_packages() .

警告

名称空间包是一个复杂的特性,有几种不同的创建方法。强烈建议您阅读 打包命名空间包 文档并清楚地记录插件首选哪种方法。

使用包元数据

Setuptools 提供 special support 对于插件。通过提供 entry_points 参数 setup() 在里面 setup.py 插件可以注册自己进行发现。

例如,如果您有一个名为 myapp-plugin-a 其中包括 setup.py

setup(
    ...
    entry_points={'myapp.plugins': 'a = myapp_plugin_a'},
    ...
)

然后,您可以使用 pkg_resources.iter_entry_points()

import pkg_resources

plugins = {
    entry_point.name: entry_point.load()
    for entry_point
    in pkg_resources.iter_entry_points('myapp.plugins')
}

在这个例子中, plugins 将是:

{
    'a': <module: 'myapp_plugin_a'>,
}

注解

这个 entry_point 规格 setup.py 相当灵活,有很多选择。建议阅读整个章节 entry points .