Checking code for deprecation warnings

4.1k views Asked by At

Consider the following sample code:

data = []
try:
    print data[0]
except IndexError as error:
    print error.message

There is nothing syntactically wrong (using Python2.7) with the code except that if you run python with warnings turned on, you would see a DeprecationWarning:

$ python -W always test.py
test.py:5: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
  print error.message
list index out of range

FYI, this is because .message was deprecated since python2.6 and removed in python3.

Now, I'd like to find all places in the project where .message is called on any exception instance by using static code analysis tools. As an end goal, I'm planning to have this check running as a part of a daily build&test&code quality check task and raise an error if the syntax is still used.

Is it possible? Is it something that pylint, pyflakes or other code analysis tools are capable of?


I found that pep8 tool has several similar checks implemented, for instance, has_key() usage check:

$ cat test.py
my_dict = {}
print my_dict.has_key('test')
$ pep8 test.py
test.py:2:14: W601 .has_key() is deprecated, use 'in'

As an alternative solution, I can treat all warnings as errors (like suggested here) and make my tests fail but this has its disadvantages:

  • there are other deprecation warnings coming from third-party packages that I cannot fix
  • strictly speaking, this requires 100% coverage, which is difficult to maintain
1

There are 1 answers

3
randomusername On BEST ANSWER

Since you want to do this statically, you can use the ast module to parse the code and then scan it for any occurrence of the deprecated code with a subclass of the NodeVisitor class. Like so:

import ast, sys

class UsingMessageAttr(ast.NodeVisitor):

    error_object_names = []

    def visit_Attribute(self, node):
        if (node.attr == 'message' and 
            hasattr(node.value, 'id') and 
            node.value.id in self.error_object_names):

            print("Danger Will Robinson!!")
            sys.exit(1)

        self.generic_visit(node)

    def visit_ExceptHandler(self, node):
        if node.name is not None:
            self.error_object_names.append(node.name)
            self.generic_visit(node)
            self.error_object_names.pop()
        else:
            self.generic_visit(node)

with open('sourcefile.py', 'r') as f:
    UsingMessageAttr().visit(ast.parse(f.read()))

This works by using python parse the source file into an AST and then uses the visitor pattern to walk through the entire file and find any instances of the deprecated attribute. For more information on how this works, see the python documentation on the ast module.

Note that this won't work if you use something clever to refer to the exception object. It simply takes the variable name that the exception object was bound to and checks if a message attribute is ever accessed from a variable of the same name inside the body of the exception handler.