cappa icon indicating copy to clipboard operation
cappa copied to clipboard

case insensitive args

Open junapur opened this issue 3 months ago • 3 comments

would it be possible to add a case_insensitive parameter to Arg?

from dataclasses import dataclass
from typing import Annotated
import cappa

@dataclass
class Command:
    color: Annotated[
        str,
        cappa.Arg(
            choices=["Red", "Green", "Blue"],
            case_insensitive=True
        )
    ]

    def __call__(self) -> None:
        print(self.color)

# with this, the following would be valid:
# my_cli --color blue
# Blue

i know you can currently specify something like parse=[str.title, cappa.default_parse], but it doesn't feel as good.

junapur avatar Nov 19 '25 13:11 junapur

What i dont like about case_insensitive is the it's a globally available parameter that only applies to a small subset of the total kinds of types. For example, would you expect a list[str] to react to case_insensitive? I think...I probably would.

I would think color: Annotated[Literal["Red", "Green", "Blue"], cappa.Arg(parse=str.title)] would be optimal (which doesn't work as might be nice, today).

And for the hopefully less common case, I might add an Arg(default_parse=False) flag to opt out of the default parse behavior.


in fact today color: Annotated[str, cappa.Arg(choices=["Red", "Green", "Blue"], parse=str.title)] seems to already work how I might hope the Literal usage might work. Choices are internally handled exactly as Literals, but they're currently applied on top of the parse function regardless of the user's supplied parse function (unlike Literal).

DanCardin avatar Nov 19 '25 17:11 DanCardin

What i dont like about case_insensitive is the it's a globally available parameter that only applies to a small subset of the total kinds of types.

i didn't think about that, i agree with your opinion.

also, would the changes in #249 need me to change this code i have?

from dataclasses import dataclass
from typing import Annotated, Literal, Optional, TypeVar

import cappa

T = TypeVar("T")


def parse_none(value: T) -> Optional[T]:
    if value == "None":
        return None

    return value


@dataclass
class Command:
    threshold: Annotated[
        float | Literal["None"],
        cappa.Arg(
            short=True,
            long=True,
            default=0.5,
            parse=[
                str.title,
                cappa.default_parse,
                parse_none,
            ],
        ),
    ]

junapur avatar Nov 20 '25 04:11 junapur

what i'm doing will cause the default parse path to become: parse=[str.title, cappa.default_parse, parse_none, cappa.default_parse], unless you provide parse_inference=False.

With that said, I think your threshold type doesn't seem right, seems like it should be float | None, which would let you do parse=[str.title, parse_none]. or just have parse=parse_none and if value.lower() == "none":...

DanCardin avatar Nov 20 '25 16:11 DanCardin

Should be included in v0.31.0.

DanCardin avatar Dec 04 '25 15:12 DanCardin