-
Notifications
You must be signed in to change notification settings - Fork 9
Add Python import tracking to code graph #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
07b7eac
28872e3
ae661e8
d8ca830
109c94a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,6 @@ | ||||||||||
| from pathlib import Path | ||||||||||
| from tree_sitter import Node, Tree | ||||||||||
| from typing import Self | ||||||||||
|
|
||||||||||
| from api.entities.entity import Entity | ||||||||||
|
|
||||||||||
|
|
@@ -21,10 +22,30 @@ def __init__(self, path: Path, tree: Tree) -> None: | |||||||||
| self.path = path | ||||||||||
| self.tree = tree | ||||||||||
| self.entities: dict[Node, Entity] = {} | ||||||||||
| self.imports: list[Node] = [] | ||||||||||
| self.resolved_imports: set[Self] = set() | ||||||||||
|
||||||||||
| self.imports: list[Node] = [] | |
| self.resolved_imports: set[Self] = set() | |
| self.imports: list[Node] = [] | |
| self.resolved_imports: set[Entity] = set() |
🤖 Prompt for AI Agents
In api/entities/file.py around lines 25 to 26, the type annotation for
resolved_imports is incorrect (set[Self]) — change it to set[Entity] so it
reflects that resolved imports are Entity objects; update or add the necessary
import or forward reference for Entity in this module if not already present,
and run a type-check to ensure no remaining references expect Self.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| """Module A with a class definition.""" | ||
|
|
||
| class ClassA: | ||
| """A simple class in module A.""" | ||
|
|
||
| def method_a(self): | ||
| """A method in ClassA.""" | ||
| return "Method A" | ||
|
|
||
| def function_a(): | ||
| """A function in module A.""" | ||
| return "Function A" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| """Module B that imports from module A.""" | ||
|
|
||
| from module_a import ClassA, function_a | ||
|
|
||
| class ClassB(ClassA): | ||
| """A class that extends ClassA.""" | ||
|
|
||
| def method_b(self): | ||
| """A method in ClassB.""" | ||
| result = function_a() | ||
| return f"Method B: {result}" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import os | ||
| import unittest | ||
| from pathlib import Path | ||
|
|
||
| from api import SourceAnalyzer, File, Graph | ||
|
|
||
|
|
||
| class Test_PY_Imports(unittest.TestCase): | ||
| def test_import_tracking(self): | ||
| """Test that Python imports are tracked correctly.""" | ||
| # Get test file path | ||
| current_dir = os.path.dirname(os.path.abspath(__file__)) | ||
| test_path = os.path.join(current_dir, 'source_files', 'py_imports') | ||
|
|
||
| # Create graph and analyze | ||
| g = Graph("py_imports_test") | ||
| analyzer = SourceAnalyzer() | ||
|
|
||
| try: | ||
| analyzer.analyze_local_folder(test_path, g) | ||
|
|
||
| # Verify files were created | ||
| module_a = g.get_file('', 'module_a.py', '.py') | ||
| self.assertIsNotNone(module_a, "module_a.py should be in the graph") | ||
|
|
||
| module_b = g.get_file('', 'module_b.py', '.py') | ||
| self.assertIsNotNone(module_b, "module_b.py should be in the graph") | ||
|
|
||
| # Verify classes were created | ||
| class_a = g.get_class_by_name('ClassA') | ||
| self.assertIsNotNone(class_a, "ClassA should be in the graph") | ||
|
|
||
| class_b = g.get_class_by_name('ClassB') | ||
| self.assertIsNotNone(class_b, "ClassB should be in the graph") | ||
|
|
||
| # Verify function was created | ||
| func_a = g.get_function_by_name('function_a') | ||
| self.assertIsNotNone(func_a, "function_a should be in the graph") | ||
|
|
||
| # Test: module_b should have IMPORTS relationship to ClassA | ||
| # Query to check if module_b imports ClassA | ||
| query = """ | ||
| MATCH (f:File {name: 'module_b.py'})-[:IMPORTS]->(c:Class {name: 'ClassA'}) | ||
| RETURN c | ||
| """ | ||
| result = g._query(query, {}) | ||
| self.assertGreater(len(result.result_set), 0, | ||
| "module_b.py should import ClassA") | ||
|
|
||
| # Test: module_b should have IMPORTS relationship to function_a | ||
| query = """ | ||
| MATCH (f:File {name: 'module_b.py'})-[:IMPORTS]->(fn:Function {name: 'function_a'}) | ||
| RETURN fn | ||
| """ | ||
| result = g._query(query, {}) | ||
| self.assertGreater(len(result.result_set), 0, | ||
| "module_b.py should import function_a") | ||
|
|
||
| print("✓ Import tracking test passed") | ||
|
|
||
| finally: | ||
| # Cleanup: delete the test graph | ||
| g.delete() | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify handling of dotted module imports and bare import statements.
The implementation may not correctly handle certain import patterns:
Dotted imports: For
import a.b.c, line 172 extractschild.children[0]which only resolvesa, not the full module patha.b.c.Bare module imports: For
import module(notfrom module import ...), the code attempts to resolve the module itself as a type/entity. This differs semantically fromfrom module import entity, where a specific entity is imported. Consider whether bare module imports should create IMPORTS edges or be handled differently.🏁 Script executed:
Length of output: 2908
I need to examine the actual implementation to verify whether the concerns about dotted imports and bare imports are valid.
🏁 Script executed:
Length of output: 1221
🏁 Script executed:
Length of output: 1529
🏁 Script executed:
Length of output: 2271
🏁 Script executed:
Length of output: 499
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 160
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 8301
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 1905
🏁 Script executed:
Length of output: 3457
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 255
🏁 Script executed:
Length of output: 1229
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 36
🏁 Script executed:
Length of output: 2573
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 7132
🏁 Script executed:
Length of output: 0
Need to check where
resolve_importis called and whether this code is actually used:Now let me search for whereresolve_importis actually being called:🏁 Script executed:
Length of output: 719
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 185
🏁 Script executed:
Length of output: 1270
🏁 Script executed:
Length of output: 2358
🏁 Script executed:
Length of output: 618
🏁 Script executed:
Length of output: 58
🏁 Script executed:
Length of output: 58
🏁 Script executed:
Length of output: 2406
🏁 Script executed:
Length of output: 0
🏁 Script executed:
Length of output: 344
🏁 Script executed:
Length of output: 628
Fix dotted import resolution to capture full module paths, not just first segment.
The code at line 172 (and 180, 198, 206) uses
child.children[0]to extract identifiers fromdotted_namenodes. This only captures the first segment. Forimport a.b.c, this extracts onlyainstead of the full patha.b.c.Bare imports may not resolve as intended. Since
resolve_typesearches for aclass_definitionparent, bare module imports (e.g.,import os) will likely return empty results. Verify whether this is intentional (only track entity imports, not module imports) or if module-level resolution needs different handling.Add test coverage for dotted and bare import patterns to prevent regression, since no test cases currently exercise
resolve_importlogic.🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot FYI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.