Git Product home page Git Product logo

typer's Introduction

Typer

Typer, build great CLIs. Easy to code. Based on Python type hints.

Test Publish Coverage Package version


Documentation: https://typer.tiangolo.com

Source Code: https://github.com/tiangolo/typer


Typer is a library for building CLI applications that users will love using and developers will love creating. Based on Python type hints.

It's also a command line tool to run scripts, automatically converting them to CLI applications.

The key features are:

  • Intuitive to write: Great editor support. Completion everywhere. Less time debugging. Designed to be easy to use and learn. Less time reading docs.
  • Easy to use: It's easy to use for the final users. Automatic help, and automatic completion for all shells.
  • Short: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
  • Start simple: The simplest example adds only 2 lines of code to your app: 1 import, 1 function call.
  • Grow large: Grow in complexity as much as you want, create arbitrarily complex trees of commands and groups of subcommands, with options and arguments.
  • Run scripts: Typer includes a typer command/program that you can use to run scripts, automatically converting them to CLIs, even if they don't use Typer internally.

FastAPI of CLIs

Typer is FastAPI's little sibling, it's the FastAPI of CLIs.

Installation

$ pip install typer
---> 100%
Successfully installed typer rich shellingham

Example

The absolute minimum

  • Create a file main.py with:
def main(name: str):
    print(f"Hello {name}")

This script doesn't even use Typer internally. But you can use the typer command to run it as a CLI application.

Run it

Run your application with the typer command:

// Run your application
$ typer main.py run

// You get a nice error, you are missing NAME
Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME
Try 'typer [PATH_OR_MODULE] run --help' for help.
╭─ Error ───────────────────────────────────────────╮
│ Missing argument 'NAME'.                          │
╰───────────────────────────────────────────────────╯


// You get a --help for free
$ typer main.py run --help

Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME

Run the provided Typer app.

╭─ Arguments ───────────────────────────────────────╮
│ *    name      TEXT  [default: None] [required]   |
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --help          Show this message and exit.       │
╰───────────────────────────────────────────────────╯

// Now pass the NAME argument
$ typer main.py run Camila

Hello Camila

// It works! 🎉

This is the simplest use case, not even using Typer internally, but it can already be quite useful for simple scripts.

Note: auto-completion works when you create a Python package and run it with --install-completion or when you use the typer command.

Use Typer in your code

Now let's start using Typer in your own code, update main.py with:

import typer


