-
Notifications
You must be signed in to change notification settings - Fork 930
⚡ perf(cli): faster CLI startup via lazy imports #3535
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
80350ad
0972be6
5abd3ee
0c56a15
8575531
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -15,31 +15,25 @@ | |||||||||||||||||||||||||||||||
| from typing import Any | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from jsonargparse import ActionConfigFile, ArgumentParser, Namespace | ||||||||||||||||||||||||||||||||
| from jsonargparse._actions import _ActionSubCommands | ||||||||||||||||||||||||||||||||
| from rich import traceback | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| from jsonargparse._actions import _ActionSubCommands | ||||||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||||||
| from jsonargparse._subcommands import ActionSubCommands as _ActionSubCommands | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from anomalib import __version__ | ||||||||||||||||||||||||||||||||
| from anomalib.cli.pipelines import PIPELINE_REGISTRY, pipeline_subcommands, run_pipeline | ||||||||||||||||||||||||||||||||
| from anomalib.cli.utils.help_formatter import CustomHelpFormatter, get_short_docstring | ||||||||||||||||||||||||||||||||
| from anomalib.cli.utils.openvino import add_openvino_export_arguments | ||||||||||||||||||||||||||||||||
| from anomalib.loggers import configure_logger | ||||||||||||||||||||||||||||||||
| from anomalib.cli.utils.help_formatter import CustomHelpFormatter | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| traceback.install() | ||||||||||||||||||||||||||||||||
| logger = logging.getLogger("anomalib.cli") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _LIGHTNING_AVAILABLE = True | ||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| from lightning.pytorch import Trainer | ||||||||||||||||||||||||||||||||
| from torch.utils.data import DataLoader, Dataset | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from anomalib.data import AnomalibDataModule | ||||||||||||||||||||||||||||||||
| from anomalib.engine import Engine | ||||||||||||||||||||||||||||||||
| from anomalib.models import AnomalibModule | ||||||||||||||||||||||||||||||||
| from anomalib.utils.config import update_config | ||||||||||||||||||||||||||||||||
| def _check_lightning_available() -> bool: | ||||||||||||||||||||||||||||||||
| """Check if Lightning and its dependencies are installed (without importing them).""" | ||||||||||||||||||||||||||||||||
| import importlib.util | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| except ImportError as error: | ||||||||||||||||||||||||||||||||
| _LIGHTNING_AVAILABLE = False | ||||||||||||||||||||||||||||||||
| logger.warning(f"Import failed: {error}. Please install the required dependencies.") | ||||||||||||||||||||||||||||||||
| return importlib.util.find_spec("lightning") is not None and importlib.util.find_spec("torch") is not None | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| class AnomalibCLI: | ||||||||||||||||||||||||||||||||
|
|
@@ -69,19 +63,48 @@ class AnomalibCLI: | |||||||||||||||||||||||||||||||
| provided via both files and command line arguments simultaneously. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _TRAINER_SUBCOMMAND_DESCRIPTIONS: dict[str, str] = { | ||||||||||||||||||||||||||||||||
| "fit": "Runs the full optimization routine.", | ||||||||||||||||||||||||||||||||
| "validate": "Perform one evaluation epoch over the validation set.", | ||||||||||||||||||||||||||||||||
| "test": "Perform one evaluation epoch over the test set.", | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def __init__(self, args: Sequence[str] | None = None, run: bool = True) -> None: | ||||||||||||||||||||||||||||||||
| self._lightning_available = _check_lightning_available() | ||||||||||||||||||||||||||||||||
| self._selected_subcommand = self._sniff_subcommand(args) | ||||||||||||||||||||||||||||||||
| self.parser = self.init_parser() | ||||||||||||||||||||||||||||||||
| self.subcommand_parsers: dict[str, ArgumentParser] = {} | ||||||||||||||||||||||||||||||||
| self.subcommand_method_arguments: dict[str, list[str]] = {} | ||||||||||||||||||||||||||||||||
| self.add_subcommands() | ||||||||||||||||||||||||||||||||
| self.config = self.parser.parse_args(args=args) | ||||||||||||||||||||||||||||||||
| self.subcommand = self.config["subcommand"] | ||||||||||||||||||||||||||||||||
| if _LIGHTNING_AVAILABLE: | ||||||||||||||||||||||||||||||||
| if self._lightning_available: | ||||||||||||||||||||||||||||||||
| self.before_instantiate_classes() | ||||||||||||||||||||||||||||||||
| self.instantiate_classes() | ||||||||||||||||||||||||||||||||
| if run: | ||||||||||||||||||||||||||||||||
| self._run_subcommand() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Flags whose next token is a value, not a subcommand. | ||||||||||||||||||||||||||||||||
| _OPTIONS_WITH_VALUE = frozenset({"-c", "--config"}) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @staticmethod | ||||||||||||||||||||||||||||||||
| def _sniff_subcommand(args: Sequence[str] | None) -> str | None: | ||||||||||||||||||||||||||||||||
| """Peek at args to identify the subcommand without full parsing.""" | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| tokens = list(args) if args is not None else sys.argv[1:] | ||||||||||||||||||||||||||||||||
| skip_next = False | ||||||||||||||||||||||||||||||||
| for token in tokens: | ||||||||||||||||||||||||||||||||
| if skip_next: | ||||||||||||||||||||||||||||||||
| skip_next = False | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| if token in AnomalibCLI._OPTIONS_WITH_VALUE: | ||||||||||||||||||||||||||||||||
| skip_next = True | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| if not token.startswith("-"): | ||||||||||||||||||||||||||||||||
| return token | ||||||||||||||||||||||||||||||||
|
Comment on lines
+97
to
+105
|
||||||||||||||||||||||||||||||||
| for token in tokens: | |
| if not token.startswith("-"): | |
| return token | |
| index = 0 | |
| while index < len(tokens): | |
| token = tokens[index] | |
| if token in {"-c", "--config"}: | |
| index += 2 | |
| continue | |
| if token.startswith("--config="): | |
| index += 1 | |
| continue | |
| if not token.startswith("-"): | |
| return token | |
| index += 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New
_sniff_subcommand()+ conditional argument registration changes how parsers are constructed and is now critical to CLI correctness (e.g.-c/--configbefore/after the subcommand,--helpbehavior, and ensuring only the selected subcommand gets heavy argument construction). There are CLI integration tests, but there doesn’t appear to be focused coverage for these parsing edge cases; adding a small unit test matrix around_sniff_subcommandand subcommand parser construction would help prevent regressions.