Skip to content

Commit 478fac6

Browse files
refactor: remove module-level help caching
1 parent 8f97308 commit 478fac6

1 file changed

Lines changed: 51 additions & 102 deletions

File tree

src/azure-cli-core/azure/cli/core/__init__.py

Lines changed: 51 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)