tiangolo / typer Goto Github PK
View Code? Open in Web Editor NEWTyper, build great CLIs. Easy to code. Based on Python type hints.
Home Page: https://typer.tiangolo.com/
License: MIT License
Typer, build great CLIs. Easy to code. Based on Python type hints.
Home Page: https://typer.tiangolo.com/
License: MIT License
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'
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!
How can I check what command or subcommand was called within a callback?
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)
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
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.
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.
Because everything is type annotated in the code base, I believe this is as simple as adding a py.typed
file to the distribution.
Is it possible to add a new field to the OAuth2PasswordRequestForm
?
I need to add a new optional field called new_password
in the following Request Body
:
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
:
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
):
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.
Steps to reproduce the behavior with a minimum self-contained file.
Replace each part with your own scenario:
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"
Able to drop in Typer at the top level to start slowly converting an existing Click app.
If applicable, add screenshots to help explain your problem.
When an option's type is List[str]
, the input gets split by character, as it would if I just called list("string")
Steps to reproduce the behavior with a minimum self-contained file.
Replace each part with your own scenario:
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()
python main.py
Prefix [()]: abcd qwerty
prefix=('a', 'b', 'c', 'd', ' ', 'q', 'w', 'e', 'r', 't', 'y')
Prefix [()]: abcd qwerty
prefix=('abcd', 'qwerty')
or
prefix=('abcd qwerty')
python migrate_dcos/test_strings.py -p abcd -p qwerty
prefix=('abcd', 'qwerty')
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.
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.
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'
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)
PYTHONPATH=/root typer typer_app.main run
.
.
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'
Linux Mint - Python 3.8.2
typer==0.1.1
typer-cli==0.0.8
I would like to use Pydantic Models as type annotations in my Typer CLI program.
Typer would call Model.parse_raw
on the string that was passed to the CLI
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.
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! 👏
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'>
Allow for datetime.date
as input arguments/options to Typer.
It is true that it is possible to get just the date from datetime.datetime
in the current implementation:
%Y-%m-%d
without needing to specify a timedatetime.date
-object with .date()
However, I think that allowing for just the date part is better in some situations:
.date()
datetime.datetime
shows the format "[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]"
Traceback (most recent call last):
...
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: str
https://www.python.org/dev/peps/pep-0563/
Add
from __future__ import annotations
...
I want to be able to replicate the functionality of click-contrib/click-help-colors to add colors to help text for easier readability
import typer
from click_help_colors import HelpColorsGroup
app = typer.Typer(cls=HelpColorsGroup,
help_headers_color='yellow',
help_options_color='green')
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()
However the color does not propagate to sub command help text
Not a big bug but just good to change help message.
__main__.py
--> python3 -m something
Create __main__.py
, put some code there. Try to execute it via python3 -m
:
$ python3 -m something
Usage: __main__.py [OPTIONS] COMMAND [ARGS]...
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.
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 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.
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:
$ 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.
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
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
?
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?
Ideally, promote it to typer.Required
and mention in the docs :)
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:
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!
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?
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?
I want to be able to integrate the generation of the CLI documentation to the documentation I build with Sphinx.
A sphinx plugin seems like the perfect candidate. Something like sphinx-click.
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.
Here is the project I am using Typer with: tfpy. And its documentation.
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" )
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.
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
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.
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?
The code can be found here.
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 ?
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
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.
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.
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()
python -c "import typer; print(typer.__version__)"
python --version
First of all, thank you for your amazing tools, it helps my team and I to build great products !
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/)
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()
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 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.
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.
Write here a clear and concise description of what the bug is.
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()
python main.py --formal John
Error: Got unexpected extra argument (Kike)
Hello John
python -c "import typer; print(typer.__version__)"
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.
I often implement global options such as --env
and --debug
.
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).
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...'.
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.
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.
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:
main
plus_one
), otherwise invoke hello
. If there are more than one functions matched, or if none of the functions matched, report an error instead.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)
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
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?
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()
python main.py --help
Usage: main.py [OPTIONS] NAME
Options:
--help Show this message and exit.
Usage: main.py [OPTIONS] NAME
+ This is a docstring.
Options:
--help Show this message and exit.
The equivalent in click
and plac
would use "This is a docstring"
to describe the command when you run it with --help
.
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?
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.
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
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.
Build command:
$ pyinstaller --onefile my_exe.py --hidden-import=pkg_resources.py2_warn
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.
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()
python main.py forward
...
TypeError: Callback is not a command.
Count: 42
python main.py invoke
Count: <typer.models.OptionInfo object at 0x10e082a10>
Count: 1
It should be possible to use .invoke()
on typer.Context
objects in a manner similar to that described in the click documentation.
Does typer collect all the values given to a dictionary or a tuple?
Thanks
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
Steps to reproduce the behavior with a minimum self-contained file.
Replace each part with your own scenario:
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)
python main.py
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'
Training neural network of type: simple
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.
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.
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'.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.