@@ -430,27 +430,35 @@ def _get_extension_suppressions(mod_loaders):
430430 # Set fallback=False to turn off command index in case of regression
431431 use_command_index = self .cli_ctx .config .getboolean ('core' , 'use_command_index' , fallback = True )
432432
433- # Fast path for help requests - check if we can use cached help index to skip module loading
433+ # Fast path for top-level help only (az --help or az with no args)
434+ # Only use cache for root level, not for modules/commands
434435 if use_command_index and args and ('--help' in args or '-h' in args or args [- 1 ] == 'help' ):
435- # Parse the command path from args (e.g., ['vm', '--help'] -> 'vm' )
436- command_path_parts = []
436+ # Check if this is top-level help request (no command path )
437+ has_command_path = False
437438 for arg in args :
438439 if arg in ('--help' , '-h' , 'help' ):
439440 break
440441 if not arg .startswith ('-' ):
441- command_path_parts .append (arg )
442-
443- command_path = ' ' .join (command_path_parts ) if command_path_parts else 'root'
442+ has_command_path = True
443+ break
444444
445+ # Only use cache for top-level help (no command arguments before --help)
446+ if not has_command_path :
447+ command_index = CommandIndex (self .cli_ctx )
448+ help_index = command_index .get_help_index ()
449+ if help_index and 'root' in help_index :
450+ logger .debug ("Using cached help index for root, skipping module loading" )
451+ self ._display_cached_help (help_index ['root' ], 'root' )
452+ sys .exit (0 )
453+ # Fast path for top-level with no args (az with no arguments)
454+ elif use_command_index and not args :
445455 command_index = CommandIndex (self .cli_ctx )
446456 help_index = command_index .get_help_index ()
447- if help_index and command_path in help_index :
448- logger .debug ("Using cached help index for '%s', skipping module loading" , command_path )
449- # Display help directly from cached data without loading modules
450- self ._display_cached_help (help_index [command_path ], command_path )
451- # Raise SystemExit to stop execution (similar to how --help normally works)
457+ if help_index and 'root' in help_index :
458+ logger .debug ("Using cached help index for root, skipping module loading" )
459+ self ._display_cached_help (help_index ['root' ], 'root' )
452460 sys .exit (0 )
453- # Fast path for top-level with no args (az with no arguments)
461+
454462 if use_command_index :
455463 command_index = CommandIndex (self .cli_ctx )
456464 index_result = command_index .get (args )
@@ -629,10 +637,8 @@ def _get_padding_len(max_len, name, tags):
629637 show_updates_available (new_line_after = True )
630638
631639 def _cache_help_index (self , command_index ):
632- """Cache help summaries for all commands/groups recursively using parallel processing ."""
640+ """Cache help summary for top-level (root) help only ."""
633641 try :
634- import concurrent .futures
635- from concurrent .futures import ThreadPoolExecutor
636642 from azure .cli .core .parser import AzCliCommandParser
637643 from azure .cli .core ._help import CliGroupHelpFile
638644
@@ -651,95 +657,38 @@ def _get_tags(item):
651657 tags .append (str (item .experimental_info .tag ))
652658 return ' ' .join (tags )
653659
654- # Function to cache help for a single group
655- def _cache_single_group_help (group_path , subparser ):
656- """Cache help for a single group and return its data along with subgroups to process."""
657- try :
658- help_file = CliGroupHelpFile (self .cli_ctx .invocation .help , group_path , subparser )
659- help_file .load (subparser )
660-
661- groups = {}
662- commands = {}
663- subgroup_names = []
664-
665- for child in help_file .children :
666- if hasattr (child , 'name' ) and hasattr (child , 'short_summary' ):
667- # Extract just the last part of the name (after the group path)
668- child_name = child .name
669- if group_path and child_name .startswith (group_path + ' ' ):
670- child_name = child_name [len (group_path ) + 1 :]
671- elif not group_path and ' ' in child_name :
672- # Skip nested items at root level
673- continue
674-
675- tags = _get_tags (child )
676- item_data = {
677- 'summary' : child .short_summary ,
678- 'tags' : tags
679- }
680-
681- if child .type == 'group' :
682- groups [child_name ] = item_data
683- # Build full path for recursion
684- if group_path :
685- full_subgroup_name = f"{ group_path } { child_name } "
686- else :
687- full_subgroup_name = child_name
688- subgroup_names .append (full_subgroup_name )
689- else :
690- commands [child_name ] = item_data
691-
692- level_key = group_path if group_path else 'root'
693- level_data = None
694- if groups or commands :
695- level_data = {'groups' : groups , 'commands' : commands }
696-
697- return level_key , level_data , subgroup_names
660+ # Only cache root level help
661+ subparser = parser .subparsers .get (tuple ())
662+ if subparser :
663+ help_file = CliGroupHelpFile (self .cli_ctx .invocation .help , '' , subparser )
664+ help_file .load (subparser )
698665
699- except Exception as ex : # pylint: disable=broad-except
700- logger .debug ("Failed to cache help for '%s': %s" , group_path , ex )
701- return None , None , []
702-
703- # Build help index using BFS with parallel processing at each level
704- help_index_data = {}
705- to_process = [('' , parser .subparsers .get (tuple ()))] # (group_path, subparser) tuples
706-
707- while to_process :
708- # Process current level in parallel
709- with ThreadPoolExecutor (max_workers = 4 ) as executor :
710- futures = {}
711- for group_path , subparser in to_process :
712- if subparser :
713- future = executor .submit (_cache_single_group_help , group_path , subparser )
714- futures [future ] = group_path
715-
716- next_level = []
717- for future in concurrent .futures .as_completed (futures ):
718- try :
719- level_key , level_data , subgroup_names = future .result (timeout = 10 )
720- if level_data :
721- help_index_data [level_key ] = level_data
722-
723- # Queue subgroups for next level
724- for subgroup_name in subgroup_names :
725- subgroup_tuple = tuple (subgroup_name .split ())
726- sub_subparser = parser .subparsers .get (subgroup_tuple )
727- if sub_subparser :
728- next_level .append ((subgroup_name , sub_subparser ))
666+ groups = {}
667+ commands = {}
668+
669+ for child in help_file .children :
670+ if hasattr (child , 'name' ) and hasattr (child , 'short_summary' ):
671+ # Only include top-level items (no spaces in name)
672+ child_name = child .name
673+ if ' ' in child_name :
674+ continue
729675
730- except concurrent .futures .TimeoutError :
731- group_path = futures [future ]
732- logger .debug ("Help caching timeout for '%s'" , group_path )
733- except Exception as ex : # pylint: disable=broad-except
734- group_path = futures [future ]
735- logger .debug ("Failed to cache help for '%s': %s" , group_path , ex )
736-
737- to_process = next_level
738-
739- # Store the complete help index in one operation
740- if help_index_data :
741- command_index .INDEX [command_index ._HELP_INDEX ] = help_index_data
742- logger .debug ("Cached help for %d command levels" , len (help_index_data ))
676+ tags = _get_tags (child )
677+ item_data = {
678+ 'summary' : child .short_summary ,
679+ 'tags' : tags
680+ }
681+
682+ if child .type == 'group' :
683+ groups [child_name ] = item_data
684+ else :
685+ commands [child_name ] = item_data
686+
687+ # Store only root level help
688+ if groups or commands :
689+ help_index_data = {'root' : {'groups' : groups , 'commands' : commands }}
690+ command_index .INDEX [command_index ._HELP_INDEX ] = help_index_data
691+ logger .debug ("Cached top-level help with %d groups and %d commands" , len (groups ), len (commands ))
743692
744693 except Exception as ex : # pylint: disable=broad-except
745694 logger .debug ("Failed to cache help data: %s" , ex )
0 commit comments