Skip to content

without_cli

Command-line parsing as values: typed tokens that are the parse, the help, and the read at once.

without_cli

BOOL module-attribute

BOOL: Converter[bool] = Converter(
    metavar="BOOL", parse=parse_boolean
)

FLOAT module-attribute

FLOAT: Converter[float] = Converter(
    metavar="FLOAT", parse=float
)

INT module-attribute

INT: Converter[int] = Converter(metavar='INT', parse=int)

PATH module-attribute

PATH: Converter[Path] = Converter(
    metavar="PATH", parse=Path
)

STR module-attribute

STR: Converter[str] = Converter(metavar='STR', parse=str)

UUID module-attribute

UUID: Converter[UUID] = Converter(
    metavar="UUID", parse=uuid.UUID
)

ANSWERED module-attribute

ANSWERED = (*HELP, VERSION)

Outcome

Outcome = Bound[T] | Answered | Rejected

Action

Action = Callable[[T], Returned]

Source

Source = FromEnv | FromFile

Parameter

Parameter = Positional | Option

Answered dataclass

Answered(spelling: str, path: tuple[Node, ...])

The scan met one of the caller's answered spellings, so nothing was bound.

This layer holds no opinion about what any spelling means: it reports which one it met and which level it was addressed to, and the shell decides whether --help prints usage, --version reads node.version, or --license prints something else entirely. That is why there is one outcome here rather than one per flag, and why adding a flag needs no change below run.

Stopping has to happen in the scan even though deciding does not, because only the scan knows whether a token is a flag or the value of the option before it, and because a level's required options have not been checked yet: that is what lets prog db migrate --help answer instead of complaining about the --dsn you were asking how to spell.

spelling instance-attribute

spelling: str

path instance-attribute

path: tuple[Node, ...]

node property

node: Node

The level the spelling was addressed to, whose version a shell may read.

usage property

usage: Usage

That level's usage, for a shell answering with help.

Bound dataclass

Bound(action: Action[_T_contra])

Bases: Generic[_T_contra]

A valid invocation: every value parsed, nothing run.

Reaching this proves the command line was good, because all extraction has already happened. That is what lets a program open its resources only for invocations that were going to work, and it is why run never has to report a usage error out of the middle of a command.

action class-attribute instance-attribute

action: Action[_T_contra] = field(compare=False)

Rejected dataclass

Rejected(message: str, usage: Usage)

The invocation was not valid, with the usage of the level that refused it.

Carrying the Usage rather than a formatted string is what lets a program decide how much to show and where: run's default is the synopsis plus a pointer to --help, and an application that wants the whole help text on a mistake renders usage in full instead.

message instance-attribute

message: str

usage instance-attribute

usage: Usage

Arm dataclass

Arm(
    node: Node,
    resolve: Callable[
        [tuple[Level, ...]], Action[_T_contra]
    ],
)

Bases: Generic[_T_contra]

One selectable command path: its description, and how to turn a bound invocation into the thing to run.

An arm is a self-contained value. It carries its own name, tokens, usage, and behaviour, so a package can ship one and a consumer can place it anywhere in a tree without the package knowing where it landed, and nothing has to be registered anywhere for it to work.

resolve runs at parse time and does all the parsing: it returns an Action that still needs the state its enclosing group builds, so by the time anything is opened every value has already been proven to parse.

node instance-attribute

node: Node

resolve class-attribute instance-attribute

resolve: Callable[
    [tuple[Level, ...]], Action[_T_contra]
] = field(compare=False)

DeclarationError

Bases: ValueError

A command tree that cannot be assembled, raised where it is written.

Authorship decides strictness: these are mistakes in code the application author owns (a variadic positional that is not last, two commands with one name), so they fail loudly at import rather than becoming a confusing parse at runtime.

Level dataclass

Level(name: str, args: Args)

One command-path level's bound values, as the binder produced them.

name instance-attribute

name: str

args instance-attribute

args: Args

Node dataclass

Node(
    name: str,
    summary: str = "",
    parameters: tuple[Parameter, ...] = (),
    children: tuple[Node, ...] = (),
    version: str | None = None,
)

The description of one level of a command path: what it is called, what it parses, and what sits under it.

