创建和发现插件¶
通常在创建Python应用程序或库时,您希望能够通过 插件 . 因为python包可以单独分布,所以您的应用程序或库可能希望自动 发现 所有插件都可用。
自动插件发现有三种主要方法:
Using naming convention _.
Using namespace packages _.
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-SQLAlchemy 和 Flask-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.a
和 myapp.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 .