Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ paths are considered internals and can change in minor and patch releases.
v4.38.0 (2025-02-??)
--------------------

Added
^^^^^
- Support ``shtab`` completion of ``Literal`` types (`#693
<https://github.com/omni-us/jsonargparse/pull/693>`__).

Changed
^^^^^^^
- ``validate`` now checks values before required so that errors related to wrong
Expand All @@ -30,6 +35,9 @@ Fixed
- Help incorrectly showing environment variable name for ``--print_shtab``.
- ``add_argument`` raises error when type is assigned with ``action=None``
(`#687 <https://github.com/omni-us/jsonargparse/issues/687>`__).
- ``shtab`` failing when parser has positional arguments (`#693
<https://github.com/omni-us/jsonargparse/pull/693>`__).


v4.37.0 (2025-02-14)
--------------------
Expand Down
34 changes: 19 additions & 15 deletions jsonargparse/_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from enum import Enum
from importlib.util import find_spec
from subprocess import PIPE, Popen
from typing import List, Union
from typing import List, Literal, Union

from ._actions import ActionConfigFile, _ActionConfigLoad, _ActionHelpClassPath, remove_actions
from ._parameter_resolvers import get_signature_parameters
Expand Down Expand Up @@ -162,7 +162,8 @@ def shtab_prepare_action(action, parser) -> None:
choices = None
if isinstance(action, ActionTypeHint):
skip = getattr(action, "sub_add_kwargs", {}).get("skip", set())
choices = get_typehint_choices(action._typehint, action.option_strings[0], parser, skip)
prefix = action.option_strings[0] if action.option_strings else None
choices = get_typehint_choices(action._typehint, prefix, parser, skip)
if shtab_shell.get() == "bash":
message = f"Expected type: {type_to_str(action._typehint)}"
add_bash_typehint_completion(parser, action, message, choices)
Expand Down Expand Up @@ -232,7 +233,9 @@ def get_typehint_choices(typehint, prefix, parser, skip, choices=None, added_sub
choices.extend(list(typehint.__members__))
else:
origin = get_typehint_origin(typehint)
if origin == Union:
if origin == Literal:
choices.extend([str(a) for a in typehint.__args__ if isinstance(a, (str, int, float))])
elif origin == Union:
for subtype in typehint.__args__:
if subtype in added_subclasses or subtype is object:
continue
Expand Down Expand Up @@ -273,18 +276,19 @@ def add_subactions_and_get_subclass_choices(typehint, prefix, parser, skip, adde
init_args[param.name].append(param.annotation)
subclasses[param.name].append(path.rsplit(".", 1)[-1])

for name, subtypes in init_args.items():
option_string = f"{prefix}.{name}"
if option_string not in parser._option_string_actions:
action = parser.add_argument(option_string)
for subtype in unique(subtypes):
subchoices = get_typehint_choices(subtype, option_string, parser, skip, None, added_subclasses)
if shtab_shell.get() == "bash":
message = f"Expected type: {type_to_str(subtype)}; "
message += f"Accepted by subclasses: {', '.join(subclasses[name])}"
add_bash_typehint_completion(parser, action, message, subchoices)
elif subchoices:
action.choices = subchoices
if prefix is not None:
for name, subtypes in init_args.items():
option_string = f"{prefix}.{name}"
if option_string not in parser._option_string_actions:
action = parser.add_argument(option_string)
for subtype in unique(subtypes):
subchoices = get_typehint_choices(subtype, option_string, parser, skip, None, added_subclasses)
if shtab_shell.get() == "bash":
message = f"Expected type: {type_to_str(subtype)}; "
message += f"Accepted by subclasses: {', '.join(subclasses[name])}"
add_bash_typehint_completion(parser, action, message, subchoices)
elif subchoices:
action.choices = subchoices

return choices

Expand Down
28 changes: 27 additions & 1 deletion jsonargparse_tests/test_shtab.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from importlib.util import find_spec
from os import PathLike
from pathlib import Path
from typing import Any, Callable, Optional, Union
from typing import Any, Callable, Literal, Optional, Union
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -146,6 +146,19 @@ def test_bash_optional_enum(parser, subtests):
)


def test_bash_literal(parser, subtests):
typehint = Optional[Literal["one", "two"]]
parser.add_argument("--literal", type=typehint)
assert_bash_typehint_completions(
subtests,
parser,
[
("literal", typehint, "", ["one", "two", "null"], "3/3"),
("literal", typehint, "t", ["two"], "1/3"),
],
)


def test_bash_union(parser, subtests):
typehint = Optional[Union[bool, AXEnum]]
parser.add_argument("--union", type=typehint)
Expand All @@ -159,6 +172,19 @@ def test_bash_union(parser, subtests):
)


def test_bash_positional(parser, subtests):
typehint = Literal["Alice", "Bob"]
parser.add_argument("name", type=typehint)
assert_bash_typehint_completions(
subtests,
parser,
[
("name", typehint, "", ["Alice", "Bob"], "2/2"),
("name", typehint, "Al", ["Alice"], "1/2"),
],
)


def test_bash_config(parser):
parser.add_argument("--cfg", action="config")
shtab_script = get_shtab_script(parser, "bash")
Expand Down