Deliberately free of types and behaviour, because three consumers need the shape and none of them needs the state type: the binder reads parameters to know each option's arity, usage renders the whole tree, and completion walks it. Keeping the description separate from the resolver is also what lets a group nest arms whose state type differs from its own without an existential type Python cannot express.

version is what this level answers --version with, and None means it answers nothing: the flag is then an unknown option like any other. Per level rather than per program, so an arm shipped by another package reports that package's version wherever it is mounted.

name instance-attribute

name: str

summary class-attribute instance-attribute

summary: str = ''

parameters class-attribute instance-attribute

parameters: tuple[Parameter, ...] = ()

children class-attribute instance-attribute

children: tuple[Node, ...] = ()

version class-attribute instance-attribute

version: str | None = None

options property

options: tuple[Option, ...]

positionals property

positionals: tuple[Positional, ...]

child

child(name: str) -> Node | None

Converter dataclass

Converter(metavar: str, parse: Callable[[str], _V_co])

Bases: Generic[_V_co]

A single-string parser paired with the placeholder that names it in usage.

parse turns one raw token into a typed value, raising ValueError to reject it. A rejection is an ordinary outcome: the extractor that applied it raises ExtractionError, which parse_argv turns into a Rejected naming the parameter, never a traceback.

metavar is the converter's identity and the placeholder shown in usage (--port PORT uses the option's own name, INT names the type), so a token that reuses a converter value declares its name, parse, type, and usage placeholder exactly once. Equality is by metavar alone, so parse is excluded from comparison; nothing here keys a structure on a converter, so what equality means is a convenience rather than a correctness property. (without-web's converter compares parse as well, because there a converter is the routing trie's branch key and two comparing equal merge.)

metavar instance-attribute

metavar: str

parse class-attribute instance-attribute

parse: Callable[[str], _V_co] = field(compare=False)

FromEnv dataclass

FromEnv(name: str)

An environment variable an option falls back to when the command line omits it.

name instance-attribute

name: str

FromFile dataclass

FromFile(path: Path, strip: bool = True)

A file an option falls back to when the command line omits it.

The shape a Docker or Kubernetes secret mount takes: the value is the file's whole contents. strip is on by default because those mounts almost always carry a trailing newline, and a token with one on the end fails authentication somewhere far away from the cause.

A missing file is absence, not an error, so a mount that is not present reads as "not configured" and the option's own parse decides whether that is fatal (once rejects, default does not). A file that exists but cannot be read raises, because that is a broken deployment rather than an unconfigured one.

path instance-attribute

path: Path

strip class-attribute instance-attribute

strip: bool = True

Capture dataclass

Capture(streams: Streams, out: StringIO, err: StringIO)

Streams writing into buffers a test can read back, from Streams.captured.

streams instance-attribute

streams: Streams

out instance-attribute

out: StringIO

err instance-attribute

err: StringIO

stdout property

stdout: str

stderr property

stderr: str

Streams dataclass

Streams(
    stdin: Iterable[str], stdout: Writer, stderr: Writer
)

The three standard streams, handed to every command as an argument.

Injecting them rather than letting a command reach for sys.stdout is what makes output testable without capturing a process: a test passes Streams.captured() and reads the buffers back, with no module global monkeypatched and no subprocess run. It is also what keeps this layer out of the encoding decision, since without-cli never writes anything a command did not write itself.

stdin is an iterable of chunks rather than a string, because input arrives over time: a filter reading a pipe should see each line as it lands, not wait for the writer upstream to close. Iterating the real sys.stdin yields lines; a test supplies a list or a generator and controls the arrival order exactly. It is a consume-once place rather than a value (the same reason without-web passes an inbound stream as an argument instead of making it an extractor), so a command that iterates it twice sees nothing the second time.

Iterating sys.stdin blocks, which for a CLI is usually right (there is nothing else to do) but stalls the event loop for a command that reads input while doing something else. That command wraps this in without_streams.stream_from_blocking, which runs the iteration on a worker thread. The plain iterable stays the type here so the common case pays nothing and without-cli needs no dependency for it.

stdin instance-attribute

stdin: Iterable[str]

stdout instance-attribute

stdout: Writer

