5050from codeanalyzer .schema .py_schema import PyCallsite
5151
5252
53- def project (app : PyApplication , app_name : str ) -> GraphRows :
53+ def project (app : PyApplication , app_name : str , sig_to_id : dict ) -> GraphRows :
5454 b = RowBuilder ()
5555
5656 app_ref = b .node (
5757 ["PyApplication" ], "name" , app_name , {"schema_version" : SCHEMA_VERSION }
5858 )
5959
6060 for file_key , mod in app .symbol_table .items ():
61- mod_ref = b .node (
62- ["PyModule" ], "file_key" , file_key , _module_props (mod , file_key )
63- )
61+ mod_ref = b .node (["PyModule" ], "id" , mod .id , _module_props (mod , file_key ))
6462 b .edge ("PY_HAS_MODULE" , app_ref , mod_ref )
6563 _project_module_body (b , file_key , mod_ref , mod )
6664
6765 # The aggregated :PY_CALLS twin. Endpoints listed in app.external_symbols become
68- # :PyExternal ghost nodes; the rest are declared :PySymbol nodes already emitted.
66+ # :PyExternal ghost nodes; the rest are declared :PySymbol nodes already emitted
67+ # (keyed by their can:// id, resolved through ``sig_to_id``).
6968 externals = app .external_symbols or {}
7069 for e in app .call_graph :
71- src = _call_endpoint (b , e .source , externals )
72- tgt = _call_endpoint (b , e .target , externals )
70+ src = _call_endpoint (b , e .source , externals , sig_to_id )
71+ tgt = _call_endpoint (b , e .target , externals , sig_to_id )
7372 b .edge (
7473 "PY_CALLS" , src , tgt , _call_edge_props (e .weight , list (e .provenance or []))
7574 )
@@ -172,21 +171,27 @@ def _project_program_graphs(b: RowBuilder, app: PyApplication) -> None:
172171 )
173172
174173
175- def _sym (signature : str ) -> NodeRef :
176- return NodeRef ("PySymbol" , "signature " , signature )
174+ def _sym (can_id : str ) -> NodeRef :
175+ return NodeRef ("PySymbol" , "id " , can_id )
177176
178177
179- def _call_endpoint (b : RowBuilder , signature : str , externals : dict ) -> NodeRef :
180- """A call-graph endpoint: a declared callable already emitted, or an external
181- symbol (imported library / builtin member) materialized as a :PyExternal ghost.
178+ def _call_endpoint (
179+ b : RowBuilder , signature : str , externals : dict , sig_to_id : dict
180+ ) -> NodeRef :
181+ """A call-graph endpoint: a declared callable already emitted (keyed by its
182+ canonical ``can://`` id, resolved through ``sig_to_id``), or an external symbol
183+ (imported library / builtin member) materialized as a :PyExternal ghost.
182184
183185 Classification is authoritative -- it comes from ``app.external_symbols``, not a
184186 "present in the graph" heuristic -- so an imported module name (which exists only
185- as a :PyPackage) can never shadow the call target. A small fallback still
186- materializes an external for any endpoint that is neither declared nor listed."""
187+ as a :PyPackage) can never shadow the call target. A declared endpoint resolves to
188+ its ``can://`` id; anything neither declared nor listed falls back to a
189+ signature-keyed :PyExternal ghost rather than raising."""
187190 ext = externals .get (signature )
188- if ext is None and b .has_key ("PySymbol" , signature ):
189- return _sym (signature )
191+ if ext is None :
192+ can_id = sig_to_id .get (signature )
193+ if can_id is not None :
194+ return _sym (can_id )
190195 name = (
191196 ext .name
192197 if ext is not None
@@ -254,7 +259,7 @@ def _project_class(
254259 b : RowBuilder , file_key : str , parent : NodeRef , parent_rel : str , cl : PyClass
255260) -> None :
256261 ref = b .node (
257- ["PySymbol" , "PyClass" ], "signature " , cl .signature , _class_props (cl , file_key )
262+ ["PySymbol" , "PyClass" ], "id " , cl .id , _class_props (cl , file_key )
258263 )
259264 b .edge (parent_rel , parent , ref )
260265
@@ -274,8 +279,8 @@ def _project_callable(
274279) -> None :
275280 ref = b .node (
276281 ["PySymbol" , "PyCallable" ],
277- "signature " ,
278- c .signature ,
282+ "id " ,
283+ c .id ,
279284 _callable_props (c , file_key ),
280285 )
281286 b .edge (owner_rel , owner , ref )
@@ -334,6 +339,8 @@ def _project_decorator(b: RowBuilder, on: NodeRef, decorator: str) -> None:
334339def _module_props (mod : PyModule , file_key : str ) -> Props :
335340 return prune (
336341 {
342+ "id" : mod .id ,
343+ "file_key" : file_key ,
337344 "module_name" : mod .module_name ,
338345 "content_hash" : mod .content_hash ,
339346 "last_modified" : mod .last_modified ,
@@ -346,8 +353,10 @@ def _module_props(mod: PyModule, file_key: str) -> Props:
346353def _class_props (cl : PyClass , file_key : str ) -> Props :
347354 return prune (
348355 {
356+ "id" : cl .id ,
357+ "signature" : cl .signature ,
349358 "name" : cl .name ,
350- "code" : cl . code ,
359+ "code" : getattr ( cl , " code" , None ) ,
351360 "base_classes" : list (cl .base_classes or []),
352361 "docstring" : _docstring_of (cl .comments ),
353362 "start_line" : cl .start_line ,
@@ -360,11 +369,13 @@ def _class_props(cl: PyClass, file_key: str) -> Props:
360369def _callable_props (c : PyCallable , file_key : str ) -> Props :
361370 return prune (
362371 {
372+ "id" : c .id ,
373+ "signature" : c .signature ,
363374 "name" : c .name ,
364375 "path" : c .path ,
365376 "return_type" : c .return_type ,
366377 "cyclomatic_complexity" : c .cyclomatic_complexity ,
367- "code" : c . code ,
378+ "code" : getattr ( c , " code" , None ) ,
368379 "code_start_line" : c .code_start_line ,
369380 "start_line" : c .start_line ,
370381 "end_line" : c .end_line ,
0 commit comments