Just using ast is not going to be helpful in a lot of situations. You
need to include some kind of (simulated) execution, akin of exec().
For example, what can the ast tell from this Python program, which outputs
a string of digits? The identifier "digits" seems to come out of thin air
in the syntax tree.
exec('from string import digits')
mystr = digits
print(mystr)
And, to support the argument of execution needed, the following program
would have to result in output nums -> int, but just using a syntax tree, it
would probably output nums -> string | int.
import string
nums = string.digits
if True:
nums = 0
print(nums)
Now, what you are asking can still be done, but rather than solve it with
abstract syntax trees, you would have more success using trace as a
starting point, and enhance the data collected in the various tracing calls.