我正在使用入口点开发一个带插件的松散机器人。我想在运行时动态添加一个插件。
我有一个具有这种结构的项目:
+ ~/my_project_dir/
+ my_projects_python_code/
+ plugins/
- plugin1.py
- plugin2.py
- ...
- pluginN.py
- setup.py
- venv/
- install.sh
我的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
"""
)
运行sudo install.sh
会执行以下操作:
将所需文件复制到/usr/share/my_project_dir/
在/usr/share/my_project_dir/venv/bin/activate
运行:python setup.py develop
这可以正常工作并正确设置我的入口点,以便我可以通过bot使用它们。
但是我希望能够在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.py
添加一行并运行sudo 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,然后从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
答案 0 :(得分:4)
我需要做类似的事情来加载一个虚拟插件用于测试目的。这与您的用例略有不同,因为我特意试图避免需要在包中定义入口点(因为它只是测试代码)。
我发现我可以动态地将条目插入到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')
这在运行时添加了一个名为“虚拟”的入口点。到'命名空间',这将是班级' DummyPlugin'在' dummy_module.py'。
这是通过使用setuptools文档和对象上的dir()来确定的,以便根据需要获取更多信息。
文档在这里:http://setuptools.readthedocs.io/en/latest/pkg_resources.html
如果您需要做的只是加载刚刚存储到本地文件系统的插件,您可能会特别关注http://setuptools.readthedocs.io/en/latest/pkg_resources.html#locating-plugins。
答案 1 :(得分:2)
自从我第一次问自己几乎同样的问题以来,已经超过至少5年了,现在你的问题是最终找到它的冲动。
对我而言,如果可以在不安装软件包的情况下从脚本添加与同一目录的入口点,那么它也很有趣。虽然我总是知道包的唯一内容可能是某些元素,其中包含查看其他包的入口点。
无论如何,这是我的目录的一些设置:
ep_test newtover$ tree
.
├── foo-0.1.0.dist-info
│ ├── METADATA
│ └── entry_points.txt
└── foo.py
1 directory, 3 files
以下是foo.py
的内容:
ep_test newtover$ cat foo.py
def foo1():
print 'foo1'
def foo2():
print 'foo2'
现在让我们打开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
只需重新访问sys.path中的每个条目,但您可以缩小列表范围。 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
以及METADATA的内容:
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))
....:
但是,如果新版本的插件包基于其他已使用模块的重大更改,则可能需要重新加载和重新加载这些已更改模块的特定顺序。这可能会成为一项繁琐的任务,因此重新启动机器人将是唯一的方法。
答案 2 :(得分:1)
我必须通过@ 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
具有测试文件的名称,然后要加载的符号位于该文件的全局范围内。