stderr instance-attribute

stderr: Writer

standard classmethod

standard() -> Streams

The real process streams. The one place sys is touched.

captured classmethod

captured(stdin: str | Iterable[str] = ()) -> Capture

In-memory streams plus the buffers behind them, for tests.

A bare string is one chunk, which is what a test asserting on whole input wants; pass a list or a generator to control how the input is split and when each piece arrives.

Writer

Bases: Protocol

The part of a text output stream a command writes to.

flush is here because a CLI's stdout is line-buffered on a terminal and block-buffered down a pipe, so a long-running command's progress would otherwise appear all at once when it exits. A command that writes incrementally has to say when its output should be visible, and only it knows.

sys.stdout and io.StringIO both satisfy this as they are, so a test needs no wrapper; flushing a StringIO is a no-op that keeps its contents.

write

write(text: str) -> int

flush

flush() -> None

Args dataclass

Args(
    options: Mapping[str, tuple[str, ...]],
    arguments: Mapping[str, tuple[str, ...]],
)

One command-path level's raw values, bound but not yet parsed.

The read-only context every extractor reads, and the point where the command line, the environment, and files have already been merged: options maps an option's canonical name to the raw values in effect for it (its command-line occurrences, or failing those whichever source supplied one), and arguments maps a positional's name to the tokens the binder assigned it.

Absent is absent: a name with no values does not appear at all, so each token's own parse decides what that means rather than the binder guessing.

options instance-attribute

options: Mapping[str, tuple[str, ...]]

arguments instance-attribute

arguments: Mapping[str, tuple[str, ...]]

Cardinality dataclass

Cardinality(
    parse: Callable[[tuple[str, ...]], _V_co],
    repeatable: bool = False,
    required: bool = False,
)

Bases: Generic[_V_co]

How many raw values an option accepts, and what they parse into.

Keeping the count and the parse in one value is what stops usage from drifting from behaviour: once(INT) is the single place saying the option is required, takes one value, and yields an int, so the help text and the parser cannot disagree about it.

parse class-attribute instance-attribute

parse: Callable[[tuple[str, ...]], _V_co] = field(
    compare=False
)

repeatable class-attribute instance-attribute

repeatable: bool = False

required class-attribute instance-attribute

required: bool = False

ExtractionError

ExtractionError(
    message: str,
    *,
    parameter: str,
    cause: Exception | None = None,
)

Bases: ValueError

An invocation rejected while one of its typed values was being extracted.

The reject signal a token raises when its parse refuses the raw input, gathering at the raise site what a good message needs: parameter names the thing that failed (--port, text) and cause carries the underlying error as a first-class value rather than hiding in __cause__.

Making the boundary one matchable type is what keeps a ValueError raised deeper inside a command from masquerading as a usage error: parse_argv turns this into a Rejected and lets anything else propagate.

parameter instance-attribute

parameter = parameter

cause instance-attribute

cause = cause

Extractor dataclass

Extractor(
    extract: Callable[[Args], _V_co],
    parameters: tuple[Parameter, ...] = (),
)

Bases: Generic[_V_co]

A typed piece of an invocation, paired with the usage it contributes.

extract is a pure Args -> V that raises ExtractionError to reject; it never decides which command runs. The same value carries the parameters it is described by, so a command's usage, its completions, and the binder's own arity table are all recovered from the tokens that parse it: one declaration, several consumers.

Extraction reads only Args, never the program's state, which is what lets every value be parsed before the program builds anything. A Bound therefore proves the whole invocation is valid, and no resource is opened for a command line that was never going to run.

extract instance-attribute

extract: Callable[[Args], _V_co]

parameters class-attribute instance-attribute

parameters: tuple[Parameter, ...] = ()

Option dataclass

Option(
    names: tuple[str, ...],
    metavar: str | None,
    summary: str = "",
    sources: tuple[Source, ...] = (),
    repeatable: bool = False,
    required: bool = False,
)

How an option/flag/count token appears in usage and to the binder.

metavar doubles as the binder's arity signal: None means the option takes no value, so it bundles into -abc and consumes nothing after itself.

names instance-attribute

names: tuple[str, ...]

metavar instance-attribute

metavar: str | None

summary class-attribute instance-attribute

summary: str = ''

sources class-attribute instance-attribute

sources: tuple[Source, ...] = ()

repeatable class-attribute instance-attribute

repeatable: bool = False

required class-attribute instance-attribute

required: bool = False

canonical property

canonical: str

The name this option's values are stored under.

The first long name when there is one, so ("-v", "--verbose") and ("--verbose", "-v") agree on --verbose and the order aliases are declared in stays a presentation choice.

Positional dataclass

Positional(
    name: str,
    summary: str = "",
    variadic: bool = False,
    required: bool = True,
)

How an argument token appears in usage and to the binder.

Mirrors Option's variadic/required pair, because both are read off the same Cardinality: once is required, optional and default are not, and many is the variadic one.

The binder assigns bare tokens to these in declaration order and greedily, so variadic (which takes everything left) is only valid as the last one, and a required positional may not follow an optional one: with one token to hand out, [A] B would give it to A and leave B empty.

name instance-attribute

name: str

summary class-attribute instance-attribute

summary: str = ''

variadic class-attribute instance-attribute

variadic: bool = False

required class-attribute instance-attribute

required: bool = True

metavar property

metavar: str

Usage dataclass

Usage(
    path: tuple[str, ...],
    summary: str,
    positionals: tuple[Positional, ...],
    options: tuple[Option, ...],
    inherited: tuple[Option, ...],
    commands: tuple[tuple[str, str], ...],
)

What one command path looks like, recovered from the tokens that parse it.

A value, not a string, which is the whole point: the plain-text render below is one rendering of it, and a markdown page, a man page, a shell completion script, or a coloured terminal renderer are others, each chosen by whoever is doing the rendering. Nothing here knows about a styling library, so none is on the path every program crosses.

inherited carries the options an ancestor declared, because todos db migrate --help should say that --endpoint exists even though it is spelled before db.

path instance-attribute

path: tuple[str, ...]

summary instance-attribute

summary: str

positionals instance-attribute

positionals: tuple[Positional, ...]

options instance-attribute

options: tuple[Option, ...]

inherited instance-attribute

inherited: tuple[Option, ...]

commands instance-attribute

commands: tuple[tuple[str, str], ...]

invocation property

invocation: str

The one-line synopsis, e.g. todos add [OPTIONS] TEXT [NOTE] [TAGS...].

parse_argv

parse_argv(
    arm: Arm[T],
    *,
    argv: Sequence[str],
    env: Mapping[str, str] = _NO_ENV,
    files: Mapping[Path, str] = _NO_FILES,
    answered: Sequence[str] = (),
) -> Outcome[T]

Turn a command line into a valid invocation, a rejection, or a stop the caller asked for.

A pure, total function of its values, which is what makes the whole parser testable without a process: no sys.argv, no os.environ, no filesystem, no exit, no output. env and files are the already-read contents of an option's fallback sources (see run, which reads them).

Nothing here is magic by default. answered is the caller's list of spellings that should stop the scan and come back as an Answered rather than being parsed, and it is empty unless asked for, so --help means nothing to this function on its own. run passes the conventional set and decides what each one does; a program wanting -?, a help subcommand, or nothing at all passes its own list and this function does not change.

Every value is extracted here, so a Bound proves the invocation is good and nothing has been opened yet when a Rejected comes back.

render_rejection

render_rejection(rejected: Rejected) -> str

The default rendering of a bad command line: what went wrong, then where to look.

Deliberately not the whole help text. A rejection usually means one thing was wrong, and burying that line under fifty lines of options is how a CLI trains people to stop reading its errors.

command

