Summary
In 1.0.1, the Neo4j projection writes code: None for every :PyCallable and :PyClass node. Schema v2 removed the code field from PyCallable/PyClass (source now lives once on PyModule.source, sliced by each node's Span), but neo4j/project.py still reads the old field:
# neo4j/project.py:468 (classes) and :487 (callables)
"code": getattr(cl, "code", None), # field no longer exists → always None
Since the pydantic models neither define code nor allow extras, the getattr fallback always fires.
Repro (1.0.1)
from codeanalyzer.core import Codeanalyzer
from codeanalyzer.options import AnalysisOptions
from codeanalyzer.config import OutputFormat
from codeanalyzer.neo4j.project import project
opts = AnalysisOptions(input=Path("tinyproj"), output=None, format=OutputFormat.JSON,
skip_tests=True, no_venv=True)
with Codeanalyzer(opts) as a:
analysis = a.analyze()
rows = project(analysis.application, "tinyproj", {})
for node in rows.nodes:
if node.props.get("signature") in ("pkg.mod.f", "pkg.mod.A.m"):
print(node.props["signature"], node.props["code"])
# pkg.mod.A.m None
# pkg.mod.f None
(tinyproj/pkg/mod.py = a two-callable module; both emit code: None while the in-memory models carry correct span.bytes and module.source.)
Impact
neo4j/schema.py still declares the code property and builds the fulltext index py_code_fts over c.code / c.docstring — the index is now over nulls, so code search in the graph is dead.
- The CLDK python-sdk's Neo4j backend (
PyNeo4jBackend.get_method_bodies, RETURN c.code AS code) returns nothing, breaking local ↔ Neo4j backend parity (the in-process backend recovers source by slicing module.source with span.bytes).
Suggested fix
Derive the property at projection time the same way schema.l1_body handles spans — slice the module source by the callable's byte span:
start, end = c.span.bytes
code = mod.source.encode("utf-8")[start:end].decode("utf-8")
(needs the owning module's source in scope at the _project_class / callable projection sites; byte_offsets/Span.bytes are already populated at L1+).
Alternatively, if code is intentionally dropped from the graph, remove the property from neo4j/schema.py and the py_code_fts index and bump SCHEMA_VERSION — consumers fail fast on the version stamp either way. Keeping the declared property but always writing null is the one option that breaks consumers silently.
Found while bumping python-sdk to codeanalyzer-python==1.0.1 for cldk 2.0.0-rc.1.
Summary
In 1.0.1, the Neo4j projection writes
code: Nonefor every:PyCallableand:PyClassnode. Schema v2 removed thecodefield fromPyCallable/PyClass(source now lives once onPyModule.source, sliced by each node'sSpan), butneo4j/project.pystill reads the old field:Since the pydantic models neither define
codenor allow extras, thegetattrfallback always fires.Repro (1.0.1)
(
tinyproj/pkg/mod.py= a two-callable module; both emitcode: Nonewhile the in-memory models carry correctspan.bytesandmodule.source.)Impact
neo4j/schema.pystill declares thecodeproperty and builds the fulltext indexpy_code_ftsoverc.code/c.docstring— the index is now over nulls, so code search in the graph is dead.PyNeo4jBackend.get_method_bodies,RETURN c.code AS code) returns nothing, breaking local ↔ Neo4j backend parity (the in-process backend recovers source by slicingmodule.sourcewithspan.bytes).Suggested fix
Derive the property at projection time the same way
schema.l1_bodyhandles spans — slice the module source by the callable's byte span:(needs the owning module's
sourcein scope at the_project_class/ callable projection sites;byte_offsets/Span.bytesare already populated at L1+).Alternatively, if
codeis intentionally dropped from the graph, remove the property fromneo4j/schema.pyand thepy_code_ftsindex and bumpSCHEMA_VERSION— consumers fail fast on the version stamp either way. Keeping the declared property but always writing null is the one option that breaks consumers silently.Found while bumping python-sdk to
codeanalyzer-python==1.0.1for cldk 2.0.0-rc.1.