Closed max-block closed 4 years ago
Hey, as Typer doesn't modify the function, and only "registers" it, you could do this:
import typer
app = typer.Typer()
@app.command("delete")
@app.command("uninstall")
@app.command("d")
def delete(name: str):
typer.echo(f"Deleting {name}")
if __name__ == "__main__":
app()
Manually adding aliases does not really scale well. Click has some code for implementing git-like behavior at https://click.palletsprojects.com/en/7.x/advanced/ but the tricky bit is that I have no idea on how to adapt this code to work for typer, especially as I do generate commands dynamically at https://github.com/pycontribs/mk/blob/main/src/mk/__main__.py#L87-L91
Hey, as Typer doesn't modify the function, and only "registers" it, you could do this:
import typer app = typer.Typer() @app.command("delete") @app.command("uninstall") @app.command("d") def delete(name: str): typer.echo(f"Deleting {name}") if __name__ == "__main__": app()
@tiangolo
This doesn't work as intended if the user invokes the app with --help
:
...
Commands:
a Add a new to-do with a DESCRIPTION.
add Add a new to-do with a DESCRIPTION.
What I want is:
...
Commands:
add, a Add a new to-do with a DESCRIPTION.
How do I do this with typer? Thank you.
@tddschn I solved this by hiding the subsequent option, works pretty well.
@app.command(no_args_is_help=True, help='Open a log stream to a service. (also "logs")')
@app.command('logs', hidden=True)
def log(
service: Service = typer.Argument(..., help='The service whose logs to stream.'),
):
docker_execute(['logs', '-f', service])
I wonder if we could use https://click.palletsprojects.com/en/8.1.x/advanced/ in with typer as being able to just catch an unknown command and redirect it to the correct one is far more flexible and could also allow us to address typos by avoiding pre-generating all possible values in advance, which is quite ugly.
I have also been looking for a solution to this, tried a few different methods, none of them worked nicely. The best one is just adding hidden commands:
@app.command()
@app.command(name="f", hidden=True)
def foobar(hello: str):
"Foobar command"
print("foobar with hello: ", hello)
but then we don't get a list of aliases, and you cant run something like alias f foobar
If someone more experienced than me would give a pointer for how a function that does alias f foobar
that would be lovely.
I always use bellow for alias (exactly, command is a decorator, so it is a caller too). Furthermore, you can do bellow before app() as a statement.
# alias, it also use the decorator's logic and work well
app.command(name="byebye", help="alias of goodbye")(goodbye)
@FFengIll thats neat but its only available on build right?
what i want is something like
@app.command()
def alias(ctx: Context, name: str, command: str):
aliases = ctx.obj.get('aliases', None)
command = ctx.parent.command.get_command(ctx, command)
if aliases and command:
aliases[name] = command
app.command(name=name)(command) # <-- this doesn't work because i cant get the global app obj? i also tried adding it to the context and running ctx.obj.app.... but it didn't work either
@app.command()
def unalias(....
that i can use from within a session..
You can support this with a tweak to the Group class:
class AliasGroup(typer.core.TyperGroup):
_CMD_SPLIT_P = re.compile(r", ?")
def get_command(self, ctx, cmd_name):
cmd_name = self._group_cmd_name(cmd_name)
return super().get_command(ctx, cmd_name)
def _group_cmd_name(self, default_name):
for cmd in self.commands.values():
if cmd.name and default_name in self._CMD_SPLIT_P.split(cmd.name):
return cmd.name
return default_name
Usage:
app = typer.Typer(cls=AliasGroup)
@app.command("foo, f")
def foo():
"""Print a message and exit."""
print("Works as command 'foo' or its alias 'f'")
@app.callback()
def main():
pass
app()
See the full Gist.
One could imagine more explicit support of this with a mod to the upstream group class and a new aliases
arg to the command decorator.
app = Typer() # Uses TyperGroup, which is modified to look for an 'aliases' attr on commands
@app.command("foo", aliases=["f"])
def foo():
pass
Thank you, @gar1t ! This works great!
+1 for command aliases=[]
It would be nice if it could include a custom parsing separator, and display separator override. E.g. I like to use |
for command separation in my help text:
b | build Build the dev environment
c | cmd Run a command inside the dev environment
d | dev Run the dev environment
m | manage Run a manage.py function
s | shell Enter into a python shell inside the dev environment
t | tui Run an interactive TUI
@gar1t A slightly more robust solution that allows for multiple (or sloppy) delimiters:
class AliasGroup(TyperGroup):
_CMD_SPLIT_P = r'[,| ?\/]' # add other delimiters inside the [ ]
...
def _group_cmd_name(self, default_name):
for cmd in self.commands.values():
if cmd.name and default_name in re.split(self._CMD_SPLIT_P, cmd.name):
return cmd.name
return default_name
@mrharpo Not really a typo, but a feeling of improvement for the regex (or maybe I didn't really understand the \/
part) could be something like:
r" ?[,|] ?"
My full code is like:
class AliasGroup(TyperGroup):
_CMD_SPLIT_P = re.compile(r" ?[,|] ?")
def get_command(self, ctx, cmd_name):
cmd_name = self._group_cmd_name(cmd_name)
return super().get_command(ctx, cmd_name)
def _group_cmd_name(self, default_name):
for cmd in self.commands.values():
name = cmd.name
if name and default_name in self._CMD_SPLIT_P.split(name):
return name
return default_name
app = typer.Typer(cls=AliasGroup)
@app.command("a | action | xyz")
def do_something():
"""
Some description here.
"""
...
I agree the help text is certainly improved 🎉 :
Usage: cmd [OPTIONS] COMMAND [ARGS]...
â•â”€ Options ──────────────────────────────────────────────────────────────────────────╮
│ --install-completion Install completion for the current shell. │
│ --show-completion Show completion for the current shell, to copy it or │
│ customize the installation. │
│ --help Show this message and exit. │
╰────────────────────────────────────────────────────────────────────────────────────╯
â•â”€ Commands ─────────────────────────────────────────────────────────────────────────╮
│ a | action | xyz Some description here. │
╰────────────────────────────────────────────────────────────────────────────────────╯
It would be nice to have command aliases, when all these commands do the same thing:
There is a module for click which can do it: https://github.com/click-contrib/click-aliases
Code looks like this:
Also this module click-aliases add info about alises to help: