typer
typer copied to clipboard
[FEATURE] [BUG] Behavior of app with single command is inconsistent if added to another app
I'm not sure whether this is a bug or a feature request. I think the behavior is not documented, so it might be a bug.
Related problem
In the docs, it says that when adding only one command to an app, 'Typer is smart enough to create a CLI application with that single function as the main CLI application, not as a command/subcommand' (no click group is created). See this example, which can be run with python welcome.py
(leaving out the name of the command main
):
# welcome.py
import typer
app = typer.Typer()
@app.command()
def main():
typer.echo("Hello World!")
if __name__ == "__main__":
app()
When I add this typer app to another app, the behavior changes. I add this file:
# parent.py
import typer
import welcome
app = typer.Typer()
app.add_typer(welcome.app, name="welcome")
@app.command()
def main():
typer.echo("Hello parent app!")
if __name__ == "__main__":
app()
When I run python parent.py welcome
, I get the following output:
Usage: parent.py welcome [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
main
The solution you would like
I expect the behavior of a typer app to stay consistent, no matter whether it is the top-level-app or a sub-app of another one. In the example above, I expect the output to be Hello world!
, but in order to get that, I need to run python parent.py welcome main
instead.
Describe alternatives you've considered
There is a workaround to get the desired behavior: Replace the line app.add_typer(welcome.app, name="welcome")
by app.command(name="welcome")(welcome.main)
in the file parent.py
.
However, this doesn't solve the inconsistency and it's also not very flexible. As soon as a second command is added to welcome.py
, the code needs to be changed back.
This would indeed be really useful as single commands of a typer.Typer
instance could then live in separate files.
I also agree with you when it comes to the workaround you described and would like to add: With the workaround, the decorator app.command()
doesn't live in the same file as the implementation welcome.main
– causing the two to be weirdly disconnected, even though they clearly belong together. (Consider that welcome.main
's signature might contain typer.Argument
s and typer.Option
s that might e.g. add help texts – just like app.command()
.)
+1 ran into the same. Also thanks for the workaround
Just ran into this issue. I kept searching the documentation and online before I remembered to search here, and found this. It would at least be good to explicitly mention this behavior in the documentation, if not change it.
(Disclaimer: I'm brand-new to typer
(as of last night).)
#119 is also relevant to this.
The approach by @cataerogong in this comment is similar to what I'm using. The relevant part of that comment roughly being:
import child
app = typer.Typer()
if len(child.app.registered_commands) == 1:
app.command('child_name')(child.app.registered_commands[0].callback)
else:
app.add_typer(child.app, name='child_name')
(Note: I haven't yet considered how run-time changes might affect this approach.)
For now, I monkeypatched typer.Typer.add_typer so that it essentially does something very similar to the above (modulo how the child's name is set). Though I would prefer it if typer had, built-in, at least an option to have this kind of behavior--so that it can still be backward-compatible with how people might have come to expect add_typer() behavior for such single command sub-apps, but optionally mirror the semantics that the docs describe for single command apps in general (i.e. if the (sub-)app has only 1 command, there's no need to include the command name when executing it). From limited testing, this approach works as expected.
(@tiangolo gives another solution here which I had already considered, but my implementation was buggin' out when I would pass e.g. an argument to the sub-app. I haven't spent enough time to see if it's a bug with my code or something else, but his alternative is probably also worth trying.)
sorry for this +1 reply but just ran into exactly the same issue.
Struggled with this for a while thank you for this, I would be happy for this to exist in the docs under sub-commands.