lib.Cmd: Build the command tree lazily
App() construction builds the entire command tree: every load_subcommands() call in a command's __init__() constructs its whole subtree eagerly, so running a single leaf command pays to instantiate every unrelated command object as well (jw-pkg builds about 50 command objects for any invocation). Defer the construction instead. load_subcommands() now records only the module search path and name filter, and the subcommands are materialized on first access to the children or child_classes property. The parser reads children only down the invoked branch on the non-help path, so a simple run instantiates just that path (jw-pkg builds 5-7 objects), while the help and completion path expands every node and leaves rendered help unchanged. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2 Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
3ae8738b36
commit
9f7e1b6cb2
1 changed files with 24 additions and 1 deletions
|
|
@ -25,6 +25,12 @@ class AbstractCmd(abc.ABC):
|
||||||
self.__children: list[Cmd] = []
|
self.__children: list[Cmd] = []
|
||||||
self.__child_classes: list[type[Cmd]] = []
|
self.__child_classes: list[type[Cmd]] = []
|
||||||
self.__parser: ArgumentParser | None = None
|
self.__parser: ArgumentParser | None = None
|
||||||
|
# -- Subcommands registered via load_subcommands() are not built
|
||||||
|
# immediately; the module search path and name filter are stored here
|
||||||
|
# and the subcommands are materialized on first access to `children`
|
||||||
|
# (see __materialize_pending_subcommands()). This keeps a simple run
|
||||||
|
# from instantiating the whole command tree.
|
||||||
|
self.__pending_subcommands: tuple[list[str], str] | None = None
|
||||||
|
|
||||||
def set_parent(self, parent: Any | Cmd) -> None:
|
def set_parent(self, parent: Any | Cmd) -> None:
|
||||||
self.__parent = parent
|
self.__parent = parent
|
||||||
|
|
@ -61,12 +67,25 @@ class AbstractCmd(abc.ABC):
|
||||||
parent = parent.__parent
|
parent = parent.__parent
|
||||||
return self.__app
|
return self.__app
|
||||||
|
|
||||||
|
def __materialize_pending_subcommands(self) -> None:
|
||||||
|
# -- Build the subcommands that load_subcommands() registered
|
||||||
|
# lazily, if any. Called on first access to `children` (and
|
||||||
|
# `child_classes`) so that only the parts of the tree a run actually
|
||||||
|
# descends into are ever instantiated.
|
||||||
|
if self.__pending_subcommands is None:
|
||||||
|
return
|
||||||
|
modules, name_filter = self.__pending_subcommands
|
||||||
|
self.__pending_subcommands = None
|
||||||
|
self.add_subcommands(LoadTypes(modules, type_name_filter = name_filter))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def children(self) -> tuple[Cmd, ...]:
|
def children(self) -> tuple[Cmd, ...]:
|
||||||
|
self.__materialize_pending_subcommands()
|
||||||
return tuple(self.__children)
|
return tuple(self.__children)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def child_classes(self) -> tuple[type[Cmd], ...]:
|
def child_classes(self) -> tuple[type[Cmd], ...]:
|
||||||
|
self.__materialize_pending_subcommands()
|
||||||
return tuple(self.__child_classes)
|
return tuple(self.__child_classes)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -127,7 +146,11 @@ class AbstractCmd(abc.ABC):
|
||||||
modules = [type(self).__module__.replace('Cmd', '').lower()]
|
modules = [type(self).__module__.replace('Cmd', '').lower()]
|
||||||
elif isinstance(modules, str):
|
elif isinstance(modules, str):
|
||||||
modules = [modules]
|
modules = [modules]
|
||||||
self.add_subcommands(LoadTypes(modules, type_name_filter = name_filter))
|
# -- Defer the actual subcommand construction: store the search path
|
||||||
|
# and filter, and materialize on first access to `children`. Building
|
||||||
|
# them here (in __init__) would instantiate the entire tree up front,
|
||||||
|
# even for a run that only descends into a single branch.
|
||||||
|
self.__pending_subcommands = (modules, name_filter)
|
||||||
|
|
||||||
# -- Interface to derived classes
|
# -- Interface to derived classes
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue