Python API 兼容性检查器

9
在我的当前工作环境中,我们为内部使用生产大量的Python软件包(10个甚至100个)。每个软件包都有一些依赖项,通常是对内部和外部软件包的混合依赖,并且其中一些依赖项是共享的。
随着我们接近依赖地狱,更新依赖项变成了一个耗时的过程。虽然我们关心新版本可能引入的功能更改,但同样重要(如果不是更重要的话)的是API更改会破坏代码。
尽管针对依赖项的较新版本运行单元/集成测试有助于我们捕获一些问题,但我们的覆盖率还不足以使其成为强大的策略。发布说明和更改日志有助于在高级别上识别主要更改,但这些很少适用于内部开发的工具或者没有足够详细的信息来理解新版本对(公共)API的影响。
我正在寻找其他自动化此过程的方法。
我希望能够自动比较两个 Python 包的版本,并报告它们之间的 API 差异。特别是包括不兼容的更改,如删除函数/方法/类/模块、向函数/方法/类添加位置参数和更改函数/方法返回项的数量。作为开发人员,基于生成的报告,我应该对此版本更改将引入的代码级影响有更深入的了解,因此需要的整合时间也更长。
在其他地方,我们使用 C++ abi-compliance-checker 并正在研究 Java api-compliance-checker 来帮助完成此过程。是否有类似的工具可用于 Python?我找到了很多 lint/analysis/refactor 工具,但没有提供这种功能水平的工具。我理解 Python 的动态类型使得全面报告不可能。
如果不存在这样的工具,是否有任何库可以帮助实现解决方案?例如,我的当前方法是使用ast.NodeVisitor遍历包并构建树,其中每个节点表示一个模块/类/方法/函数,然后将此树与同一包的另一个版本的树进行比较。 编辑:发布问题后,我发现pysdiff符合我的一些要求,但仍然对其他选择感兴趣。 编辑:还发现Upstream-Tracker是我想要最终得到的信息类型的很好的例子。
3个回答

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并使用所需信息构建树。 一旦有了树,可以对其进行pickle和diff或将树转换为文本表示形式,以便在文本文件中使用difftools或某些外部diff程序进行diff。

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


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))

这将会给你一个"公共"(不以下划线开头)函数列表,以及它们的参数列表。你可以添加更多内容来打印kwargs、类等。

一旦你在所有你关心的包/模块上运行了这个命令,并且包括新旧版本,你将会得到两个这样的列表:

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

接下来,您可以仅对它们进行排序和差异比较,或者编写一些更智能的 Python 工具来实际比较所有名称,例如允许额外的可选参数,但拒绝新的强制参数。


1
这种方法看起来相当简单,并且很容易扩展到涵盖types.ClassType和types.MethodType等类型。但我宁愿不必导入正在检查的模块(我应该在原始问题中加上这个)。 我认为静态分析会更可取,因为它简化了比较的运行时环境(例如,我认为pylint就是这样工作的)。 - Mark Streatfield

0

请查看zope.interfaces(您可以从PyPI获取它)。然后,您可以将模块支持接口的单元测试纳入到您的单元测试中。但是,这可能需要一些时间来进行后期调整 - 同时它也不是万能解决方案。


所以,这个想法是将每个依赖项都包装在一个 zope.interface 对象中,该对象是我针对新版本运行的一部分验收单元测试套件?听起来这对于全新的项目来说非常有效,但我认为在我目前的情况下会花费太长时间。 - Mark Streatfield
是的,我认为那是一个相当不错的总结。我想我是在建议您将大部分现有代码隔离在接口后面。我会再试一次回答 :) - demented hedgehog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接