1

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.

Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
Ramon Moraes
  • 477
  • 7
  • 23
  • I'm not sure this is as easy as it could be with Jedi. You could probably somehow call usages on each of the names. That would probably kind of get to what you want. But it's probably not "fast". – Dave Halter Sep 19 '22 at 05:53

0 Answers0