command(
    name: str,
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[[Callable[[T], Returned]], Arm[T]]
command(
    name: str,
    a: Extractor[A],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[[Callable[[T, A], Returned]], Arm[T]]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[[Callable[[T, A, B], Returned]], Arm[T]]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [Callable[[T, A, B, C], Returned]], Arm[T]
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [Callable[[T, A, B, C, D], Returned]], Arm[T]
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [Callable[[T, A, B, C, D, E], Returned]], Arm[T]
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [Callable[[T, A, B, C, D, E, F], Returned]],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [Callable[[T, A, B, C, D, E, F, G], Returned]],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [T, A, B, C, D, E, F, G, H], Returned
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [T, A, B, C, D, E, F, G, H, J],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [T, A, B, C, D, E, F, G, H, J, K],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
                S,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
                S,
                V,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
                S,
                V,
                W,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
                S,
                V,
                W,
                X,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    y: Extractor[Y],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
                S,
                V,
                W,
                X,
                Y,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    y: Extractor[Y],
    z: Extractor[Z],
    /,
    *,
    summary: str = ...,
    version: str | None = ...,
) -> Callable[
    [
        Callable[
            [
                T,
                A,
                B,
                C,
                D,
                E,
                F,
                G,
                H,
                J,
                K,
                N,
                P,
                Q,
                R,
                S,
                V,
                W,
                X,
                Y,
                Z,
            ],
            Returned,
        ]
    ],
    Arm[T],
]
command(
    name: str,
    *extractors: AnyExtractor,
    summary: str = "",
    version: str | None = None,
) -> Callable[[Callable[..., Returned]], Arm[Never]]

Bind a handler to a name and a list of tokens, producing an Arm value.

The decorator returns the arm rather than registering it, so assembly stays the explicit commands=(...) on a program or group and a command can be passed around, renamed, or shipped from a package that does not know where it will be mounted.

Each token supplies one argument to the handler, in declaration order, after the two every command receives: the Streams to write to and the state its program built. The overloads tie the token types to those parameters, so a argument("id", once(INT)) paired with a handler expecting a str is a mypy error with no runtime introspection anywhere.

summary is the one line --help shows beside the command's name. Omitting it falls back to the first line of the handler's docstring, so a command documented for the next reader of the code is documented for its user too; passing it explicitly wins, for when those two readers want different words.

version, when given, is what --version answers with here. Omitting it means this command has none and --version is an unknown option.

group

group(
    name: str,
    /,
    *,
    state: Callable[[T], AbstractAsyncContextManager[U]],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    /,
    *,
    state: Callable[
        [T, A], AbstractAsyncContextManager[U]
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    /,
    *,
    state: Callable[
        [T, A, B], AbstractAsyncContextManager[U]
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    /,
    *,
    state: Callable[
        [T, A, B, C], AbstractAsyncContextManager[U]
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    /,
    *,
    state: Callable[
        [T, A, B, C, D],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E, F],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E, F, G],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E, F, G, H],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E, F, G, H, J],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E, F, G, H, J, K],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    /,
    *,
    state: Callable[
        [T, A, B, C, D, E, F, G, H, J, K, N],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
            X,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    y: Extractor[Y],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
            X,
            Y,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    y: Extractor[Y],
    z: Extractor[Z],
    /,
    *,
    state: Callable[
        [
            T,
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
            X,
            Y,
            Z,
        ],
        AbstractAsyncContextManager[U],
    ],
    commands: tuple[Arm[U], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    /,
    *,
    commands: tuple[Arm[T], ...],
    summary: str = ...,
    version: str | None = ...,
) -> Arm[T]
group(
    name: str,
    *extractors: AnyExtractor,
    state: Callable[
        ..., AbstractAsyncContextManager[object]
    ]
    | None = None,
    commands: tuple[Arm[Never], ...],
    summary: str = "",
    version: str | None = None,
) -> Arm[Never]

Gather arms under a name, deriving their state from its parent's and this level's own options.

A group is where a resource lives: state is an async context manager taking the parent's state and this group's parsed options, entered only when one of its children is actually selected and unwound when that child returns. So prog db --dsn ... migrate opens the database, runs migrate, and closes it, while prog status never touches it.

There is no separate root. A tree's top level is an ordinary group whose parent is the shell, which supplies Streams, so the root of a program is an Arm[Streams] and run hands it the streams the same way a group hands its children what it built. A CLI with no shared resource declares no state at all and its commands receive that Streams directly; one that has a resource builds a state carrying the streams onward, by subclassing Streams or by holding one.

Omitting state makes the group pure namespacing: nothing is built and its children see exactly what it was given.

A group declares options but never positionals: a bare token after a group is the name of its subcommand, so the two would be indistinguishable.

version, when given, is what --version answers with at this level. It is per level rather than per program, so prog --version and prog db --version can report the version of whatever package shipped each of them.

source_paths

source_paths(node: Node) -> tuple[Path, ...]

Every file the whole tree's options name, for the shell to read before parsing.

Recovered from the same sources the options parse from, so a secret mount is declared exactly once and adding one changes nothing else.

The whole tree, so every mount is read on every invocation, including one that selects a command sharing none of them. That is the price of parse_argv being pure: the paths are known before parsing, but which level the command line selects is not, so reading only what the invocation needs would mean putting the filesystem back inside the parse.

choice

choice(enum: type[E]) -> Converter[E]

One member of enum, spelled on the command line as that member's value.

Matching on the value rather than the member name is what lets the shell spelling and the Python identifier differ, which they usually want to (--log-level warning for LogLevel.WARNING). Values are compared as text, so a StrEnum, an Enum with string values, and an IntEnum all work.

The placeholder is the alternation of the values ([dev|prod]), so the enum is the single place its members, their spellings, and the way --help and a rejection name them are declared.

run

run(
    program: Arm[Streams],
    *,
    argv: Sequence[str] | None = None,
    env: Mapping[str, str] | None = None,
    streams: Streams | None = None,
    files: Mapping[Path, str] | None = None,
) -> int

Parse a command line, run what it selected, and return the exit code.

The imperative shell, and the only place this package reads sys.argv, reads the environment, touches the filesystem, or starts an event loop. Every one of those is an argument with a real default, so a test drives a whole program by passing values (run(app, argv=[...], env={...}, streams=capture.streams)) with nothing patched and no subprocess.

It returns rather than exits, so the caller keeps the continuation: raise SystemExit(run(app)) is the conventional entry point, and a program that wants to do something else after a command finishes simply does.

The default policy is the obvious one: help and a version to stdout with 0, a bad command line to stderr with 2, otherwise the command's own code. This is also the only place that knows -h, --help, and --version mean anything: it names them in ANSWERED and interprets what comes back, so an application wanting different answers calls parse_argv and matches the outcome itself, which costs it this function and nothing else.

read_files

read_files(paths: Iterable[Path]) -> Mapping[Path, str]

Read the files a spec names, skipping the ones that are absent.

The imperative half of source resolution, and the only I/O parsing needs: the shell reads once at the boundary and hands the result to parse_argv as a value, so parsing stays pure and a test supplies a mapping instead of a filesystem.

A path named by several options is read once, so where a mount is shared the number of reads follows the files rather than the tokens declaring them.

lines

lines(chunks: Iterable[str]) -> Iterator[str]

Re-split a stream of arbitrary chunks into lines, keeping the terminators.

Iterating sys.stdin already yields lines, but a chunk from a pipe, a socket, or a test can split anywhere, so a command that means "per line" says so with this rather than assuming its chunks arrived pre-split. A trailing fragment with no newline is yielded when the input ends.

argument

argument(
    name: str,
    cardinality: Cardinality[V],
    *,
    summary: str = "",
) -> Extractor[V]

A positional argument, parsed into V.

Takes the same cardinality vocabulary an option does, so one set of words describes both halves of a command line: once is the required single value, optional and default say what an omitted one becomes, and many takes every remaining token (prog cp SRC DST against prog rm PATHS...).

Positionals are assigned in declaration order, so this value is both the usage entry and the typed read, declared exactly once. A many argument consumes the rest of the command line, so it is valid only as the last positional, and a required one may not follow an optional one; command refuses either layout where it is written.

count

count(
    names: str | tuple[str, ...],
    *,
    sources: tuple[Source, ...] = (),
    summary: str = "",
) -> Extractor[int]

How many times a valueless switch appeared, so -vvv is 3.

Each command-line occurrence contributes 1 and a source contributes its own number, so -vv and VERBOSE=2 reach the command as the same value.

default

default(
    value: V, converter: Converter[V]
) -> Cardinality[V]

One value or none, yielding value when absent. A repeat is still a rejection.

flag

flag(
    names: str | tuple[str, ...],
    *,
    sources: tuple[Source, ...] = (),
    summary: str = "",
) -> Extractor[bool]

A valueless switch: True when present, False when absent.

A source supplies a spelling rather than a presence (DEBUG=false), and the last value decides, which is what lets an environment variable turn a flag off as well as on.

into

into(
    make: Callable[[A], M], a: Extractor[A]
) -> Extractor[M]
into(
    make: Callable[[A, B], M],
    a: Extractor[A],
    b: Extractor[B],
) -> Extractor[M]
into(
    make: Callable[[A, B, C], M],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
) -> Extractor[M]
into(
    make: Callable[[A, B, C, D], M],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
) -> Extractor[M]
into(
    make: Callable[[A, B, C, D, E], M],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
) -> Extractor[M]
into(
    make: Callable[[A, B, C, D, E, F], M],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
) -> Extractor[M]
into(
    make: Callable[[A, B, C, D, E, F, G], M],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
) -> Extractor[M]
into(
    make: Callable[[A, B, C, D, E, F, G, H], M],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
) -> Extractor[M]
into(
    make: Callable[
        [A, B, C, D, E, F, G, H, J], M
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
) -> Extractor[M]
into(
    make: Callable[
        [A, B, C, D, E, F, G, H, J, K], M
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
) -> Extractor[M]
into(
    make: Callable[
        [A, B, C, D, E, F, G, H, J, K, N], M
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
) -> Extractor[M]
into(
    make: Callable[
        [A, B, C, D, E, F, G, H, J, K, N, P], M
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
            X,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
            X,
            Y,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    y: Extractor[Y],
) -> Extractor[M]
into(
    make: Callable[
        [
            A,
            B,
            C,
            D,
            E,
            F,
            G,
            H,
            J,
            K,
            N,
            P,
            Q,
            R,
            S,
            V,
            W,
            X,
            Y,
            Z,
        ],
        M,
    ],
    a: Extractor[A],
    b: Extractor[B],
    c: Extractor[C],
    d: Extractor[D],
    e: Extractor[E],
    f: Extractor[F],
    g: Extractor[G],
    h: Extractor[H],
    j: Extractor[J],
    k: Extractor[K],
    n: Extractor[N],
    p: Extractor[P],
    q: Extractor[Q],
    r: Extractor[R],
    s: Extractor[S],
    v: Extractor[V],
    w: Extractor[W],
    x: Extractor[X],
    y: Extractor[Y],
    z: Extractor[Z],
) -> Extractor[M]
into(
    make: Callable[..., M], *extractors: AnyExtractor
) -> Extractor[M]

Combine several tokens into one that builds a typed value.

The escape hatch from a command's token-arity ceiling, and the way to parse a group of inputs into one model: each extractor supplies one positional argument to make, in order, with the types tied so a mismatch is a mypy error. The constituents' usage entries carry through, so combining changes nothing about how the command is described.

many

many(
    converter: Converter[V],
) -> Cardinality[tuple[V, ...]]

Every value the option was given, in order, possibly none.

A source supplies at most one raw value, so splitting TAGS=a,b into two is this converter's business rather than the source's; no separator is baked in anywhere below here.

once

once(converter: Converter[V]) -> Cardinality[V]

Exactly one value, required.

Rejects when the option is absent everywhere and when it is repeated, since a repeated singleton is an ambiguity rather than a value to quietly resolve.

option

option(
    names: str | tuple[str, ...],
    cardinality: Cardinality[V],
    *,
    metavar: str | None = None,
    sources: tuple[Source, ...] = (),
    summary: str = "",
) -> Extractor[V]

A named option, parsed into V.

cardinality (once, optional, default, many) owns both how many values are accepted and what they become, so the usage line and the parse cannot disagree. sources are consulted in order when the command line omits the option entirely and the first that holds a value wins; because they feed the same cardinality.parse, a value from the environment and one from the command line are validated identically.

optional

optional(
    converter: Converter[V],
) -> Cardinality[V | None]

One value or none, yielding None when absent. A repeat is still a rejection.

render

render(described: Usage) -> str

The plain-text rendering of a Usage, and deliberately the only one shipped.

Everything here is str: no colour, no width detection, no styling dependency. A program that wants those renders the same Usage value differently, which is a choice it makes rather than one this layer makes for it.