I trying to come up with a script that could generate a sort of a list of the possible call chains on a project that could reach a certain function.
Let's say we have the following modules
models/user.py
class User:
def get_by_id(self, pk):
pass
services/products.py
from models.user import User
def get_products_by_user_id(self, user_pk):
# ...
user = User.get_by_id(user_pk)
# ...
return products
controllers/products.py
from services.products import get_products_by_user_id
class ProductList:
def get(self):
# ...
user_pk = self.get_logged_user_id()
get_products_by_user_id(user_pk)
# ...
On the majority of the code editors or IDEs we could find out which parts of our code base are making direct reference to User.get_by_id
function. The result of such action would be something like:
services/products.py:123
But what I need is to extend this "traceback" to the point where I can extend the search and see which part of our code base is referencing the first result set. That would look something like:
* controllers.products.ProductList.get
|
`---> * services.products.get_products_by_user_id
|
`---> models.user.User.get_by_id
Right now I'm trying to accomplish this using the Jedi library. Not sure if that's what I needed though but that what I have so far:
import os
import jedi
proj_base_path = '/path/to/the/project_folder'
def get_file_paths(path):
ex_path = os.path.expanduser(path)
for root, dirs, files in os.walk(ex_path):
for f_path in files:
if os.path.splitext(f_path)[1] != '.py':
continue
ex_f_path = os.path.join(root, f_path)
yield ex_f_path
fn_name = 'get_by_id'
for file_path in get_file_paths(os.path.join(proj_base_path, 'core')):
script = jedi.Script(path=file_path, project=jedi.Project(proj_base_path))
jnames = script.search(fn_name)
if len(jnames) == 0:
continue
print(f'found {len(jnames)} occurrences of [{fn_name}] in [{file_path}]')
for jname in jnames:
print(f'\t{jname.name} {jname.type} {jname.module_name}')
This script is being able to more or less returning to me the "1st layer" of references.
What's cracking my head is: is Jedi able to recover the name of the function calling the function we searched first?
Based on the first example I can search for get_by_id
and we will get an jedi.api.classes.Name which refers to the services/products.py. But, so far, I wasn't able to figure out how to use it to recover the get_products_by_user_id
name and loop again to build the 2nd layer of references.