Open kootenpv opened 4 years ago
How to know when the list stops then? The comma is more explicit. Or you can make it hackable, like a separator definition in a doctring.
It's already implemented, it's done by spaces actually as is custom in bash:
--some-list 1 2 3 4
and you have to use quotes to separate the args if a space occurs in them.
I think it would be difficult to implement in a different way.
Just saw two issues with list options/arguments:
argparse
, it is allowed to add constraint requiring passing at least one item to a list option, through nargs='+'
. From source code seems that it's possible to indicate "1 or more" constraint through giving default value (a non-empty collection) as a hint, but that only matters in descriptions shown on help doc, and the generated parser doesn't prevent users from passing an empty list without getting warned, different from argparse
's behavior on nargs='+'
constraint (stop and show violation of the constraint).Trailing positional arguments (variable number of arguments, or *args
in general) ~is not supported~ get skipped instead of turning into "0 or more" constraint (Updated: 2022/08/25). One common example for this kind of design is passing multiple targets into rm
command without declaring option name (rm file ...
).
Currently if I write:
# my_application.py
@cli()
def command(item: str, *items: str):
pass
and run my_application command --help
, from help text it's pretty clear that items
gets ignored.
My current use case for this type of requirement is appending a set of names to internal reference list for the application. Take the command
function above as example, the generated application should behave like this:
my_application command item_01
will append item_01
to the internal list.
my_application command item_01 item_02 item_03
will append item_01
, item_02
, and item_03
to the internal list.
my_application command
will ring alert and print help doc again to correct usage.
Also it's kind of awkward to use a list parameter with default value to indicate variable number of arguments with "1 or more" constraint like this...
def command(items: List[str] = ['default_00']):
# ...
... since Python linters (such as PyCharm's builtin linter) often captures mutable default arguments to avoid possible runtime errors.
Of course workaround exists, like using tuple, which is how *args
exactly works in Python:
from typing import Tuple
def command(items: Tuple[str, ...] = ('default_00', ))
# ...
Or even using more generic type in typing annotation:
from collections.abc import Sequence
def command(items: Sequence[str] = ('default_00', ))
# ...
But to me they're not so intuitive as the one proposed earlier:
def command(
item: str, # At least one string
*items: str # and more are accepted as well
):
# ...
Plus in my use case there's no default value when users pass nothing to the list argument. They should be warned instead of accidentally appending extra data (from default values) to the internal list.
Yea I have been using tuple
for this which pleases mypy.
If you want to do verification of how many elements, it's best to use that in the function itself (as also the function could be called not as cli
but from within a program)
@cli
def fn(items: tuple[str, ...] = ()):
if not items:
raise ValueError()
Just realized that cliche
still prints "1 or more" in help document even if passing empty collection as default value for an option/kwarg... The problem is usage generated from help doc will become -i [ITEMS [ITEMS ...]]
, which contradicts with the "1 or more" statement.
Also flag is strictly required for this approach, so you'll need to write fn -i item_01 item_02 item_03
instead of fn item_01 item_02 item_03
, which is kind of inconvenient.
In this case I'll prefer implementing "1 or more" positional argument constraint in this way as workaround:
# my_application.py
@cli
def command(
item: str,
items: Tuple[str, ...],
):
# ...
my_application command
does not work.my_application command item_01 item_02 item_03
ArgumentParser
instance has behavior that aligns with help document (for the fn
example above, the internal ArgumentParser
instance accepts empty collection for items
, while help document stating at least 1 element is required)item [items [items ...]]
, instead of item [items ...]
, or even item ...
)Another problem when defining positional arguments in this way below (should be invalid since we don't know how to set the boundary between items
and some_other_items
on trailing positional arguments, or one of the list arguments should be converted to option with "0 or more" constraint):
# my_application.py
@cli
def command(
item: str,
items: Tuple[str, ...],
some_other_items: Tuple[str, ...],
):
"""
:param item: An item
:param items: Additional items
:param some_other_items: Some other items
"""
# ...
Generated usage (from ArgumentParser
?):
my_application command [-h] [--notraceback] [--cli] [--pdb] [--timing] [--raw] [-s]
Positional argument section in help doc:
POSITIONAL ARGUMENTS:
item |str| An item
items |0 or more of: str| Additional items
some-other-items |0 or more of: str| Some other items
Determine the right format, probably space separated instead of comma separated though: