without_cli¶
Command-line parsing as values: typed tokens that are the parse, the help, and the read at once.
without_cli
¶
Answered
dataclass
¶
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.
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.
Rejected
dataclass
¶
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.
Arm
dataclass
¶
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.
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
¶
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.
Converter
dataclass
¶
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.)
FromFile
dataclass
¶
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.
Capture
dataclass
¶
Streams
dataclass
¶
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.
standard
classmethod
¶
standard() -> Streams
The real process streams. The one place sys is touched.
captured
classmethod
¶
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.
Args
dataclass
¶
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.
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.
ExtractionError
¶
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.
Extractor
dataclass
¶
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.
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.
Positional
dataclass
¶
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.
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.
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
¶
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,
*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
¶
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
¶
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 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
¶
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, 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]
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
¶
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.