Observation
The same checkout (odoo-slim-19, 2,364 files) analysed by codeanalyzer-python 1.4.1 under two different Python interpreters produces materially different graphs. Emitting into Neo4j from two runs:
|
run A |
run B |
PY_CALLS |
baseline |
+2,256 (3,125 new / 869 gone) |
PY_RESOLVES_TO |
baseline |
−40 |
PyBodyNode, L4 kinds only (actual_in −1,193, actual_out −568, formal_in −678, formal_out −285) |
baseline |
−2,724 |
PY_DDG |
baseline |
−5,360 (reaching-defs −6,993, points-to +1,633) |
every level-1 body kind (call 77,332, statement 55,169, …) |
identical |
identical |
The differences are systematic re-spellings of resolved targets:
werkzeug.exceptions/NotFound → werkzeug.exceptions.NotFound/__init__
_thread/allocate_lock → _thread.LockType/__init__
super().write → BaseModel.write in one run, builtins.super/write (a ghost) in the other
with downstream consequences: one call site in StockWarehouse.write loses 100 L4 vertices because its chain no longer reaches odoo.tools.translate.
Isolation
A controlled run over a small fixture shows this is not a 1.4.0→1.4.1 emitter change:
- 1.4.0 and 1.4.1 on the same interpreter give byte-identical call targets.
- The same fixture under Python 3.13 flips
Lock() to _thread.LockType/__init__ for both versions.
v1.4.0..v1.4.1 touches only entrypoints/, neo4j/, schema/; jedi is 0.19.2 in both.
The variable is the interpreter (and by implication its stdlib typeshed and installed packages) that Jedi inspects at analysis time.
Why it matters
- Two analyses of the same commit are not comparable across machines; a Neo4j graph's
PY_CALLS depends on who emitted it.
- The defuse linker's fan-out amplifies each unresolved call (companion issue): one changed resolution added 219 edges to a single hub in a consumer's bounded call graph.
:PyApplication records analyzer_version but not the interpreter or package set, so a consumer cannot detect the difference.
Suggestions
- Record the analysis interpreter (
sys.version_info, platform) and ideally a hash of the resolved environment on :PyApplication and in analysis.json metadata.
- Document that resolution is environment-sensitive and recommend pinning the analysis interpreter for reproducible graphs.
- Consider spelling stdlib targets canonically regardless of interpreter (always the class, never the factory) so the stable cases agree.
Definition of done
- A consumer reading
analysis.json or the graph can determine which interpreter produced it.
- The docs state the sensitivity and the recommended practice.
Found while migrating the python-sdk Neo4j backend to 1.4.1 (python-sdk#327).
Observation
The same checkout (odoo-slim-19, 2,364 files) analysed by codeanalyzer-python 1.4.1 under two different Python interpreters produces materially different graphs. Emitting into Neo4j from two runs:
PY_CALLSPY_RESOLVES_TOPyBodyNode, L4 kinds only (actual_in−1,193,actual_out−568,formal_in−678,formal_out−285)PY_DDGreaching-defs−6,993,points-to+1,633)call77,332,statement55,169, …)The differences are systematic re-spellings of resolved targets:
werkzeug.exceptions/NotFound→werkzeug.exceptions.NotFound/__init___thread/allocate_lock→_thread.LockType/__init__super().write→BaseModel.writein one run,builtins.super/write(a ghost) in the otherwith downstream consequences: one call site in
StockWarehouse.writeloses 100 L4 vertices because its chain no longer reachesodoo.tools.translate.Isolation
A controlled run over a small fixture shows this is not a 1.4.0→1.4.1 emitter change:
Lock()to_thread.LockType/__init__for both versions.v1.4.0..v1.4.1touches onlyentrypoints/,neo4j/,schema/; jedi is 0.19.2 in both.The variable is the interpreter (and by implication its stdlib typeshed and installed packages) that Jedi inspects at analysis time.
Why it matters
PY_CALLSdepends on who emitted it.:PyApplicationrecordsanalyzer_versionbut not the interpreter or package set, so a consumer cannot detect the difference.Suggestions
sys.version_info, platform) and ideally a hash of the resolved environment on:PyApplicationand inanalysis.jsonmetadata.Definition of done
analysis.jsonor the graph can determine which interpreter produced it.Found while migrating the python-sdk Neo4j backend to 1.4.1 (python-sdk#327).