Skip to content
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

[FEATURE] [BUG] Behavior of app with single command is inconsistent if added to another app #243

Open
graue70 opened this issue Mar 4, 2021 · 6 comments
Labels
feature New feature, enhancement or request investigate

Comments

@graue70
Copy link
Contributor

graue70 commented Mar 4, 2021

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.

@graue70 graue70 added the feature New feature, enhancement or request label Mar 4, 2021
@codethief
Copy link

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.Arguments and typer.Options that might e.g. add help texts – just like app.command().)

@pkkr
Copy link

pkkr commented Apr 30, 2021

+1 ran into the same. Also thanks for the workaround

@cyphase
Copy link

cyphase commented Feb 8, 2022

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.

@single-fingal
Copy link

(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.)

@haoyun
Copy link

haoyun commented Jan 16, 2024

sorry for this +1 reply but just ran into exactly the same issue.

@albalamia
Copy link

Struggled with this for a while thank you for this, I would be happy for this to exist in the docs under sub-commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature, enhancement or request investigate
Projects
None yet
Development

No branches or pull requests

8 participants