Argument value returned by callback function is list of None #629
-
First Check
Commit to Help
Example Codefrom devtools import debug
import typer
from typing import Annotated
from typing import List
from enum import Enum
app = typer.Typer()
class Models(str, Enum):
all = 'all'
a = 'a'
b = 'b'
c = 'c'
def model_callback(
ctx: typer.Context,
model: List[Models],
param: typer.CallbackParam
) -> List[Models]:
"""Callbac function
"""
print(f'Inside _parse_model():')
print(f'ctx : {ctx}')
print(f'param : {param}')
print(f'model : {model}')
debug(locals())
# if ctx.resilient_parsing:
# return
if Models.all in model:
# Return all models except for the 'all' itself!
model = [model for model in Models if model != Models.all]
debug(locals())
print(f"_parse_model() will return: {model}")
debug(locals())
return model
@app.command('position', no_args_is_help=True, help='Calculate')
def position(
model: Annotated[List[Models], typer.Option(
default='--model',
show_default=True,
show_choices=True,
case_sensitive=False,
callback=model_callback,
help="Model(s)")] = [Models.a],
):
"""
"""
# print(f'Model(s) : {model}')
# debug(locals())
# Why does the callback function `_parse_model` not work?
# if Models.all in model:
# # Return all models except for the 'all' itself!
# model = [model for model in Models if model != Models.all]
debug(locals()) DescriptionUsing the example code, ❯ typer geometry.py run --model c correctly returns geometry.py:61 position
locals(): {
'model': [
<Models.c: 'c'>,
],
} (dict) len=1 and ❯ typer geometry.py run --model c --model a correctly returns geometry.py:61 position
locals(): {
'model': [
<Models.c: 'c'>,
<Models.a: 'a'>,
],
} (dict) len=1 However, ❯ typer geometry.py run --model all does not return, as expected, all models defined in the class
A bit more detailed: ❯ typer geometry.py run --model all
Inside _parse_model():
ctx : <click.core.Context object at 0x7f52ba16d330>
param : <TyperOption model>
model : [<Models.all: 'all'>]
geometry.py:28 model_callback
locals(): {
'ctx': <click.core.Context object at 0x7f52ba16d330>,
'model': [
<Models.all: 'all'>,
],
'param': <TyperOption model>,
} (dict) len=3
geometry.py:36 model_callback
locals(): {
'ctx': <click.core.Context object at 0x7f52ba16d330>,
'model': [
<Models.a: 'a'>,
<Models.b: 'b'>,
<Models.c: 'c'>,
],
'param': <TyperOption model>,
} (dict) len=3
_parse_model() will return: [<Models.a: 'a'>, <Models.b: 'b'>, <Models.c: 'c'>]
geometry.py:39 model_callback
locals(): {
'ctx': <click.core.Context object at 0x7f52ba16d330>,
'model': [
<Models.a: 'a'>,
<Models.b: 'b'>,
<Models.c: 'c'>,
],
'param': <TyperOption model>,
} (dict) len=3
geometry.py:59 position
locals(): {
'model': [
None,
None,
None,
],
} (dict) len=1 It seems the callback function works, yet the function What is wanted, works with an @app.command('position', no_args_is_help=True, help='Calculate')
def position(
model: Annotated[List[Models], typer.Option(
default='--model',
show_default=True,
show_choices=True,
case_sensitive=False,
help="Model(s)")] = [Models.a],
):
"""
"""
# Why does the callback function `_parse_model` not work?
if Models.all in model:
# Return all models except for the 'all' itself!
model = [model for model in Models if model != Models.all]
debug(locals()) correctly returns ❯ typer geometry.py run --model all
geometry.py:58 position
locals(): {
'model': [
<Models.a: 'a'>,
<Models.b: 'b'>,
<Models.c: 'c'>,
],
} (dict) len=1 What is asked here, is to have a cleaner function body and let the selection of the models be done by the callback function. Operating SystemLinux Operating System DetailsEndeavourOS Linux x86_64 Typer Version0.9.0 Python VersionPython 3.10.9 Additional ContextNo response |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
Hi Nikos you probably have solved this by now, but anyhow :) According to the docs, "You can declare a CLI option that can be used multiple times, and then get all the values." (src) OR "You can also declare a CLI option that takes several values of different types. You can set the number of values and types to anything you want, but it has to be a fixed number of values." (src) Therefore if you want users to be able to specify a variable number of models you can only use the first option, I.e. allow invocations like the following:
Now, as far as the code goes, you can implement this logic using callbacks etc, but honestly why overcomplicate things? Define a simple function that does not need any context objects and other shenanigans and call it a day: from enum import Enum
from typing import Annotated
import typer
app = typer.Typer()
class Model(str, Enum):
all = "all"
a = "a"
b = "b"
c = "c"
def resolve_models(models: list[Model]) -> Model:
if Model.all in models:
models = [model for model in Model if model != Model.all]
return models
@app.command(no_args_is_help=True, help="Calculate")
def position(model: Annotated[list[Model], typer.Option(help="Choose Model(s)")]) -> None:
models = resolve_models(model)
print(models)
if __name__ == "__main__":
app() So, executing the following:
will result in:
Just take note, that I have changed some variable names compared to your code (e.g. renamed |
Beta Was this translation helpful? Give feedback.
-
UPDATED Using ❯ pip freeze |ag typer
typer==0.9.0 it will work if the input type is set to ❯ python geometry.py
Usage: geometry.py [OPTIONS] MODEL_X...
Calculate
Arguments:
MODEL_X... Model(s) [required]
Options:
--model TEXT Model(s) [default: Model.a]
--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. and python geometry.py position a
python geometry.py position a --model b
python geometry.py position a --model b --model c
python geometry.py position a b --model b --model c
python geometry.py position a b --model b --model c --model all
python geometry.py position a all --model b --model c --model all
python geometry.py position all --model b --model all will return Argument : [<Model.a: 'a'>]
Option : []
Argument : [<Model.a: 'a'>]
Option : [<Model.b: 'b'>]
Argument : [<Model.a: 'a'>]
Option : [<Model.b: 'b'>, <Model.c: 'c'>]
Argument : [<Model.a: 'a'>, <Model.b: 'b'>]
Option : [<Model.b: 'b'>, <Model.c: 'c'>]
Argument : [<Model.a: 'a'>, <Model.b: 'b'>]
Option : [<Model.a: 'a'>, <Model.b: 'b'>, <Model.c: 'c'>]
Argument : [<Model.a: 'a'>, <Model.b: 'b'>, <Model.c: 'c'>]
Option : [<Model.a: 'a'>, <Model.b: 'b'>, <Model.c: 'c'>]
Argument : [<Model.a: 'a'>, <Model.b: 'b'>, <Model.c: 'c'>]
Option : [<Model.a: 'a'>, <Model.b: 'b'>, <Model.c: 'c'>] The source : from devtools import debug
import typer
from typing import Annotated
from typing import List
from enum import Enum
app = typer.Typer(
add_completion=True,
add_help_option=True,
no_args_is_help=True,
rich_markup_mode="rich",
help=f"Example command with callback function returning a list",
)
class Model(str, Enum):
all = 'all'
a = 'a'
b = 'b'
c = 'c'
def model_callback(
model: str,
) -> List[Model]:
"""Callback function"""
model = [Model(m) for m in model if m in Model.__members__]
if Model.all in model:
model = [model for model in Model if model != Model.all]
return model
@app.command('position', no_args_is_help=True, help='Calculate')
def position(
model_x: Annotated[List[str], typer.Argument(
show_default=True,
show_choices=True,
case_sensitive=False,
callback=model_callback,
help="Model(s)")],
model: Annotated[List[str], typer.Option(
default='--model',
show_default=True,
show_choices=True,
case_sensitive=False,
callback=model_callback,
help="Model(s)")] = [Model.a],
) -> None:
"""
"""
print(f'Argument : {model_x}')
print(f'Option : {model}')
if __name__ == "__main__":
app() |
Beta Was this translation helpful? Give feedback.
UPDATED Using
❯ pip freeze |ag typer typer==0.9.0
it will work if the input type is set to
List[str]
. Examples :and