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

2024-09-30 01:33:35 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在开发一个使用入口点插件的slack bot。我想在运行时动态添加一个插件。在

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

+ ~/my_project_dir/
    + my_projects_python_code/
    + plugins/
        - plugin1.py
        - plugin2.py
        - ...
        - pluginN.py
    - setup.py
    - venv/
    - install.sh

我的setup.py文件如下所示:

^{pr2}$

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

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

  2. {cd4>

  3. 运行:python setup.py develop

这正如预期的那样工作,并且正确地设置了我的入口点,以便我可以通过bot使用它们。在

但是我希望能够向setup.py添加一个插件,并且能够在bot运行时使用它。所以我想添加一行: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(),其中列出了从初始状态加载的所有内容设置.py(即从plugin1到pluginN)

  • 如果我在setup.py中添加一行,然后运行sudo python setup.py develop并用同一个pythonshell再次迭代,它不会获取新的插件,但是如果我退出shell并重新打开它,新的插件就会被选中。

  • 我注意到,当我安装bot时,部分输出显示:

    • 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,并从shell运行setup.py,它说:

    • 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

Tags: installpyproject插件sharevenvmyusr
3条回答

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

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

总之,下面是我的目录设置:

ep_test newtover$ tree
.
├── foo-0.1.0.dist-info
│   ├── METADATA
│   └── entry_points.txt
└── foo.py

1 directory, 3 files

以下是foo.py的内容:

^{pr2}$

现在让我们打开ipython

In [1]: def write_ep(lines):  # a helper to update entry points file
   ...:     with open('foo-0.1.0.dist-info/entry_points.txt', 'w') as f1:
   ...:         print >> f1, '\n'.join(lines)
   ...:        

In [2]: write_ep([  # only one entry point under foo.test
   ...: "[foo.test]",
   ...: "foo_1 = foo:foo1",
   ...: ])

In [3]: !cat foo-0.1.0.dist-info/entry_points.txt
[foo.test]
foo1 = foo:foo1

In [4]: import pkg_resources

In [5]: ws = pkg_resources.WorkingSet()  # here is the answer on the question

In [6]: list(ws.iter_entry_points('foo.test'))
Out[6]: [EntryPoint.parse('foo_1 = foo:foo1')]

In [7]: write_ep([  # two entry points
   ...: "[foo.test]",
   ...: "foo_1 = foo:foo1",
   ...: "foo_2 = foo:foo2"
   ...: ])

In [8]: ws = pkg_resources.WorkingSet()  # a new instance of WorkingSet

使用默认参数WorkingSet只需重新访问搜索路径,但您可以缩小列表范围。pkg_resources.iter_entry_points绑定到WorkingSet的全局实例。在

In [9]: list(ws.iter_entry_points('foo.test'))  # both are visible
Out[9]: [EntryPoint.parse('foo_1 = foo:foo1'), EntryPoint.parse('foo_2 = foo:foo2')]

In [10]: foos = [ep.load() for ep in ws.iter_entry_points('foo.test')]

In [11]: for func in foos: print 'name is {}'.format(func.__name__); func()
name is foo1
foo1
name is foo2
foo2

以及元数据的内容:

ep_test newtover$ cat foo-0.1.0.dist-info/METADATA
Metadata-Version: 1.2
Name: foo
Version: 0.1.0
Summary: entry point test

UPD1:我又想了一遍,现在明白了在使用新插件之前还需要一个额外的步骤:需要重新加载模块。在

这可能很简单:

In [33]: modules_to_reload = {ep1.module_name for ep1 in ws.iter_entry_points('foo.test')}

In [34]: for module_name in modules_to_reload:
   ....:     reload(__import__(module_name))
   ....:

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

我不得不通过@j3p0uk更改一点解决方案来为我工作。我想在单元测试(unittest框架)中使用它。我所做的是:

def test_entry_point(self):
    distribution = pkg_resources.Distribution(__file__)
    entry_point = pkg_resources.EntryPoint.parse('plugin1 = plugins.plugin1:plugin1_class', dist=distribution)
    distribution._ep_map = {'my_project.plugins': {'plugin1': entry_point}}
    pkg_resources.working_set.add(distribution)

这也使entry_point.load()在正在测试的代码中为我工作,以便真正加载入口点引用的符号。在我的测试中,my_project.plugins也有测试文件的名称,然后要加载的符号在该文件的全局范围内。在

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

我发现我可以在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')

这在运行时向“namespace”添加了一个名为“dummy”的入口点,它将是“dummy”中的类“DummyPlugin”_模块.py'. 在

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

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

如果您只需要将刚刚存储到本地文件系统中的插件加载,那么您可能会特别关注http://setuptools.readthedocs.io/en/latest/pkg_resources.html#locating-plugins。在

相关问题 更多 >

    热门问题