How to recover the call chain of the functions calling the initially searched function?

59 Views Asked by At

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.

0

There are 0 best solutions below