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)
fromcodeanalyzer.coreimportCodeanalyzerfromcodeanalyzer.optionsimportAnalysisOptionsfromcodeanalyzer.configimportOutputFormatfromcodeanalyzer.neo4j.projectimportprojectopts=AnalysisOptions(input=Path("tinyproj"), output=None, format=OutputFormat.JSON,
skip_tests=True, no_venv=True)
withCodeanalyzer(opts) asa:
analysis=a.analyze()
rows=project(analysis.application, "tinyproj", {})
fornodeinrows.nodes:
ifnode.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.bytescode=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.