我有一个python项目,我们称之为foobar
,项目根目录中有一个setup.py
脚本,就像所有Python项目一样。例如
setup.py文件内容:
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
setup(
name='foobar',
version='0.0.0',
packages=find_packages(),
install_requires=[
'spam==1.2.3',
'eggs>=4.5.6',
],
)
我需要使用Python从该setup.py文件中获取依赖项信息。我想要的部分是
[
'spam==1.2.3',
'eggs>=4.5.6',
]
在上面的例子中。我不想安装这个包,我只需要依赖项信息。当然,我可以使用正则表达式来解析它,但那会很难看,我也可以使用Python AST来解析它,但我认为应该已经有一些工具可以做到这一点。这样做的最佳方法是什么?
答案 0 :(得分:11)
在我看来,您可以使用mock
来完成工作(假设您已安装它并且您拥有所有setup.py
要求......)。这里的想法是模拟setuptools.setup
并检查它被调用的参数。当然,你真的不需要mock
这样做 - 如果你想要的话,你可以直接修补setuptools
...
import mock # or `from unittest import mock` for python3.3+.
import setuptools
with mock.patch.object(setuptools, 'setup') as mock_setup:
import setup # This is setup.py which calls setuptools.setup
# called arguments are in `mock_setup.call_args`
args, kwargs = mock_setup.call_args
print kwargs.get('install_requires', [])
答案 1 :(得分:3)
您可以使用distutils.core的run_setup:
from distutils.core import run_setup
result = run_setup("./setup.py", stop_after="init")
result.install_requires
['spam==1.2.3', 'eggs>=4.5.6']
通过这种方法,无需模拟任何内容,而且可以潜在地提取有关项目的更多信息,而无需模拟setup()调用。
请注意,此解决方案可能会出现问题,因为显然正在进行着不推荐使用distutils的工作。有关详细信息,请参见评论。
答案 2 :(得分:2)
非常类似于@ mgilson的解决方案,我使用ast,解析setup.py模块,在设置调用之前插入模拟设置方法,并收集args和kwargs。
import ast
def parse_setup(setup_filename):
"""Parse setup.py and return args and keywords args to its setup
function call
"""
mock_setup = textwrap.dedent('''\
def setup(*args, **kwargs):
__setup_calls__.append((args, kwargs))
''')
parsed_mock_setup = ast.parse(mock_setup, filename=setup_filename)
with open(setup_filename, 'rt') as setup_file:
parsed = ast.parse(setup_file.read())
for index, node in enumerate(parsed.body[:]):
if (
not isinstance(node, ast.Expr) or
not isinstance(node.value, ast.Call) or
node.value.func.id != 'setup'
):
continue
parsed.body[index:index] = parsed_mock_setup.body
break
fixed = ast.fix_missing_locations(parsed)
codeobj = compile(fixed, setup_filename, 'exec')
local_vars = {}
global_vars = {'__setup_calls__': []}
exec(codeobj, global_vars, local_vars)
return global_vars['__setup_calls__'][0]
答案 3 :(得分:1)
这里有很好的答案,但我不得不修改@mgilson的答案,让它对我有用,因为我的源代码树中有(显然)配置错误的项目,并且导入了错误的设置。在我的解决方案中,我暂时使用其他名称创建setup.py文件的副本,以便我可以导入它,并且模拟可以拦截正确的install_requires数据。
import sys
import mock
import setuptools
import tempfile
import os
def import_and_extract(parent_dir):
sys.path.insert(0, parent_dir)
with tempfile.NamedTemporaryFile(prefix="setup_temp_", mode='w', dir=parent_dir, suffix='.py') as temp_fh:
with open(os.path.join(parent_dir, "setup.py"), 'r') as setup_fh:
temp_fh.write(setup_fh.read())
temp_fh.flush()
try:
with mock.patch.object(setuptools, 'setup') as mock_setup:
module_name = os.path.basename(temp_fh.name).split(".")[0]
__import__(module_name)
finally:
# need to blow away the pyc
try:
os.remove("%sc"%temp_fh.name)
except:
print >> sys.stderr, ("Failed to remove %sc"%temp_fh.name)
args, kwargs = mock_setup.call_args
return sorted(kwargs.get('install_requires', []))
if __name__ == "__main__":
if len(sys.argv) > 1:
thedir = sys.argv[1]
if not os.path.isdir(thedir):
thedir = os.path.dirname(thedir)
for d in import_and_extract(thedir):
print d
else:
print >> sys.stderr, ("syntax: %s directory"%sys.argv[0])