@@ -57,15 +57,18 @@ def project(app: PyApplication, app_name: str, sig_to_id: dict) -> GraphRows:
5757 ["PyApplication" ], "name" , app_name , {"schema_version" : SCHEMA_VERSION }
5858 )
5959
60+ # Endpoints listed in app.external_symbols become :PyExternal ghost nodes; the
61+ # rest are declared :PySymbol nodes emitted here (keyed by their can:// id,
62+ # resolved through ``sig_to_id``). Both the module-body projection (for
63+ # PY_EXTENDS / PY_RESOLVES_TO) and the PY_CALLS twin below share this split.
64+ externals = app .external_symbols or {}
65+
6066 for file_key , mod in app .symbol_table .items ():
6167 mod_ref = b .node (["PyModule" ], "id" , mod .id , _module_props (mod , file_key ))
6268 b .edge ("PY_HAS_MODULE" , app_ref , mod_ref )
63- _project_module_body (b , file_key , mod_ref , mod )
69+ _project_module_body (b , file_key , mod_ref , mod , externals , sig_to_id )
6470
65- # The aggregated :PY_CALLS twin. Endpoints listed in app.external_symbols become
66- # :PyExternal ghost nodes; the rest are declared :PySymbol nodes already emitted
67- # (keyed by their can:// id, resolved through ``sig_to_id``).
68- externals = app .external_symbols or {}
71+ # The aggregated :PY_CALLS twin.
6972 for e in app .call_graph :
7073 src = _call_endpoint (b , e .source , externals , sig_to_id )
7174 tgt = _call_endpoint (b , e .target , externals , sig_to_id )
@@ -175,6 +178,16 @@ def _sym(can_id: str) -> NodeRef:
175178 return NodeRef ("PySymbol" , "id" , can_id )
176179
177180
181+ def _symbol_ref (signature : str , externals : dict , sig_to_id : dict ) -> NodeRef :
182+ """Resolve a call/inheritance target to the NodeRef under which it was (or
183+ will be) emitted: a declared symbol by its can:// id, otherwise a
184+ signature-keyed :PySymbol (external ghost)."""
185+ can_id = sig_to_id .get (signature )
186+ if can_id is not None :
187+ return NodeRef ("PySymbol" , "id" , can_id )
188+ return NodeRef ("PySymbol" , "signature" , signature )
189+
190+
178191def _call_endpoint (
179192 b : RowBuilder , signature : str , externals : dict , sig_to_id : dict
180193) -> NodeRef :
@@ -212,12 +225,13 @@ def _call_endpoint(
212225
213226
214227def _project_module_body (
215- b : RowBuilder , file_key : str , mod_ref : NodeRef , mod : PyModule
228+ b : RowBuilder , file_key : str , mod_ref : NodeRef , mod : PyModule ,
229+ externals : dict , sig_to_id : dict ,
216230) -> None :
217231 for fn in (mod .functions or {}).values ():
218- _project_callable (b , file_key , mod_ref , "PY_DECLARES" , fn )
232+ _project_callable (b , file_key , mod_ref , "PY_DECLARES" , fn , externals , sig_to_id )
219233 for cl in (mod .classes or {}).values ():
220- _project_class (b , file_key , mod_ref , "PY_DECLARES" , cl )
234+ _project_class (b , file_key , mod_ref , "PY_DECLARES" , cl , externals , sig_to_id )
221235 for v in mod .variables or []:
222236 _project_variable (b , file_key , mod_ref , file_key , v )
223237 _project_imports (b , mod_ref , mod )
@@ -256,26 +270,29 @@ def _project_imports(b: RowBuilder, mod_ref: NodeRef, mod: PyModule) -> None:
256270
257271
258272def _project_class (
259- b : RowBuilder , file_key : str , parent : NodeRef , parent_rel : str , cl : PyClass
273+ b : RowBuilder , file_key : str , parent : NodeRef , parent_rel : str , cl : PyClass ,
274+ externals : dict , sig_to_id : dict ,
260275) -> None :
261276 ref = b .node (
262277 ["PySymbol" , "PyClass" ], "id" , cl .id , _class_props (cl , file_key )
263278 )
264279 b .edge (parent_rel , parent , ref )
265280
266281 for base in cl .base_classes or []:
267- b .edge_to_symbol ("PY_EXTENDS" , ref , base )
282+ if base :
283+ b .edge_to_symbol ("PY_EXTENDS" , ref , _symbol_ref (base , externals , sig_to_id ))
268284
269285 for m in (cl .methods or {}).values ():
270- _project_callable (b , file_key , ref , "PY_HAS_METHOD" , m )
286+ _project_callable (b , file_key , ref , "PY_HAS_METHOD" , m , externals , sig_to_id )
271287 for a in (cl .attributes or {}).values ():
272288 _project_attribute (b , file_key , ref , cl .signature , a )
273289 for ic in (cl .inner_classes or {}).values ():
274- _project_class (b , file_key , ref , "PY_DECLARES" , ic )
290+ _project_class (b , file_key , ref , "PY_DECLARES" , ic , externals , sig_to_id )
275291
276292
277293def _project_callable (
278- b : RowBuilder , file_key : str , owner : NodeRef , owner_rel : str , c : PyCallable
294+ b : RowBuilder , file_key : str , owner : NodeRef , owner_rel : str , c : PyCallable ,
295+ externals : dict , sig_to_id : dict ,
279296) -> None :
280297 ref = b .node (
281298 ["PySymbol" , "PyCallable" ],
@@ -296,14 +313,17 @@ def _project_callable(
296313 cs = b .node (["PyCallSite" ], "id" , cs_id , _call_site_props (s , file_key ))
297314 b .edge ("PY_HAS_CALLSITE" , ref , cs )
298315 if s .callee_signature :
299- b .edge_to_symbol ("PY_RESOLVES_TO" , cs , s .callee_signature )
316+ b .edge_to_symbol (
317+ "PY_RESOLVES_TO" , cs ,
318+ _symbol_ref (s .callee_signature , externals , sig_to_id ),
319+ )
300320
301321 for v in c .local_variables or []:
302322 _project_variable (b , file_key , ref , c .signature , v )
303323 for ic in (c .inner_callables or {}).values ():
304- _project_callable (b , file_key , ref , "PY_DECLARES" , ic )
324+ _project_callable (b , file_key , ref , "PY_DECLARES" , ic , externals , sig_to_id )
305325 for cl in (c .inner_classes or {}).values ():
306- _project_class (b , file_key , ref , "PY_DECLARES" , cl )
326+ _project_class (b , file_key , ref , "PY_DECLARES" , cl , externals , sig_to_id )
307327
308328
309329def _project_attribute (
0 commit comments