11from __future__ import annotations
22
3+ import collections
34import dataclasses
45import graphlib
56import json
@@ -349,7 +350,7 @@ def remove_dependency(
349350 """Remove a dependency node and any orphaned descendants from the graph.
350351
351352 Removes the node and all edges pointing to it. Child nodes that have
352- no remaining parents after removal are recursively removed as well.
353+ no remaining parents after removal are iteratively removed as well.
353354
354355 Args:
355356 req_name: Canonical name of the package
@@ -360,36 +361,50 @@ def remove_dependency(
360361 logger .debug (f"Cannot remove { key } - not in graph" )
361362 return
362363
363- logger . debug ( f"Removing failed dependency { key } from graph" )
364+ queue : collections . deque [ str ] = collections . deque ([ key ] )
364365
365- deleted_node = self .nodes [key ]
366-
367- children = [edge .destination_node for edge in deleted_node .children ]
368-
369- # Clean up back-references (parents) in nodes that were children of the removed node
370- for child_edge in deleted_node .children :
371- child_node = child_edge .destination_node
372- filtered_parents = [
373- edge for edge in child_node .parents if edge .destination_node .key != key
374- ]
375- child_node .parents .clear ()
376- child_node .parents .extend (filtered_parents )
377-
378- # Remove the node itself
379- del self .nodes [key ]
380-
381- # Remove forward edges from any node whose children pointed to the removed node
382- for node in self .nodes .values ():
383- filtered_children = [
384- edge for edge in node .children if edge .destination_node .key != key
385- ]
386- node .children .clear ()
387- node .children .extend (filtered_children )
366+ # BFS over orphaned descendants
367+ while queue :
368+ key = queue .popleft ()
369+ # This is a defensive check, should not happen in normal operation
370+ if key not in self .nodes :
371+ logger .debug (f"Cannot remove { key } - not in graph" )
372+ continue
388373
389- # Recursively remove children that have no remaining parents
390- for child in children :
391- if child .key != ROOT and child .key in self .nodes and not child .parents :
392- self .remove_dependency (child .canonicalized_name , child .version )
374+ logger .debug (f"Removing failed dependency { key } from graph" )
375+
376+ deleted_node = self .nodes [key ]
377+
378+ # Remove references to this node from its direct children
379+ children = []
380+ for child_edge in deleted_node .children :
381+ child_node = child_edge .destination_node
382+ filtered_parents = [
383+ edge
384+ for edge in child_node .parents
385+ if edge .destination_node .key != key
386+ ]
387+ child_node .parents .clear ()
388+ child_node .parents .extend (filtered_parents )
389+ children .append (child_node )
390+
391+ # Remove references to this node from its direct parents
392+ for parent_edge in deleted_node .parents :
393+ parent_node = parent_edge .destination_node
394+ filtered_children = [
395+ edge
396+ for edge in parent_node .children
397+ if edge .destination_node .key != key
398+ ]
399+ parent_node .children .clear ()
400+ parent_node .children .extend (filtered_children )
401+
402+ del self .nodes [key ]
403+
404+ # Enqueue children that have become orphans
405+ for child in children :
406+ if child .key != ROOT and child .key in self .nodes and not child .parents :
407+ queue .append (child .key )
393408
394409 def get_dependency_edges (
395410 self , match_dep_types : list [RequirementType ] | None = None
0 commit comments