Replies: 9 comments 2 replies
-
In my own code, I use an @unique
class HelpPanel(str, Enum):
Creation = "Creation"
Configuration = "Configuration"
Startup = "Startup" I wonder if the API used an I use an |
Beta Was this translation helpful? Give feedback.
-
@mike0sv (and others looking to achieve something similar) this might help for the time being: from click import Context
import typer
from typer.core import TyperGroup
class OrderCommands(TyperGroup):
def list_commands(self, ctx: Context):
"""Return list of commands in the order appear."""
return list(self.commands) # get commands using self.commands
app = typer.Typer(
cls=OrderCommands,
)
... |
Beta Was this translation helpful? Give feedback.
-
@iameskild Thanks for the suggestion. I tried this out, but The panel grouping must be applied after this step? |
Beta Was this translation helpful? Give feedback.
-
As I mentioned, the actual order of panels in help text is the order of keys in some dict inside formatting function. So probably there is no way to influence it unless you monkey-patch that function |
Beta Was this translation helpful? Give feedback.
-
I would suspect, then, that the order that the panels appears should, in later versions of Python or at least CPython (where Dict key order is guaranteed), this would then be influenced directly by the order in which you declare your commands... Or at least, by the order in which the decorators are parsed. You can cheat this in a few ways, but the one I came up with on the spot is as follows: from typer import Typer
from typer.models import CommandFunctionType
from typing import Optional, List, Callable
from collections import defaultdict
class PanelOrderedTyper(Typer):
def init(self, *, rich_help_panel_order: Optional[List[str]] = None, **kwargs: Any):
super().__init__(**kwargs)
self.rich_help_panel_order = rich_help_panel_order
self._rich_help_panels_commands = defaultdict(list)
def command(self, *, rich_help_panel: Optional[str] = None, **kwargs: Any) -> Callable[[CommandFunctionType], CommandFunctionType]:
def decorator(f: CommandFunctionType) -> CommandFunctionType:
if rich_help_panel is not None:
# Defer original decorator's functionality for later
self._rich_help_panels_commands[rich_help_panel].append((f,kwargs))
else:
# But we don't care to defer those that don't have a named panel
return super().command(**kwargs)(f)
return f
return decorator
def __call__(self, *args: Any, **kwargs: Any) -> Any:
for _panel in self.rich_help_panel_order: # All known in order
for _panel_command in self._rich_help_panels_commands[_panel]:
_f, _kwargs = _panel_command
super().command(rich_help_panel=_panel, **_kwargs)(_f)
for _panel in self._rich_help_panels_commands.keys(): # Handle any not in order list
if _panel not in self.rich_help_panel_order:
for _panel_command in self._rich_help_panels_commands[_panel]:
_f, _kwargs = _panel_command
super().command(rich_help_panel=_panel, **_kwargs)(_f)
return super().__call__(*args, **kwargs)
app = PanelOrderedTyper(rich_help_panel_order=['first', 'second'])
[...] (Untested, ymmv, sorry) ... But I sort of suspect that might be more work (and certainly more jank) than implementing it in the core 😅 |
Beta Was this translation helpful? Give feedback.
-
In case of a mix of app = typer.Typer(
cls=OrderCommands,
add_completion=False,
add_help_option=True,
rich_markup_mode="rich",
)
app.command(
name='command-1',
)(some_function)
app.add_typer(
some_typer_app,
name="group-of-commands-1",
) this helped me : class OrderCommands(TyperGroup):
def list_commands(self, ctx: Context):
"""Return list of commands in the order they appear.
See: https://github.com/tiangolo/typer/issues/428#issuecomment-1238866548
"""
order = ["command-1", "group-of-commands-1", "command-2", "group-of-commands-2", "some-other-command"]
ordered_commands = [command for command in order if command in self.commands]
additional_commands = [
command for command in self.commands if command not in ordered_commands
]
return ordered_commands + additional_commands |
Beta Was this translation helpful? Give feedback.
-
[Maintenance note]: moving this to the discussion forum as it's a feature request - we can continue the discussion there! 🙏 |
Beta Was this translation helpful? Give feedback.
-
Here's my solution, it uses the order the enum values are defined to set the order of the groups in help: class Panel(str, Enum):
Creation = "Creation"
Configuration = "Configuration"
Startup = "Startup"
class OrderCommands(TyperGroup):
def list_commands(self, _ctx: Context) -> list[str]:
order = list(Panel)
grouped_commands = {
name: getattr(command, "rich_help_panel")
for name, command in sorted(self.commands.items())
if getattr(command, "rich_help_panel") in order
}
ungrouped_command_names = [
command.name
for command in self.commands.values()
if command.name not in grouped_commands
]
return [
name
for name, command in sorted(
grouped_commands.items(),
key=lambda item: order.index(item[1]),
)
] + sorted(ungrouped_command_names)
app = typer.Typer(
cls=OrderCommands,
…
)
@app.command(name="create", rich_help_panel=Panel.Creation)
def cli_create() -> None:
pass |
Beta Was this translation helpful? Give feedback.
-
I've started using trogon with Typer in one of my projects and wanted to:
In case anyone may find it helpful, of as reference to my future self, this is how I've implemented it: from collections.abc import Iterable
import typer
from trogon.typer import init_tui
from typer.core import TyperGroup
class CustomizedCliCommandsGenerator(TyperGroup):
"""See <https://github.com/fastapi/typer/issues/428#issuecomment-2041956972>."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for command_name, updates_dict in self.command_updates.items():
self.update_command_attributes(current_name_in_cli=command_name, new_attrs=updates_dict)
self.sort_commands()
@classmethod
def with_updates(cls, command_updates: dict, command_order: Iterable = ()):
"""Get instance of the class with added `command_updates` and `command_order` properties."""
class CustomizedCliCommands(cls):
@property
def command_updates(self):
return command_updates
@property
def command_order(self):
return command_order
return CustomizedCliCommands
def sort_commands(self):
"""Sort the command in the `help` session according to `self.command_order`."""
if not self.command_order:
return
commands_in_original_order = self.commands
sorted_cmd_names = list(dict.fromkeys([*self.command_order, *commands_in_original_order]))
self.commands = {
cmd_name: commands_in_original_order[cmd_name]
for cmd_name in sorted_cmd_names
if cmd_name in commands_in_original_order
}
def update_command_attributes(self, current_name_in_cli: str, new_attrs: dict):
"""Update attributes of command named `current_name_in_cli` according to `new_attrs`."""
if current_name_in_cli not in self.commands:
return
new_name_in_cli = new_attrs.pop("name_in_cli", current_name_in_cli)
name_shown_in_help = new_attrs.pop("name_in_help", new_name_in_cli)
new_attrs["name"] = name_shown_in_help
command = self.commands.pop(current_name_in_cli)
for attr_name, new_value in new_attrs.items():
setattr(command, attr_name, new_value)
self.commands[new_name_in_cli] = command With this, you can then do: typer_app = typer.Typer(
cls=CustomizedCliCommandsGenerator.with_updates(
{
"tui": {
"name_in_cli": "ui",
"name_in_help": "ui (DEFAULT)",
"help": "Start the app in text-base user interface (TUI) mode.",
}
},
command_order=["ui"],
),
rich_markup_mode="rich",
)
init_tui(typer_app) and now the former |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
First Check
Commit to Help
Example Code
Description
New typer version introduced
rich_help_panel
option for commands and groups, which groups together different commands for multi-command help message. However there is no way to set the order of different panels (it's semi-random depending on the order ofpanel_to_commands
dict insiderich_format_text
function). It would be nice to have an option to configure this orderWanted Solution
see code example above
Wanted Code
Alternatives
No response
Operating System
Other
Operating System Details
No response
Typer Version
0.6.1
Python Version
3.9.4
Additional Context
No response
Beta Was this translation helpful? Give feedback.
All reactions