def main(name: str):
    print(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

Now you could run it with Python directly:

// Run your application
$ python main.py

// You get a nice error, you are missing NAME
Usage: main.py [OPTIONS] NAME
Try 'main.py --help' for help.
╭─ Error ───────────────────────────────────────────╮
│ Missing argument 'NAME'.                          │
╰───────────────────────────────────────────────────╯


// You get a --help for free
$ python main.py --help

Usage: main.py [OPTIONS] NAME

╭─ Arguments ───────────────────────────────────────╮
│ *    name      TEXT  [default: None] [required]   |
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --help          Show this message and exit.       │
╰───────────────────────────────────────────────────╯

// Now pass the NAME argument
$ python main.py Camila

Hello Camila

// It works! 🎉

Note: you can also call this same script with the typer command, but you don't need to.

Example upgrade

This was the simplest example possible.

Now let's see one a bit more complex.

An example with two subcommands

Modify the file main.py.

Create a typer.Typer() app, and create two subcommands with their parameters.

import typer

app = typer.Typer()


@app.command()
def hello(name: str):
    print(f"Hello {name}")


@app.command()
def goodbye(name: str, formal: bool = False):
    if formal:
        print(f"Goodbye Ms. {name}. Have a good day.")
    else:
        print(f"Bye {name}!")


if __name__ == "__main__":
    app()

And that will:

  • Explicitly create a typer.Typer app.
    • The previous typer.run actually creates one implicitly for you.
  • Add two subcommands with @app.command().
  • Execute the app() itself, as if it was a function (instead of typer.run).

Run the upgraded example

Check the new help:

$ python main.py --help

 Usage: main.py [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 ────────────────────────────────────────╮
│ goodbye                                           │
│ hello                                             │
╰───────────────────────────────────────────────────╯

// When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion

// You have 2 subcommands (the 2 functions): goodbye and hello

Now check the help for the hello command:

$ python main.py hello --help

 Usage: main.py hello [OPTIONS] NAME

╭─ Arguments ───────────────────────────────────────╮
│ *    name      TEXT  [default: None] [required]   │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --help          Show this message and exit.       │
╰───────────────────────────────────────────────────╯

And now check the help for the goodbye command:

$ python main.py goodbye --help

 Usage: main.py goodbye [OPTIONS] NAME

╭─ Arguments ───────────────────────────────────────╮
│ *    name      TEXT  [default: None] [required]   │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --formal    --no-formal      [default: no-formal] │
│ --help                       Show this message    │
│                              and exit.            │
╰───────────────────────────────────────────────────╯

// Automatic --formal and --no-formal for the bool option 🎉

Now you can try out the new command line application:

// Use it with the hello command

$ python main.py hello Camila

Hello Camila

// And with the goodbye command

$ python main.py goodbye Camila

Bye Camila!

// And with --formal

$ python main.py goodbye --formal Camila

Goodbye Ms. Camila. Have a good day.

Recap

In summary, you declare once the types of parameters (CLI arguments and CLI options) as function parameters.

You do that with standard modern Python types.

You don't have to learn a new syntax, the methods or classes of a specific library, etc.

Just standard Python.

For example, for an int:

total: int

or for a bool flag:

force: bool

And similarly for files, paths, enums (choices), etc. And there are tools to create groups of subcommands, add metadata, extra validation, etc.

You get: great editor support, including completion and type checks everywhere.

Your users get: automatic --help, auto-completion in their terminal (Bash, Zsh, Fish, PowerShell) when they install your package or when using the typer command.

For a more complete example including more features, see the Tutorial - User Guide.

Dependencies

Typer stands on the shoulders of a giant. Its only internal required dependency is Click.

By default it also comes with extra standard dependencies:

  • rich: to show nicely formatted errors automatically.
  • shellingham: to automatically detect the current shell when installing completion.
    • With shellingham you can just use --install-completion.
    • Without shellingham, you have to pass the name of the shell to install completion for, e.g. --install-completion bash.

typer-slim

If you don't want the extra standard optional dependencies, install typer-slim instead.

When you install with:

pip install typer

...it includes the same code and dependencies as:

pip install "typer-slim[standard]"

The standard extra dependencies are rich and shellingham.

Note: The typer command is only included in the typer package.

License

This project is licensed under the terms of the MIT license.

typer's People

Contributors

dependabot[bot] avatar gjolga avatar graue70 avatar huxuan avatar m9810223 avatar mariacamilagl avatar mccarthysean avatar michaelriri avatar mjodmj avatar mrcartoonster avatar paulo-raca avatar plannigan avatar pre-commit-ci[bot] avatar rkbeatss avatar rmstorm avatar runofthemill avatar ryangalamb avatar ryanstreur avatar septatrix avatar softwarebloat avatar ssbarnea avatar svlandeg avatar tadasgedgaudas avatar targhs avatar teymour-aldridge avatar themarix avatar theowisear avatar tiangolo avatar valentincalomme avatar victorphoenix3 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

typer's Issues

[QUESTION] How to gradually migrate from Click?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

I have a CLI application written using Click and I'd like to migrate to Typer.
It seems like there's no way to gradually migrate from Click to Typer and that's a refactor I'd like to avoid doing all at once.
We already did the refactor to migrate to Click and I'd like to avoid another large refactor.

Is there a way to call typer.run() on a regular Click group and migrate each command once at a time?

Additional context

The code can be found here.

[BUG] CliRunner invoke tests fail with Click 7.1

Describe the bug

Running tests with typer.testing.CliRunner invoke method with Click 7.1 installed results in failures with

__init__() got an unexpected keyword argument 'mix_stderr'

Forcing Click 7.0 installed fixes the issue.

Looks like mix_stderr keyword argument was removed from the invoke method.

7.0 invoke method:

    def invoke(self, cli, args=None, input=None, env=None,
               catch_exceptions=True, color=False, mix_stderr=False, **extra):

https://github.com/pallets/click/blob/a936b99212c69a36a2cc2828252b2c4280c0c88c/click/testing.py#L280

7.1 invoke method:

    def invoke(
        self,
        cli,
        args=None,
        input=None,
        env=None,
        catch_exceptions=True,
        color=False,
        **extra
    ):

https://github.com/pallets/click/blob/913ddf258dc973b79450f32169678f78770e1114/src/click/testing.py#L275

[BUG] List values get split by character when prompted

Describe the bug

When an option's type is List[str], the input gets split by character, as it would if I just called list("string")

To Reproduce

Steps to reproduce the behavior with a minimum self-contained file.

Replace each part with your own scenario:

  • Create a file main.py with:
import typer

from typing import List

app = typer.Typer()


@app.command()
def main(
    prefix: List[str] = typer.Option(
        ...,
        "-p",
        "--prefix",
        help="Prefix to filter, can specify multiple times",
        prompt="Prefix",
    )
):
    typer.echo(f"{prefix=}")


if __name__ == "__main__":
    app()
  • Call it with:
python main.py
  • It outputs:
Prefix [()]: abcd qwerty
prefix=('a', 'b', 'c', 'd', ' ', 'q', 'w', 'e', 'r', 't', 'y')
  • But I expected it to output:
Prefix [()]: abcd qwerty
prefix=('abcd', 'qwerty')

or

prefix=('abcd qwerty')
  • When using multiple flags, it is correct:
python migrate_dcos/test_strings.py -p abcd -p qwerty
prefix=('abcd', 'qwerty')

Expected behavior

I expected the values to be automatically (or with an Option argument) split on spaces, rather than the single string getting cast to a tuple.

Environment

  • OS: macOS
  • Typer Version: 0.1.0
  • Python version: Python 3.8.1

Additional context

I can work around it for space and comma-delimited values with:

def fix_strlist_value(value):
    if " " in value or "," in value or all(len(i) == 1 for i in value):
        return [k for k in "".join(value).replace(",", " ").split(" ") if k]
    return value

but I'd rather not have to.

[BUG] Context .invoke() fails quietly and .forward() fails

Describe the bug

When attempting to use the invoke() or forward() methods on a typer.Context object, the results are unpredictable, resulting in either outright failures, or passing of strange values in place of defaults.

To Reproduce

  • Create a file main.py with:
import typer

app = typer.Typer()

@app.command()
def test(count: int = typer.Option(1, help="How high to count")):
    typer.echo(f"Count: {count}")

@app.command()
def forward(ctx: typer.Context, count: int = 1):
    ctx.forward(test)

@app.command()
def invoke(ctx: typer.Context):
    ctx.invoke(test)

if __name__ == "__main__":
    app()
  • Call it with:
python main.py forward
  • It outputs:
...
TypeError: Callback is not a command.
  • But I expected it to output:
Count: 42
  • Call it with:
python main.py invoke
  • It outputs:
Count: <typer.models.OptionInfo object at 0x10e082a10>
  • But I expected it to output:
Count: 1

Expected behavior

It should be possible to use .invoke() on typer.Context objects in a manner similar to that described in the click documentation.

Environment

  • OS: macOS
  • Typer Version: 0.2.1
  • Python version: 3.7.6

[BUG] one command not detected, 2 commands ok

Describe the bug

using the below, we got

Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.

Error: Missing argument "NAME".

while uncommenting cmd2 we got as expected

Usage: main.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  cmd1
  cmd2

import typer


app = typer.Typer()


@app.command()
def cmd1(name: str):
    typer.echo(f"Hello1 {name}")

# @app.command()
# def cmd2(name: str):
#     typer.echo(f"Hello2 {name}")

if __name__ == "__main__":
    app()

To Reproduce

Environment

  • OS: [e.g. Linux]
  • Typer Version [e.g. 0.3.0], get it with: 0.0.8
python -c "import typer; print(typer.__version__)"
  • Python version, get it with: 3.8.1
python --version

[QUESTION] How to use with async?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

I have existing methods/model functions that use async functions with encode/databases to load data but I'm unable to use these within commands without getting errors such as RuntimeWarning: coroutine 'something' was never awaited

How can I make make my @app.command() functions async friendly?

[Doc] How to test a typer CLI ?

Hello,

First of all, thank you for your work, I'm a huge fan of FastAPI and my team is using Spacy everyday !
I'm trying to migrate my Click CLI to typer, and I was wondering how to test the CLI ?

[QUESTION] eagerness to mimic click.option ?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

how can I mimic the following

@click.option(
    "--version",
    is_flag=True,
    callback=print_version,
    expose_value=False,
    is_eager=True,
    help="Display version and exit.",
)

added to a command it shortcuts the command to run the callback (described here: https://click.palletsprojects.com/en/7.x/options/#callbacks-and-eager-options)

I tried the following yet the callback is called no matter a command is or is not entered, even --help is shortcut so I may be using the typer.Option wrongly or the is_eager kwarg is not used, no idea :)

click version:

def print_version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    click.echo('Version 1.0')
    ctx.exit()

@click.command()
@click.option('--version', is_flag=True, callback=print_version,
              expose_value=False, is_eager=True)
def hello():
    click.echo('Hello World!')
$ hello
Hello World!
$ hello --version
Version 1.0

typer version

import typer

app = typer.Typer()


def print_version(ctx, param, value):
    if not value or ctx.resilient_parsing:
        return
    typer.echo('Version 1.0')
    ctx.exit()


@app.command()
def hello(version: str = typer.Option('--version', is_flag=True, callback=print_version, expose_value=False, is_eager=True)):
    typer.echo('Hello World!')


if __name__ == '__main__':
    app()
$ python version.py
Version 1.0
$ python version.py --version
Error: --version option requires an argument
$ python version.py version
Version 1.0

Add support for custom parameter types

I want to extend the CLI app with custom data types not supported by Typer/Click. Right now, the Typer app raises an error on encountering an unsupported type.

The solution you would like

We could add a method to the Typer object to register custom data types along with a function to convert terminal input string to an object of the desired type.

A possible API could be:

app = typer.Typer()
app.register_custom_type(_type, _deserializer)

Here is the monkey-patched solution I use right now without providing a registration API. If this seems like a useful feature, I'll try refine this and submit a PR.

import datetime
from typing import Any, Callable, Optional

import typer
import click


_get_click_type = typer.main.get_click_type
REGISTERED_TYPES = {
    datetime.date: lambda string: datetime.datetime.strptime(string, '%Y-%m-%d').date()
}

class TyperCustomParam(click.ParamType):
    name = 'CustomParameter'
    def __init__(self, typ: type, deserializer: Optional[Callable] = None): 
        self.name = typ.__name__
        self._deserializer = typ if deserializer is None else deserializer

    def convert(self, value, param, ctx): 
        try:
            return self._deserializer(value)
        except Exception as E: 
            self.fail(
                f"couldn't serialize {value} to an instance of type {self.name}, error: {E}"
            )

def supersede_get_click_type(
    *, annotation: Any, parameter_info: typer.main.ParameterInfo
) -> click.ParamType:
    if annotation in REGISTERED_TYPES:
        return TyperCustomParam(annotation, REGISTERED_TYPES[annotation])
    else:
        return _get_click_type(annotation=annotation, parameter_info=parameter_info)

typer.main.get_click_type = supersede_get_click_type

Alternatives

Alternatives would be to either modify the original routine or create a wrapper function to accept a Click supported datatype and convert later. Letting users create custom types directly is preferred since that lets us create CLIs without touching existing code.

Thank you for looking into this. Similar to FastAPI, typer is a really nice library.

[QUESTION] Is Command chaining supported?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

Is it possible to 'chain' commands in a single Typer call? Typical usecase for us would be an application where we have a CLI app to do input->processing->output kind of tasks, and for all three there are multiple variants with different options, but they can be combined in a lot of ways.

Additional context

We currently use Google Fire for our CLI app (which I was surprised not to found in the Inspiration section), which does this by saving intermediate results in a class containing all 'Commands'.

[FEATURE] callback should not be called on --help for a subcommand

Again, thanks for the great library. So far it's be great to use (though some of the more advanced features I think lack some documentation).

from pathlib import Path
import typer
app = typer.Typer()

@app.command()
def bar(y: str):
    print('y =', y)

@app.callback()
def my_callback(path: Path = 'testing'):
    """
    testing
    """
    print('calling my_callback...')
    assert path.exists(), path

if __name__ == '__main__':
    app()

If I execute python test.py or python test.py --help then correctly my_callback is not called.

But if I execute python test.py bar --help then my_callback is called. I think in this case my_callback should not be called.

If it is called, is there a clean way to check if it's being called as part of a help command other than if '--help' in sys.argv: return?

[FEATURE] Explicitly using typer.models.Required

Is your feature request related to a problem

First of all, thank you so much for yet another awesome project! It feels almost like magic to use your products.

However, some things are a bit too magic for me. One is the use of ... for indication of required parameters. I've found that typer.models.Required can be used instead, which feels more explicit and can be understood even without looking at the docs. Do you plan to keep it available in this module? Can I rely on it?

The solution you would like

Ideally, promote it to typer.Required and mention in the docs :)

[FEATURE] Sphinx plugin

Is your feature request related to a problem

I want to be able to integrate the generation of the CLI documentation to the documentation I build with Sphinx.

The solution you would like

A sphinx plugin seems like the perfect candidate. Something like sphinx-click.

Describe alternatives you've considered

The workaround I am using is to modify the Makefile provided by Sphinx to generate the Typer documentation with typer-cli:

DETAILED_USAGE_PAGE = source/usage.md

%: Makefile
    echo "# Detailed usage" > "$(DETAILED_USAGE_PAGE)"
    typer $(shell git rev-parse --show-toplevel)/tfpy/main.py utils docs --name tfpy | tail -n +2 >> "$(DETAILED_USAGE_PAGE)"
    @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

Here is the actual file.

Additional context

Here is the project I am using Typer with: tfpy. And its documentation.

[FEATURE] Set a defaut command in multiple commands

I wanna to be able to set a default command when I have multiples commands. For example, with:

import typer

app = typer.Typer()


@app.command()
def hello(name: str):
    typer.echo(f"Hello {name}")


@app.command()
def goodbye(name: str, formal: bool = False):
    if formal:
        typer.echo(f"Goodbye Ms. {name}. Have a good day.")
    else:
        typer.echo(f"Bye {name}!")


if __name__ == "__main__":
    app()

I would like to have some way of being able to call it without specifying the command, calling a default command (maybe with something like @app.command(default=True) like this:

$ python main.py Hiro
Hello Hiro

$ python main.py helo Hiro  # This also should work
Hello Hiro

[FEATURE] Load command line options from config file

Hello! I love Typer and have started to use it for my deep learning training.

One addition that would be really helpful for deep learning CLIs is the ability to load command line arguments and options from configuration files. This is particularly useful to help manage CLIs with lots of arguments and options, especially when certain sets of inputs are common. Sacred configs have this ability, as do WandB configs:

Here is a demo of how this could work with Typer:

Parameters specified in resnet.yml will automatically fill in args/options of the same names:

train.py --config resnet.yml

When multiple configs are provided, the latter will override the former should there be conflicts.

run.py --config resnet.yml small_test.yml

If args/options are also specified directly, those override anything provided in the config.

run.py --config resnet.yml small_test.yml --num_layers 4

An alternative to consider is just making each config its own boolean option. This has the following downsides:

  • Requires adding more flags, even if they only modify other args/options. This does not scale well.
  • Ambiguous overriding if multiple configs are provided or if args/options are also added directly.

With argparser, people will sometimes use the following pattern to achieve something similar. By changing the defaults and re-parsing rather than directly changing the args, this allows args/options defined directly on the command line to still override the new defaults. However, this too does not scale well, and there is no clear way to change defaults with Typer.

args, _ = parser.parse_known_args()
        if opt.small_test:
            parser.set_defaults(batch_size=1, num_layers=2, multiscale=False, ...)
args = parser.parse()

I believe the addition of loading args from config files would make Typer fantastic for deep learning research, making it much easier to scale and compose different configurations, and clarifying which parameters override others.

This unfortunately isn't something I know how to implement, but I would be happy to discuss or clarify this further if you agree that it would be a good addition.

Thanks!

[BUG] Cannot create optional multi-valued positional argument

Describe the bug

First off, let me thank you for creating typer 😄 I absolutely love it, especially because click was my favorite CLI library and you went and made it even nicer 👍

Soooo, I was attempting to create a CLI with an optional multi-valued positional argument (i.e. taking zero or more values). I made two attempts to no avail. The first one didn't even run, and the second one didn't allow me to omit the argument (the "optional" part).

To Reproduce

Here is a minimal example that shows my first attempt at using a list-valued typer.Argument(). According to the docs, providing a default value makes the argument optional, so I provided an empty list.

import typer
import typing

app = typer.Typer()

@app.command()
def test(
    patterns: typing.List[str] = typer.Argument([]),
    untracked: bool = typer.Option(False, "-u", "--untracked"),
):
    typer.echo(locals())

if __name__ == "__main__":
    app()

Running this with python test.py gives me the following stack trace:

% python test.py
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    app()
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 213, in __call__
    return get_command(self)()
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 238, in get_command
    click_command = get_command_from_info(typer_instance.registered_commands[0])
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 422, in get_command_from_info
    ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 403, in get_params_convertors_ctx_param_name_from_function
    click_param, convertor = get_click_param(param)
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 722, in get_click_param
    click.Argument(
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/click/core.py", line 1986, in __init__
    raise TypeError(
TypeError: nargs=-1 in combination with a default value is not supported.

So in an attempt to fix this error, I replaced with argument's default value with ellipsis ..., which allows the program to run but requires at least one value for the argument.

% python test.py
Usage: test.py [OPTIONS] PATTERNS...
Try 'test.py --help' for help.

Error: Missing argument 'PATTERNS...'.

Expected behavior

I believe the error is due to typer marking the argument as required despite it being a list-valued argument. I would expect it to be possible (and even the default behavior!) to create list-valued arguments that take zero or more values, instead of one or more. Even better (if possible) would be a generalization where minimum and maximum number of values consumed by the argument are specified, defaulting to 0 and infinity, respectively.

Environment

  • OS: Debian 10
  • Python 3.8.2
  • Typer 0.2.1
  • Click 7.1.2

[FEATURE] Support Pydantic Models as ParamTypes

Is your feature request related to a problem

I would like to use Pydantic Models as type annotations in my Typer CLI program.

The solution you would like

Typer would call Model.parse_raw on the string that was passed to the CLI

Describe alternatives you've considered

Rather than having Typer be so aware of the Pydantic API, I tried to create a custom click.ParamType that did the parsing, but even that did not work as custom types do not currently seem to be supported in the get_click_type function.

Additional context

Here's a simple example:

#!/usr/bin/env python3
import click
import typer
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = "Jane Doe"


class UserParamType(click.ParamType):
    def convert(self, value, param, ctx):
        return User.parse_raw(value)


USER = UserParamType()


def main(num: int, user: USER):
    print(num, type(num))
    print(user, type(user))


if __name__ == "__main__":
    typer.run(main)

This currently throws:

Traceback (most recent call last):
  File "./typer_exp.py", line 26, in <module>
    typer.run(main)
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 848, in run
    app()
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 213, in __call__
    return get_command(self)()
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 238, in get_command
    click_command = get_command_from_info(typer_instance.registered_commands[0])
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 422, in get_command_from_info
    ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 403, in get_params_convertors_ctx_param_name_from_function
    click_param, convertor = get_click_param(param)
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 656, in get_click_param
    annotation=main_type, parameter_info=parameter_info
  File "/usr/local/lib/python3.7/site-packages/typer/main.py", line 586, in get_click_type
    raise RuntimeError(f"Type not yet supported: {annotation}")  # pragma no cover
RuntimeError: Type not yet supported: <__main__.UserParamType object at 0x7fc346234b10>

A major bonus would be if I could write the user: User type annotation directly, without creating the UserParamType.

Also - just want to say thank you for writing such an awesome python package and keep up the great work! 👏

[BUG] Example not working as expected

Describe the bug

Write here a clear and concise description of what the bug is.

To Reproduce

  • Create a file main.py with:
import typer

app = typer.Typer()

@app.command()
def hello(name: str, formal: bool = False):
    if formal:
        typer.echo(f'Hello {name}')
    else:
        typer.echo(f'Whaaaasuuuup {name}')

if __name__ == '__main__':
    app()
  • Call it with:
python main.py --formal John
  • It outputs:
Error: Got unexpected extra argument (Kike)
  • But I expected it to output:
Hello John

Environment

  • OS: Linux (Ubuntu 18.04)
  • Typer Version: 0.0.4
python -c "import typer; print(typer.__version__)"
  • Python version: 3.6.9

[QUESTION] How to implement global options.

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

How can I implement global options like --install-completion, --show-completion?

Click allows to easily implement global options in context.
I want a simple function similar to typer.

Additional context

I often implement global options such as --env and --debug.

[FEATURE] Typing Support (PEP 561)

Is your feature request related to a problem

I would like mypy to be able to type check usages of Typer. Currently running mypy on a project that uses Typer gives the "Cannot find implementation or library stub for module named 'typer'" error.

The solution you would like

Because everything is type annotated in the code base, I believe this is as simple as adding a py.typed file to the distribution.

[QUESTION] Where can mypy find library stubs for typer?

First check

  • [x ] I used the GitHub search to find a similar issue and didn't find it.
  • [x ] I searched the Typer documentation, with the integrated search.
  • [ x] I already searched in Google "How to X in Typer" and didn't find any information.
  • [ x] I already searched in Google "How to X in Click" and didn't find any information.

Description

Where can mypy find library stubs for typer? Currently when I run mypy in my projects root directory I get the error Cannot find implementation or library stub for module named 'typer'

[FEATURE] Prevent install-completion from failing when command is not available

When using click-completion in previous versions, if the command was not available in my path when the shell started, it wouldn't cause an error. I have a variety of command line utilities added to various web apps that I don't want installed globally where I'd have to update them all the time. Instead, they are available when I'm in the poetry shell for that project.

I don't completely understand what click-completion was doing before, but it looks like it was only trying to evaluate completion when calling the command instead of at startup:

#compdef openapi-python-client
 _openapi-python-client() {
    eval $(env COMMANDLINE="${words[1,$CURRENT]}" _OPENAPI_PYTHON_CLIENT_COMPLETE=complete-zsh  openapi-python-client)
 }
 if [[ "$(basename -- ${(%):-%x})" != "_openapi-python-client" ]]; then
    compdef _openapi-python-client openapi-python-client
 fi

Whereas Typer attempts to evaluate the command immediately:

eval "$(_OPENAPI_PYTHON_CLIENT_COMPLETE=source_zsh openapi-python-client)"

Would it be possible to add some similar delayed evaluation to Typer?

[QUESTION] Is pyinstaller supported for automatic shell completion?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

I am bundling a executable using pyinstaller 3.6 that has a dependency on typer[all].

altgraph==0.17,click==7.1.2,colorama==0.4.3,coloredlogs==14.0,dataclasses==0.6,humanfriendly==8.2,macholib==1.14,PyInstaller==3.6,shellingham==1.3.2,typer==0.2.1

When I run with --show-completion I get the following error:

$ ./my_exe --show-completion
Traceback (most recent call last):
  File "shellingham/__init__.py", line 13, in detect_shell
  File "importlib/__init__.py", line 126, in import_module
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'shellingham.posix'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "my_exe.py", line 240, in <module>
  File "my_exe.py", line 236, in main
  File "typer/main.py", line 213, in __call__
  File "click/core.py", line 829, in __call__
  File "click/core.py", line 781, in main
  File "click/core.py", line 700, in make_context
  File "click/core.py", line 1212, in parse_args
  File "click/core.py", line 1048, in parse_args
  File "click/core.py", line 1630, in handle_parse_result
  File "click/core.py", line 123, in invoke_param_callback
  File "typer/main.py", line 788, in wrapper
  File "typer/completion.py", line 59, in show_callback
  File "shellingham/__init__.py", line 16, in detect_shell
RuntimeError: Shell detection not implemented for 'posix'
[16390] Failed to execute script my_exe

I wasn't sure if this was a bug, or something that simply isn't supported.

Additional context

Build command:

$ pyinstaller --onefile my_exe.py --hidden-import=pkg_resources.py2_warn

`typer run` with a script declaring multiple functions

When there are more than one function declared in a Python script,
and none of them is named main,
it seems that typer will randomly choose the function to run when using typer that-script.py run ARG.

For example:

def hello(name: str):
    print(f"Hello {name}!")

def plus_one(n: int):
    print(n + 1)

Several runs:

$ typer hello.py run "world"
Usage: typer run [OPTIONS] N
Try 'typer run --help' for help.

Error: Invalid value for 'N': world is not a valid integer
$ typer hello.py run "typer"
Hello typer!
$ typer hello.py run 1
2
$ typer hello.py run 0
Hello 0!

I suggest the following alternatives to the current undefined behavior:

  1. Reports an error and abort, telling the developer that they must declare only one function in the script, or declare multiple functions with one of them named main
  2. (I think this will be cool but maybe over-engineered) Try to dispatch based on guessing the type of command line argument. For the example above, first try to convert the argument to an integer. If succeed, invoke the function with matching signature (plus_one), otherwise invoke hello. If there are more than one functions matched, or if none of the functions matched, report an error instead.

[BUG] Typer.style fails in Windows Powershell with colorama installed

Describe the bug

I tried to print red text in powershell on Windows. However, it just printed with escape symbols instead. This is with colorama installed and if i manually initialize colorama the colors works as it should.

To Reproduce

Reporduce non-working version:

import typer
def main():
    print(typer.style("RED", fg=typer.colors.RED))
if __name__ == "__main__":
    typer.run(main)

Outputs:

�[31mRED�[0m

With manual colorma init:

import typer
import colorama
colorama.init()
def main():
    print(typer.style("RED", fg=typer.colors.RED))
if __name__ == "__main__":
    typer.run(main)

Ouputs, in red text:

RED

Expected behavior

As it in the documentation, https://typer.tiangolo.com/tutorial/printing/#color, is stated that you don't have to manually initialize colorama I expected the first to work.

Environment

  • OS: Windows
  • Typer Version: 0.1.1
  • Python version: 3.7.2

[FEATURE] Literal type support

Support for the literal type (python 3.8 or via typing_extensions) would be nice. Eg.

def get_list(output: Literal["raw", "json", "yaml"] = "raw"):
  if output == "json":
    typer.echo(json.dumps([1, 2, 3]))

The only workarounds (using string or multiple bool flags + validating input) are ugly now.

[FEATURE] Adding Typer to custom package using entry points

Wuhuu finally started using Typer and very excited about it.

I went ahead and used the most simple example

import typer

def main(name: str):
    typer.echo(f"Hello {name}")


if __name__ == "__main__":
    typer.run(main)

which works just fine obviously. But I'd like to expose my custom package with CLI using Typer so I made a setup.py with

from setuptools import setup

setup(
    name="my_package",
    entry_points={
        "console_scripts": ["hello = my_package.cli:main"],
    },
)

Then I can call the command using hello in a terminal BUT it doesn't carry over any positional arguments. Maybe you could add some about entry points integration in the docs?

Using typer as package with pydantic BaseSettings

Describe the bug

I am using typer as a package where I get the following error

  .
  .
  File "/root/typer_app/main.py", line 9, in publish
    class Settings(BaseSettings):
  File "pydantic/main.py", line 184, in pydantic.main.ModelMetaclass.__new__
  File "pydantic/typing.py", line 136, in pydantic.typing.resolve_annotations
    if arg is None:
KeyError: 'typer_app.main'

To Reproduce

I am using typer as a package

/root
| typer_app
  | __init__.py
  | main.py

where

main.py

import typer
from pydantic import BaseSettings

app = typer.Typer()


@app.command()
def publish():
    class Settings(BaseSettings):
        HTTP_ENDPOINT: str

    settings = Settings()
    typer.echo(settings.HTTP_ENDPOINT)
  • Call it with:
PYTHONPATH=/root typer typer_app.main run
  • It outputs:
  .
  .
  File "/root/typer_app/main.py", line 9, in publish
    class Settings(BaseSettings):
  File "pydantic/main.py", line 184, in pydantic.main.ModelMetaclass.__new__
  File "pydantic/typing.py", line 136, in pydantic.typing.resolve_annotations
    if arg is None:
KeyError: 'typer_app.main'
  • But I expected it to output a validation error since HTTP_ENDPOINT is not set.

Environment

Linux Mint - Python 3.8.2
typer==0.1.1
typer-cli==0.0.8

Auto-document command with docstrings

Describe the bug

It seems like docstrings are currently not used to auto-document the commands when typing --help. Or maybe there's a setting that I'm missing?

To Reproduce

  • Create a file main.py with:
import typer

app = typer.Typer()


@app.command()
def hello(name: str):
    """This is a docstring."""
    typer.echo(f"Hello {name}")


if __name__ == "__main__":
    app()
  • Call it with:
python main.py --help
  • It outputs:
Usage: main.py [OPTIONS] NAME

Options:
  --help  Show this message and exit.
  • But I expected it to output:
Usage: main.py [OPTIONS] NAME

+  This is a docstring.

Options:
  --help  Show this message and exit.

Expected behavior

The equivalent in click and plac would use "This is a docstring" to describe the command when you run it with --help.

Environment

  • OS: macOs
  • Typer Version: 0.0.4
  • Python version: 3.6.0

[QUESTION] How can I check what command was called within a callback?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

How can I check what command or subcommand was called within a callback?

Additional context

I am using a callback for concern separation. I am not sure if there is a better way to address this particular problem. Ideally I want to selectively run the callback logic depending on the command that was called. For example, I want to check for the following condition if app.info.command != "configure":.

@app.callback()
def check_configuration():
    if app.info.command != "configure": 
        import configparser
        config = configparser.ConfigParser()
        config.read("stack_config.ini")

        for section in config.sections():
            for _, value in config.items(section):
                if value is None or len(value) == 0:
                    typer.echo("Please configure CLI options.")
                    raise typer.Exit(code=1)

[FEATURE] Don't prompt if given extra argument

When using typer.Option(prompt=True)) and calling the script with more arguments than are allowed, the user will first be prompted for all prompts and then get an error because of the extra argument.

To reproduce use this

# test.py
import typer

app = typer.Typer()

@app.command()
def run(a: str = typer.Option(..., prompt=True)): ...

app()
❯ python test.py extra
A:

but then

❯ python test.py extra
A: a
Usage: test.py [OPTIONS]
Try 'test.py --help' for help.

Error: Got unexpected extra argument (extra)

This is a weird user experience and should be changed to show the error before prompting if we know we're going to fail anyway.

I hope I could make it understandable what I mean.
Kindest regards and thanks for your great work!

running typer cli command from python code

How can i trigger/run typer cli specific command from python code. I guess i need to emulate command line arguments some where but not sure. Can you please help on this?

Cli:
python main.py goodbye --formal Camila

From Code:
i want to run specific command based on my need. how do i pass command line arguments, some thing like:
typer_app( "goodbye --formal Camila" )

[FEATURE] Add easy --version

Is your feature request related to a problem

I'd like a way to do the standard my-command --version to exit an print the version number of the application. Click offers a version_option decorator that you can put on the entrypoint of your application to achieve this relatively easily.

The solution you would like

The ability to pass in a version string to an application and have the option added automatically. I'd then end up doing something like this:

from importlib.metadata import version

app = typer.Typer(version=version(__package__))

Though if it's possible to attempt to extract the package version automatically without having to pass it in, that would be even better.

Describe alternatives you've considered

I attempted to just add a version option in my callback like below, but I can't seem to call this without passing in a command.

@app.callback()
def cli(display_version: bool = typer.Option(False, "--version")) -> None:
    if display_version:
        print(f"Version: {version(__package__)}")
        raise typer.Exit()

However if I just try my-command --version it gives me an error saying I need to pass a command. It would be nice to be able to add custom top level options like --help which are allowed to run. Then users could implement things like --version themselves.

[QUESTION]

Description

Is it possible to add a new field to the OAuth2PasswordRequestForm?

Additional context

I need to add a new optional field called new_password in the following Request Body:
WhatsApp Image 2020-05-26 at 19 26 29

What I was trying to do:

class LoginForm(OAuth2PasswordRequestForm):
    def __init__(self, new_password: str = Form(None)):
        super().__init__()
        self.new_password = new_password

But it overwrites what is there and just show the new_password:
image

[FEATURE] Support datetime.date in addition to datetime.datetime

Is your feature request related to a problem

Typer currently allows for datetime.datetime-objects as arguments,
but does not support the less specific datetime.date-object:

# example.py
import datetine
import typer

def do_thing_with_date(date: datetime.date):
    typer.echo(f"Year is: {date.year}")

if __name__ == "__main__":
    typer.run(do_thing_with_date)

This code produces:

$ python example.py 2020-04-01
Traceback (most recent call last):
  File "dateapp.py", line 12, in <module>
    typer.run(do_thing_with_date)
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 743, in run
    app()
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 215, in __call__
    return get_command(self)()
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 241, in get_command
    click_command = get_command_from_info(typer_instance.registered_commands[0])
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 423, in get_command_from_info
    ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 404, in get_params_convertors_ctx_param_name_from_function
    click_param, convertor = get_click_param(param)
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 655, in get_click_param
    parameter_type = get_click_type(
  File "/usr/local/lib/python3.8/site-packages/typer/main.py", line 586, in get_click_type
    raise RuntimeError(f"Type not yet supported: {annotation}")  # pragma no cover
RuntimeError: Type not yet supported: <class 'datetime.date'>

The solution you would like

Allow for datetime.date as input arguments/options to Typer.

Describe alternatives you've considered

It is true that it is possible to get just the date from datetime.datetime in the current implementation:

  • input parsing supports the format %Y-%m-%d without needing to specify a time
  • you can get a datetime.date-object with .date()

However, I think that allowing for just the date part is better in some situations:

  • No need to explicitly extract the date part with .date()
  • It provides some intention to the end user that only the date part is considered
  • It does not confuse any user with input formatting info that is irrelevant to the application: the current error message for missing arg/invalid value for datetime.datetime shows the format "[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]"

[QUESTION] Question re: `Testing` docs—Is an empty __init__.py required?

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • [n/a] I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • [n/a] I already searched in Google "How to X in Click" and didn't find any information.

Description

I'm relatively new to Python so I want to ask something about the docs. In docs about Testing, it says

And the directory also has an empty app/init.py file. So, the app is a "Python package".

It makes it sound like an empty __init__.py should be there when I've been reading that this is no longer required for Python packages since Python 3.3+ and I'm not planning to distribute my typer app.

The Question

What am I missing? Is an empty __init__.py indeed required to be able to test my typer app?

Thanks for the awesome package,
Also a FastAPI User

[BUG] typer.main.get_group should return a click.Group

Describe the bug

The function typer.main.get_group is annotated as returning a click.Command which isn't wrong because a click.Group is a click.Command, but means I have to cast to group when trying to integrate with existing Click applications.

To Reproduce

Steps to reproduce the behavior with a minimum self-contained file.

Replace each part with your own scenario:

  • Create a file main.py with:
import typer

app = typer.Typer()


@app.callback()
def callback():
    """ My partially-upgraded-to-Typer app """

# insert some fancy new Typer commands here


click_group = typer.main.get_group(app)


@click_group.command()
def some_old_click_function():
      """ I need to rewrite this somday """

Run mypy on this script with mypy main.py and it will output something like

main.py:16: error: "Command" has no attribute "command"

Expected behavior

Able to drop in Typer at the top level to start slowly converting an existing Click app.

Screenshots

If applicable, add screenshots to help explain your problem.

Environment

  • OS: macOS
  • Typer Version: 0.2.1
  • Python version: 3.8.2
  • mypy version: 0.770

[QUESTION] Dependency Injection

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

First of all, thank you for your amazing tools, it helps my team and I to build great products !

Description

How can I inject a Database connection to a "cli function" ?

I was looking for a mecanism like Depends in FastAPI (https://fastapi.tiangolo.com/tutorial/dependencies/)

Additional context

I'm trying to create a cli which read files and insert them to a database but one of my subcommands is depending of a Database connection.

For the moment I've used:

save_app = typer.Typer()


def get_db():
    typer.echo("Initializing database")
    db = VerticaDB(
        host=conf.get_vertica_host(),
        port=conf.get_vertica_port(),
        user=conf.get_vertica_user(),
        password=conf.get_vertica_password(),
        database=conf.get_vertica_database()
    )
    return db


@save_app.command("save")
def ingest_snitch_file(
        path_to_log_file: Path = typer.Option(
            ...,
            exists=True,
            readable=True,
            envvar='PATH_TO_SNITCH_FILE'
        ),
        db=typer.Option(
            hidden=True,
            default=get_db()
        )
):
    """
    Ingest snitch files to Database
    """
    snitch = Snitch(
        db=db,
        path_to_snitch=path_to_log_file,
        table_schema=conf.get_vertica_table_schema(),
        table_name=conf.get_vertica_table_name()
    )
    snitch.insert_log_to_database()

[FEATURE] Handling Path / File arguments easily

click has an opportunity to handle Path and File arguments with ease.
Is there an option to do the same in typer?

I am not gonna lie, that was so fun to just put this into the click.argument parameters and let the library do all the magic and checking for me:

@click.command()
@click.argument(
    "filename",
    type=click.Path(exists=True, dir_okay=False, resolve_path=True, readable=True),
)
def path_command(filename):
    ...

The only inconvenience I had - it did not return a pathlib.Path object, so I had do manually create it, but that is not a big deal.

As I can see, click.Path type is not exposed in the typer module. And using pathlib.Path as a type for my parameter is not helping in any way.

Thanks for your time! I would like to know what you think about it.

[QUESTION] Typer w/ asyncio

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

Is it possible to get Typer to play nicely with asyncio? I would like to use Typer to create a utility that calls into a WebAPI service like FastAPI (thanks for that, BTW). I have options parsing and validating properly from a separate function, but Typer appears to exit directly instead of continuing.

Additional context

The relevant bits:

def parse_cli(
    hostValue: str = typer.Option(..., '--host', '-h', callback=validateHostValue, help='The DNS hostname (or IP address) of the WebAPI server'),
  ):

  return hostValue # ??? Would be really swell

async def main():
  print('Awaiting a GET response...') # Never gets to here

if __name__ == '__main__':
  print('START...')
  result = typer.run(parse_cli)
  print(f"RESULT TYPE {type(result)}") # Never gets to here

  asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
  result: str = asyncio.get_event_loop().run_until_complete(main())
  if (result is not None) and (len(result) != 0):
    print(result)

Thanks for the guidance.

Enum Option default is `None` [BUG]

Describe the bug

If an enum option type is not given, it's value is None instead of the specified default value. If the option is specified then it works as expected

To Reproduce

Steps to reproduce the behavior with a minimum self-contained file.

Replace each part with your own scenario:

  • Create a file main.py with:
from enum import Enum

import typer


class NeuralNetwork(str, Enum):
    simple = "simple"
    conv = "conv"
    lstm = "lstm"


def main(
    network: NeuralNetwork = typer.Option(NeuralNetwork.simple, case_sensitive=False)
):
    typer.echo(f"Training neural network of type: {network.value}")


if __name__ == "__main__":
    typer.run(main)
  • Call it with:
python main.py
  • It outputs:
Traceback (most recent call last):
...
  File "tutorial002.py", line 15, in main
    typer.echo(f"Training neural network of type: {network.value}")
AttributeError: 'NoneType' object has no attribute 'value'
  • But I expected it to output:
Training neural network of type: simple

Expected behavior

I would expect that the specified default value of an enum would be passed to the function parameter instead of None when the option is not provided.

Environment

  • OS: [macOS]
  • Typer Version [0.0.8]
  • Python version [3.7.6]

[QUESTION] get arguments value

First check

  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the Typer documentation, with the integrated search.
  • I already searched in Google "How to X in Typer" and didn't find any information.
  • I already searched in Google "How to X in Click" and didn't find any information.

Description

Does typer collect all the values given to a dictionary or a tuple?

Thanks

[FEATURE] Handling Mutliple Variable Length Arguments

I'm not familiar with the internals of typer and whether this is feasible, but it would be nice if it were possible to implement a command like the following:

from typing import Optional, List

import typer

app = typer.Typer()


@app.command()
def main(first_list: List[str], option: bool = False, second_list: List[str] = ()):
    print(first_list, second_list)

Where the expected behavior is:

$ python main.py a b c --option d e f
('a', 'b', 'c') ('d', 'e', 'f')
$ python main.py a b c d e f --option
('a', 'b', 'c', 'd', 'e', 'f') ()
$ python main.py a b c d e f
('a', 'b', 'c', 'd', 'e', 'f') ()

However the actual behavior in this case does not adhere to the above:

$ python main.py a b c --option d e f
('a', 'b', 'c', 'd', 'e', 'f') True ()

This can presently be achieved in argparse quite concisely:

import argparse

def main():
    cli = argparse.ArgumentParser()
    cli.add_argument(
        "first_list", nargs="*", type=str,
    )
    cli.add_argument(
        "--option", nargs="*", type=str, default=[],
    )
    args = cli.parse_args()
    print(args.first_list, args.option)

generate markdown documentation

The solution you would like

    $ mycommand --help-markdown-documentation > mycommand.md

It would be nice if there was an automatic way of generating a markdown documentation from the Typer help texts.

Describe alternatives you've considered

A script like the following kind of works, but it's fragile and doesn't have access to all the same information as the Typer library.

#!/bin/bash

for command in "$@"; do
    echo
    echo "# $command (command)"
    echo
    $command --help | sed '1,/^Commands:/d' | awk '{print $1}' |
    while read subcommand; do
        echo
        echo "## $command $subcommand (subcommand)"
        echo
        echo '```generic'
        obj $subcommand --help
        echo '```'
        echo
    done
done

Support click-help-colors [FEATURE]

Problem

I want to be able to replicate the functionality of click-contrib/click-help-colors to add colors to help text for easier readability

Ideal API

import typer
from click_help_colors import HelpColorsGroup

app = typer.Typer(cls=HelpColorsGroup,
    help_headers_color='yellow',
    help_options_color='green')

Attempted

By subclassing HelpColorsGroup and explicitly setting the colors in the __init__ it is possible to get colors

import typer
from click_help_colors import HelpColorsGroup


class CustomHelpColorsGroup(HelpColorsGroup):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.help_headers_color = "blue"
        self.help_options_color = "yellow"


app = typer.Typer(cls=CustomHelpColorsGroup)


@app.command()
def test_function(
    name: str = typer.Option("World", "--name", "-n", help="name to say hello")
) -> None:
    typer.echo(f"Hello {name}!")


@app.command()
def other() -> None:
    typer.echo("other")


if __name__ == "__main__":
    app()

typer_help_colors

However the color does not propagate to sub command help text

sub_command_no_color

can't get --install-completion to work

given this program:

#!/usr/bin/env python
  port typer

app = typer.Typer()

@app.command()
def foo():
    print('foo')

@app.command()
def bar():
    print('bar')

if __name__ == "__main__":
    app()

I run: ./foo.py --install-completion

I see:

zsh completion installed in /Users/tchan/.zshrc.
Completion will take effect once you restart the terminal.

I source my .zshrc and then type ./foo.py and hit tab

I see this:

./foo.py env: foo.py: No such file or directory

options files

I want to be able to load options defaults from an options file (e.g., in YAML or JSON) to be able to define default options for commands.

Add a clear and concise description of what you want to happen.

    app = typer.Typer()
    app.load_options("options.yaml")

    @app.command()
    def command1(option1:int=0, option2:str=""):
        ...

    @app.command()
    def command2(something:str, count:int=0):
        ...
 
    if __name__ == "__main__":
        app()

The options.yaml file would look something like this:

    command1:
        option1: 1234
    command2:
        something: value
        count: 17

The suggested order of precedence is:

  1. command line options
  2. environment variables
  3. loaded options
  4. defaults from function signature

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.