如何为支持setuptools和distribute等的twistd/twisted插件编写setup.py?
Twisted 插件系统 是编写可扩展的 Twisted 应用程序的推荐方式。
不过,由于插件系统的结构(插件放在一个叫做 twisted/plugins 的文件夹里,而这个文件夹不能是一个 Python 包),所以写一个合适的 setup.py 来安装这些插件并不是一件简单的事。
我看到有些人尝试把 'twisted.plugins' 加到 distutils 的 setup 命令中的 'packages' 选项里,但因为它其实并不是一个真正的包,所以会出现一些问题(比如,有些工具会自动添加一个 __init__.py
文件)。
还有一些人似乎使用 'package_data' 来解决这个问题(例如,http://bazaar.launchpad.net/~glyph/divmod.org/trunk/view/head:/Epsilon/epsilon/setuphelper.py),但这也可能会以奇怪的方式失败。
问题是:有没有人成功写过一个可以在所有情况下安装 Twisted 插件的 setup.py?
4 个回答
也许你可以考虑用data_files来替代package_data的想法:这样就不需要把twisted.plugins列为包,因为它使用的是绝对路径。不过,这样做还是有点儿不太好。
我用纯distutils做了一些测试,发现可以覆盖来自其他发行版的文件。我想测试一下用pkgutil.extend_path和distutils实现的简单命名空间包,结果发现我可以用spam.ham/setup.py安装spam/ham/__init__.py
,还可以用spam.eggs/setup.py安装spam/eggs/__init__.py
。目录没问题,但文件会被直接覆盖。我觉得这在distutils中其实是未定义的行为,这个问题会影响到setuptools和pip,所以我认为pip可以把这个问题标记为不会修复。
安装Twisted插件的通常方法是什么?是手动把它放在这里吗?
这里有一篇博客文章,讲的是如何使用 'package_data' 来实现这个功能:
http://chrismiles.livejournal.com/23399.html
那这个方法可能会以什么奇怪的方式失败呢?如果安装包的时候,没有把包的数据放到一个在 sys.path 里的目录下,就会失败。在这种情况下,Twisted 插件加载器就找不到这些数据。不过,我知道的所有 Python 包的安装方式,都会把数据放到和 Python 模块或包本身相同的目录里,所以这应该不是个问题。
下面我会介绍一个只有在用户使用的 pip 版本低于 1.2(比如在 Ubuntu 12.04 上)时才需要的 setup.py 文件。如果大家的 pip 版本是 1.2 或更新的,那么你只需要在代码里加上 packages=[..., 'twisted.plugins']
就可以了。
通过阻止 pip 在 .egg-info/top_level.txt
文件中写入 "twisted
" 这一行,你可以继续使用 packages=[..., 'twisted.plugins']
,并且在使用 pip uninstall
时不会把整个 twisted/
文件夹都删掉。这需要在你的 setup.py
文件的开头进行一些修改。下面是一个示例的 setup.py
文件:
from distutils.core import setup
# When pip installs anything from packages, py_modules, or ext_modules that
# includes a twistd plugin (which are installed to twisted/plugins/),
# setuptools/distribute writes a Package.egg-info/top_level.txt that includes
# "twisted". If you later uninstall Package with `pip uninstall Package`,
# pip <1.2 removes all of twisted/ instead of just Package's twistd plugins.
# See https://github.com/pypa/pip/issues/355 (now fixed)
#
# To work around this problem, we monkeypatch
# setuptools.command.egg_info.write_toplevel_names to not write the line
# "twisted". This fixes the behavior of `pip uninstall Package`. Note that
# even with this workaround, `pip uninstall Package` still correctly uninstalls
# Package's twistd plugins from twisted/plugins/, since pip also uses
# Package.egg-info/installed-files.txt to determine what to uninstall,
# and the paths to the plugin files are indeed listed in installed-files.txt.
try:
from setuptools.command import egg_info
egg_info.write_toplevel_names
except (ImportError, AttributeError):
pass
else:
def _top_level_package(name):
return name.split('.', 1)[0]
def _hacked_write_toplevel_names(cmd, basename, filename):
pkgs = dict.fromkeys(
[_top_level_package(k)
for k in cmd.distribution.iter_distribution_names()
if _top_level_package(k) != "twisted"
]
)
cmd.write_file("top-level names", filename, '\n'.join(pkgs) + '\n')
egg_info.write_toplevel_names = _hacked_write_toplevel_names
setup(
name='MyPackage',
version='1.0',
description="You can do anything with MyPackage, anything at all.",
url="http://example.com/",
author="John Doe",
author_email="jdoe@example.com",
packages=['mypackage', 'twisted.plugins'],
# You may want more options here, including install_requires=,
# package_data=, and classifiers=
)
# Make Twisted regenerate the dropin.cache, if possible. This is necessary
# because in a site-wide install, dropin.cache cannot be rewritten by
# normal users.
try:
from twisted.plugin import IPlugin, getPlugins
except ImportError:
pass
else:
list(getPlugins(IPlugin))
我已经用 pip install
、pip install --user
和 easy_install
测试过这个方法。无论使用哪种安装方式,上面的修改和 pip uninstall
都能正常工作。
你可能会问:我需要清除这个修改以避免影响下次安装吗?(比如 pip install --no-deps MyPackage Twisted
;你不想影响 Twisted 的 top_level.txt
文件。)答案是不用;这个修改不会影响其他安装,因为 pip
每次安装时都会启动一个新的 python
进程。
相关提示:请记住,在你的项目中,必须 不要有一个文件 twisted/plugins/__init__.py
。如果在安装过程中看到这个警告:
package init file 'twisted/plugins/__init__.py' not found (or not a regular file)
这是完全正常的,你不需要通过添加一个 __init__.py
文件来尝试修复它。