Python API兼容性检查器

时间:2014-02-21 02:46:10

标签: python backwards-compatibility

在我目前的工作环境中,我们会生成大量内部使用的Python包(如果不是100则为10s)。每个包都有一些依赖关系,通常是内部和外部包的混合,并且这些依赖关系中的一些是共享的。

当我们接近dependency hell时,更新依赖关系变得非常耗时。虽然我们关心新版本可能引入的功能更改,但重要的是(如果不是更多)重要的API更改会破坏代码。

尽管针对较新版本的依赖项运行单元/集成测试有助于我们捕获一些问题,但我们的覆盖率并不足以达到100%才能使其成为一个强大的策略。发行说明和更改日志有助于识别高级别的主要更改,但这些更改很少存在于内部开发的工具中,或者详细了解新版本对(公共)API的影响。

我正在寻找其他方法来自动化这个过程。

我希望能够自动比较Python包的两个版本并报告它们之间的API差异。特别是这将包括向后不兼容的更改,例如删除函数/方法/类/模块,向函数/方法/类添加位置参数以及更改函数/方法返回的项数。作为开发人员,根据生成的报告,我应该对此版本更改将引入的代码级别含义有更深入的了解,因此需要时间来集成它。

在其他地方,我们使用C++ abi-compliance-checker并正在查看Java api-compliance-checker以帮助完成此过程。是否有适用于Python的类似工具?我发现了大量的lint / analysis / refactor工具,但没有提供这种功能。我知道Python的动态类型将无法提供全面的报告。

如果这样的工具不存在,那么它们是否可以帮助实施解决方案?例如,我当前的方法是使用ast.NodeVisitor遍历包并构建一个树,其中每个节点代表一个模块/类/方法/函数,然后将该树与同一个包的另一个版本的树进行比较

修改:自发布问题后我发现pysdiff涵盖了我的一些要求,但仍有兴趣看到替代方案。

修改:同时发现Upstream-Tracker也是我最终希望得到的信息的一个很好的例子。

3 个答案:

答案 0 :(得分:5)

使用AST模块解析文件怎么样?

import ast

with file("test.py") as f:
    python_src = f.read()

    node = ast.parse(python_src) # Note: doesn't compile the src
    print ast.dump(node)

ast节点上有walk方法(描述为http://docs.python.org/2/library/ast.html

astdump可能有效(在pypi上可用)

这个过时的漂亮打印机  http://code.activestate.com/recipes/533146-ast-pretty-printer/

文档工具Sphinx还会提取您要查找的信息。或许可以看看。

所以走AST并用你想要的信息构建一棵树。一旦你有了一棵树,你可以腌制它并稍后进行差异化或将树转换为文本表示形式 您可以使用difftools或某些外部差异程序进行差异的文本文件。

ast有parse()和compile()方法。唯一的问题是我不能完全确定解析后有多少信息可供使用(因为你不想编译())。

答案 1 :(得分:2)

也许你可以先使用inspect模块

import inspect
import types
def genFunctions(module):
    moduleDict = module.__dict__
    for name in dir(module):
        if name.startswith('_'):
            continue
        element = moduleDict[name]
        if isinstance(element, types.FunctionType):
            argSpec = inspect.getargspec(element)
            argList = argSpec.args
            print "{}.{}({})".format(module.__name__, name, ", ".join(argList))

这将为您提供一个“public”列表(不以下划线开头)及其参数列表。您可以添加更多内容来打印kwargs,类等。

一旦你在所有你关心的软件包/模块上运行它,无论是旧版本还是新版本,你都会有两个这样的列表:

myPackage.myModule.myFunction1(foo, bar)
myPackage.myModule.myFunction2(baz)

然后你可以对它们进行排序和区分,或者在Python中编写一些更智能的工具来实际比较所有的名称,例如:允许其他可选参数但拒绝新的强制参数。

答案 2 :(得分:0)

查看zope.interfaces(您可以从PyPI获取)。然后,您可以将模块支持接口的单元测试合并到单元测试中。可能需要一段时间来适应复古 - 但它也不是银弹。