It is very hard to determine which functions and methods are called without executing the code, even if the code doesn't do any fancy stuff. Plain function invocations are rather easy to detect, but method calls are really hard. Just a simple example:
class A(object):
def f(self):
pass
class B(A):
def f(self):
pass
a = []
a.append(A())
a.append(B())
a[1].f()
Nothing fancy going on here, but any script that tries to determine whether A.f()
or B.f()
is called will have a rather hard time to do so without actually executing the code.
While the above code doesn't do anything useful, it certainly uses patterns that appear in real code -- namely putting instances in containers. Real code will usually do even more complex things -- pickling and unpickling, hierarchical data structures, conditionals.
As stated before, just detecting plain function invocations of the form
function(...)
or
module.function(...)
will be rather easy. You can use the ast
module to parse your source files. You will need to record all imports, and the names used to import other modules. You will also need to track top-level function definitions and the calls inside these functions. This will give you a dependency graph, and you can use NetworkX to detect the connected components of this graph.
While this might sound rather complex, it can probably done with less than 100 lines of code. Unfortunately, almost all major Python projects use classes and methods, so it will be of little help.