如何动态添加和加载入口点?

Heb*_*rge 16 python plugins setuptools entry-point

我正在使用入口点开发一个带插件的松散机器人.我想在运行时动态添加插件.

我有一个这种结构的项目:

+ ~/my_project_dir/
    + my_projects_python_code/
    + plugins/
        - plugin1.py
        - plugin2.py
        - ...
        - pluginN.py
    - setup.py
    - venv/
    - install.sh
Run Code Online (Sandbox Code Playgroud)

我的setup.py文件看起来像这样:

from setuptools import setup, find_packages

setup(
    name="My_Project_plugins",
    version="1.0",
    packages=['plugins'],
    entry_points="""
        [my_project.plugins]
        plugin1 = plugins.plugin1:plugin1_class
        plugin2 = plugins.plugin2:plugin2_class
        ...
        pluginN = plugins.pluginN:pluginN_class
    """
        )
Run Code Online (Sandbox Code Playgroud)

运行sudo install.sh执行以下操作:

  1. 将所需文件复制到 /usr/share/my_project_dir/

  2. 激活virtualenv at /usr/share/my_project_dir/venv/bin/activate

  3. 跑: python setup.py develop

这按预期工作,并正确设置我的入口点,以便我可以通过机器人使用它们.

但我希望能够setup.py在机器人运行时添加插件并能够使用它.所以我想添加一行:pluginN+1 = plugins.pluginN+1:pluginN+1_class并且可以使用pluginN + 1.

我尝试/学到了什么:

  • /usr/share/my_project_dir/venv/bin/activate我打开一个Python交互式shell并迭代之后pkg_resources.iter_entry_points(),它列出了从setup.py的初始状态加载的所有东西(即plugin1到pluginN)

  • 如果我添加一行setup.pysudo python setup.py develop使用相同的Python shell再次运行和迭代,它就不会选择新的插件,但是如果我退出shell并重新打开它,新的插件就会被拾取.

  • 我注意到当我安装机器人时,输出的一部分说:

    • Copying My_Project_plugins-1.0-py2.7.egg to /usr/share/my_project-dir/venv/lib/python2.7/site-packages
  • 当我cd /usr/share/my_project_dir/,激活我的virtualenv,并setup.py从shell 运行它说:

    • Creating /usr/local/lib/python2.7/dist-packages/My_Project-plugins.egg-link (link to .) My_Project-plugins 1.0 is already the active version in easy-install.pth

小智 7

我需要做一些类似的事情来加载一个虚拟插件以进行测试。这与您的用例略有不同,因为我特别想避免需要在包中定义入口点(因为它只是测试代码)。

我发现我可以动态地将条目插入到 pkg_resources 数据结构中,如下所示:

import pkg_resources
# Create the fake entry point definition
ep = pkg_resources.EntryPoint.parse('dummy = dummy_module:DummyPlugin')

# Create a fake distribution to insert into the global working_set
d = pkg_resources.Distribution()

# Add the mapping to the fake EntryPoint
d._ep_map = {'namespace': {'dummy': ep}}

# Add the fake distribution to the global working_set
pkg_resources.working_set.add(d, 'dummy')
Run Code Online (Sandbox Code Playgroud)

这在运行时向“命名空间”添加了一个名为“dummy”的入口点,这将是“dummy_module.py”中的“DummyPlugin”类。

这是通过使用 setuptools 文档和对象上的 dir() 确定的,以根据需要获取更多信息。

文档在这里:http : //setuptools.readthedocs.io/en/latest/pkg_resources.html

如果您需要做的只是加载刚刚存储到本地文件系统的插件,您可能会特别查看http://setuptools.readthedocs.io/en/latest/pkg_resources.html#locating-plugins


new*_*ver 3

自从我第一次问自己几乎同样的问题以来,已经过去了至少五年多了,而你现在的问题是一种最终找到答案的冲动。

\n\n

对我来说,如果可以从与脚本相同的目录添加入口点而无需安装包,这也很有趣。尽管我一直知道该包的唯一内容可能是一些带有查看其他包的入口点的元数据。

\n\n

无论如何,这是我的目录的一些设置:

\n\n
ep_test newtover$ tree\n.\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 foo-0.1.0.dist-info\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 METADATA\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 entry_points.txt\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 foo.py\n\n1 directory, 3 files\n
Run Code Online (Sandbox Code Playgroud)\n\n

以下是 的内容foo.py

\n\n
ep_test newtover$ cat foo.py\ndef foo1():\n    print \'foo1\'\n\ndef foo2():\n    print \'foo2\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在让我们打开ipython

\n\n
In [1]: def write_ep(lines):  # a helper to update entry points file\n   ...:     with open(\'foo-0.1.0.dist-info/entry_points.txt\', \'w\') as f1:\n   ...:         print >> f1, \'\\n\'.join(lines)\n   ...:        \n\nIn [2]: write_ep([  # only one entry point under foo.test\n   ...: "[foo.test]",\n   ...: "foo_1 = foo:foo1",\n   ...: ])\n\nIn [3]: !cat foo-0.1.0.dist-info/entry_points.txt\n[foo.test]\nfoo1 = foo:foo1\n\nIn [4]: import pkg_resources\n\nIn [5]: ws = pkg_resources.WorkingSet()  # here is the answer on the question\n\nIn [6]: list(ws.iter_entry_points(\'foo.test\'))\nOut[6]: [EntryPoint.parse(\'foo_1 = foo:foo1\')]\n\nIn [7]: write_ep([  # two entry points\n   ...: "[foo.test]",\n   ...: "foo_1 = foo:foo1",\n   ...: "foo_2 = foo:foo2"\n   ...: ])\n\nIn [8]: ws = pkg_resources.WorkingSet()  # a new instance of WorkingSet\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用默认参数WorkingSet只需重新访问 sys.path 中的每个条目,但您可以缩小列表范围。pkg_resources.iter_entry_points绑定到 的全局实例WorkingSet

\n\n
In [9]: list(ws.iter_entry_points(\'foo.test\'))  # both are visible\nOut[9]: [EntryPoint.parse(\'foo_1 = foo:foo1\'), EntryPoint.parse(\'foo_2 = foo:foo2\')]\n\nIn [10]: foos = [ep.load() for ep in ws.iter_entry_points(\'foo.test\')]\n\nIn [11]: for func in foos: print \'name is {}\'.format(func.__name__); func()\nname is foo1\nfoo1\nname is foo2\nfoo2\n
Run Code Online (Sandbox Code Playgroud)\n\n

还有 METADATA 的内容:

\n\n
ep_test newtover$ cat foo-0.1.0.dist-info/METADATA\nMetadata-Version: 1.2\nName: foo\nVersion: 0.1.0\nSummary: entry point test\n
Run Code Online (Sandbox Code Playgroud)\n\n

UPD1:我再次考虑了一下,现在明白在使用新插件之前您需要一个额外的步骤:您需要重新加载模块。

\n\n

这可能很简单:

\n\n
In [33]: modules_to_reload = {ep1.module_name for ep1 in ws.iter_entry_points(\'foo.test\')}\n\nIn [34]: for module_name in modules_to_reload:\n   ....:     reload(__import__(module_name))\n   ....:\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,如果您的插件包的新版本基于其他已使用模块的重大更改,则您可能需要按特定顺序重新加载和重新加载这些已更改的模块。这可能会成为一项繁琐的任务,因此重新启动机器人将是唯一的方法。

\n