tophat / codewatch

[deprecated] Monitor and manage deeply customizable metrics about your python code using ASTs
https://codewatch.io
Apache License 2.0
38 stars 3 forks source link

Read about asteroid + potential proof of concept - https://astroid.readthedocs.io/en/latest/ #20

Closed cabiad closed 5 years ago

cabiad commented 5 years ago

First attempt at using astroid. It seems really cool, but I haven't yet figured out how to connect to the context about types that exist outside of the AST / file it is currently parsing.

Here's an edited and annotated session with it in a python shell working on some tests for a specific project.

In [1]: import astroid

In [2]: code = open('../../src/gbs/src/engine/tests.py').read()

In [3]: n = (astroid.parse(code, 'engine.tests', '../../src/gbs/src/engine/tests.py'))

In [4]: children = n.get_children()  # all top level children in the file

In [5]: tclass = n.body[30]  # it so happens that there's an interesting class that's the 31st child

In [8]: tclass.name
Out[8]: 'TestTChangeEvent'

In [9]: test_func = tclass.body[5]  # an interesting test function

In [10]: test_func.name
Out[10]: 'test_g_t_diff'

In [12]: assertion_line = test_func.body[8]

In [16]: print(assertion_line.as_string())
assert external_obj.some_attr == external_obj.another_attr * some_local_var

In [12]: # NOTE that external_obj is an instance of a class that is defined outside of this module

In [12]: # Now for the attempt at type inference ...

In [18]: print(assertion_line.test.left.expr.as_string())
external_obj

In [20]: ext_obj = assertion_line.test.left.expr

In [22]: print(next(ext_obj.infer()))
Uninferable
lime-green commented 5 years ago

I think I got it working:

$ cat use_cases.py
from get_user import C
c = C()
c.get_user()

$ cat get_user.py
class C(object):
    def get_user(self):
        return "abc"
>>> import astroid
>>> m = astroid.parse(open('use_cases.py').read())
>>> next(m.body[2].value.infer()).frame().parent.name
'C'

Does this work for you?

print(next(ext_obj.value.infer()))
lime-green commented 5 years ago

hmmm I made the example a little more complex and it's no longer working :(

edit: this seems to work with this example:

# tree is the top-level tree returned by `ast.parse`
tree.infer()
print(m.body[2].value.args[0].func.inferred()[0].frame().parent)
cabiad commented 5 years ago

Nice! I was thinking this morning that the python instance where you run astroid.parse() probably needs to be able to import the same dependencies as the file you're parsing the AST of, so easiest is to run it in an interpreter with the same context as your code (which I wasn't doing). I had assumed that with those extra args that it'd be able to complete relative imports. Trying it in a trivial project was my next step for this morning. :)

Now the next step is to try it in a larger project...

cabiad commented 5 years ago

Works great in this larger project, except that there are some Django-specific inference limitations that we can work around later.