fastapi / typer

Typer, build great CLIs. Easy to code. Based on Python type hints.
https://typer.tiangolo.com/
MIT License
15.68k stars 667 forks source link

[QUESTION] why replace "_" with "-" in command name? #341

Open bloodynumen opened 2 years ago

bloodynumen commented 2 years ago

First Check

Commit to Help

Example Code

from typing import Optional

import typer

app = typer.Typer()

@app.command()
def hello_test(name: Optional[str] = None):
    if name:
        typer.echo(f"Hello {name}")
    else:
        typer.echo("Hello World!")

if __name__ == "__main__":
    app()

Description

expect: python main.py hello_test --name world actually: python main.py hello-test --name world

I found the "_" in command name is replaced with "-". source code https://github.com/tiangolo/typer/blob/master/typer/main.py#L390

why do this?

Thank u

Operating System

macOS

Operating System Details

No response

Typer Version

0.4.0

Python Version

3.9.7

Additional Context

No response

daddycocoaman commented 2 years ago

This is probably just a design choice based around natural CLI convention. You're usually gonna find dashes in commands in any CLI, not underscores. If you want to keep the underscore, you can just specify the command name manually in the app.command().

mbopfNIH commented 2 years ago

This type of undocumented behavior is why I have decided not to use Typer. I like a lot of things about the package, but it's not worth the learning curve of digging through the code when something doesn't work as expected.

targhs commented 2 years ago

I guess that helps

manueldeprada commented 2 years ago

In fact, this is something that comes from the "click" package which typer uses as a base. If it helps, it comes from here:

https://github.com/pallets/click/blob/dc918b48fb9006be683a684b42cc7496ad649b83/src/click/core.py#L2633

Timmmm commented 1 year ago

Yeah this is pretty annoying. Is there any way to disable this behaviour? There doesn't seem to be anything in typer.Option(). In fact I couldn't find any reference documentation at all...?

manueldeprada commented 1 year ago

Yeah this is pretty annoying. Is there any way to disable this behaviour? There doesn't seem to be anything in typer.Option(). In fact I couldn't find any reference documentation at all...?

I donโ€™t think so, since it comes from click. I switched to Python Fire by google.

Timmmm commented 1 year ago

Ok this comes from get_command_name().

You can fix it like this:

typer.main.get_command_name = lambda name: name

I suggest an annotation on def main() to allow you to configure whether or not this happens.

Also note for bool options you'll get --no-foo_bar which is a bit weird but whatever.

Timmmm commented 1 year ago

I switched to Python Fire by google.

Ah I hadn't heard of that. Had a look but it seems like it has a slightly different focus and also doesn't support type hints which is like 90% of the reason for using Typer. I'll stick with my get_command_name hack for now I reckon.

manueldeprada commented 1 year ago

Ok this comes from get_command_name().

You can fix it like this:

typer.main.get_command_name = lambda name: name

I suggest an annotation on def main() to allow you to configure whether or not this happens.

Also note for bool options you'll get --no-foo_bar which is a bit weird but whatever.

Hmm interesting. Can you check if it also works for option names? I mean like --cool_option gets transformed into cool-option? Thanks :)

Timmmm commented 1 year ago

Hmm interesting. Can you check if it also works for option names? I mean like --cool_option gets transformed into cool-option? Thanks :)

Yep that's how I'm using it.

#!/usr/bin/env python3

import typer
from typing_extensions import Annotated

typer.main.get_command_name = lambda name: name

def main(foo_bar: Annotated[int, typer.Option(help="Do a thing.")]):
    return 0

if __name__ == "__main__":
    typer.run(main)
โฏ ./test.py --help

 Usage: test.py [OPTIONS]                                                                                    

โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ *  --foo_bar        INTEGER  Do a thing. [default: None] [required]                                       โ”‚
โ”‚    --help                    Show this message and exit.                                                  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
VladikaAndrii commented 1 year ago

ะฅะผ ั†ั–ะบะฐะฒะพ. ะงะธ ะผะพะถะตั‚ะต ะฒะธ ะฟะตั€ะตะฒั–ั€ะธั‚ะธ, ั‡ะธ ั†ะต ั‚ะฐะบะพะถ ะฟั€ะฐั†ัŽั” ะดะปั ะฝะฐะทะฒ ะฟะฐั€ะฐะผะตั‚ั€ั–ะฒ? ะฏ ะผะฐัŽ ะฝะฐ ัƒะฒะฐะทั–, ัะบ --cool_option ะฟะตั€ะตั‚ะฒะพั€ัŽั”ั‚ัŒัั ะฝะฐ cool-option? ะ”ัะบัƒัŽ :)

ะขะฐะบ, ั ั†ะธะผ ะบะพั€ะธัั‚ัƒัŽัั.

#!/usr/bin/env python3

import typer
from typing_extensions import Annotated

typer.main.get_command_name = lambda name: name

def main(foo_bar: Annotated[int, typer.Option(help="Do a thing.")]):
    return 0

if __name__ == "__main__":
    typer.run(main)
โฏ ./test.py --help

 Usage: test.py [OPTIONS]                                                                                    

โ•ญโ”€ Options โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ *  --foo_bar        INTEGER  Do a thing. [default: None] [required]                                       โ”‚
โ”‚    --help                    Show this message and exit.                                                  โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Thanks, you saved me a lot of time!

jaraco commented 2 months ago

Personally, I prefer the dash convention because it allows for less typing. On many (most?) keyboard layouts, - is a single keystroke, whereas _ is a shifted keystroke, requiring three motions in coordination versus one.

Timmmm commented 2 months ago

Yeah that's an advantage for sure. I guess it's up to you whether it outweighs the disadvantage of making the code harder to follow.

Another disadvantage of using _ I've found is that for bool arguments Typer generates --no-foo_bar which is just weird!

gajop-ptx commented 2 months ago

There are plenty cases where this automatic replacement of "_" with "-" does more harm than good.

I am often the user of my CLI programs, and not being able to directly copy-paste (or programatically generate) use cases from the API is annoying, but most of all, this hinders search / refactoring capability.

CLI programs are, for the most part, refactored by text search & replacement, and it's annoying to think about underscore/dash in these scenarios.

This

typer.main.get_command_name = lambda name: name

can suffice for now, but a more reliable (proper API) option is necessary, as this might break at any time, since it's not part of the API.