Args
duho.args
AUTO = _AutoVersion()
module-attribute
Arg = _ty.Annotated
module-attribute
Factory = _ty.Callable[[str], _T]
module-attribute
NOT_DEFINED = _inspect.NOT_DEFINED
module-attribute
NS = _argparse.Namespace
module-attribute
__all__ = ['Append', 'Argument', 'ArgumentBuilder', 'ArgumentMeta', 'Args', 'Arg', 'Choice', 'Cli', 'Cmd', 'command', 'Const', 'Count', 'Extend', 'Factory', 'main', 'Meta', 'NS', 'NOT_DEFINED', 'parse', 'parse_globals', 'print_agent_help', 'print_completion', 'UpdateAction', 'value_sources']
module-attribute
Args(**kwargs)
Bases: Namespace
Source code in src/duho/args.py
1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 | |
Argument
Bases: Protocol
from_type(factory, **kwargs)
classmethod
Source code in src/duho/args.py
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 | |
ArgumentBuilder
Bases: Namespace
action = None
class-attribute
instance-attribute
choices = None
class-attribute
instance-attribute
collection = None
class-attribute
instance-attribute
const = NOT_DEFINED
class-attribute
instance-attribute
default
instance-attribute
env = None
class-attribute
instance-attribute
flags
instance-attribute
help
instance-attribute
metavar = None
class-attribute
instance-attribute
name
instance-attribute
nargs = None
class-attribute
instance-attribute
required = None
class-attribute
instance-attribute
type
instance-attribute
version = None
class-attribute
instance-attribute
add_to_parser(parser)
Source code in src/duho/args.py
1122 1123 1124 1125 1126 1127 1128 1129 1130 | |
convert_layered(raw, *, source)
Convert a raw env/config layer value to this field's Python value.
The env/config layers feed parser.set_defaults directly, bypassing
argparse's own type=/action= handling -- so a layered value must
be converted here to match what CLI parsing of the same field yields.
Three field shapes are handled:
- bool (
self.type is boolor a store_true/BooleanOptionalAction effective action): real bools pass through; strings map via the strict :data:_BOOL_TRUE/:data:_BOOL_FALSEsets (unknown -> error). - collection (
self.collectionset): a string raw becomes a single element wrapped in the collection (FILES=a.txt->["a.txt"], matching one CLI occurrence); a list/tuple/set raw (a TOML array) converts element-wise then coerces to the collection. - scalar: via :meth:
_convert_single.
source names the layer ("env"/"config") for error messages; the
calling resolver wraps any ValueError/TypeError with the field
and variable name.
Source code in src/duho/args.py
964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 | |
ArgumentMeta
Bases: _ProtocolMeta
__instancecheck__(instance)
Source code in src/duho/args.py
757 758 759 | |
Cli(**kwargs)
Bases: Cmd
Application-root layer: an opt-in mixin over Cmd.
A leaf Cmd is lean -- it declares its own CLI fields and a
__call__. A Cli root is the top of an app and additionally
exposes the app-wide, sandwich-named configuration attributes a plain
Cmd does not declare (--version, shell completion, a config
file, a subcommand tree). Opt in by subclassing Cli::
class MyApp(LoggingArgs, Cli):
_version_ = "1.2.3"
_completion_ = True
Cli adds no new runtime behavior for running -- it inherits
Cmd.__call__ unchanged (a data-only Cli that never overrides
__call__ still fails loud when dispatched, exactly like a Cmd).
What it adds is two things:
- Typed, documented app-root class attrs. Every one of these is
already read elsewhere via
getattr(cls, "_x_", default)(args.py/runtime.py), so declaring them here changes no reader -- it only gives them a typed home and a class-level default where one exists. A plainCmdleaves them undeclared; aCliroot is where they belong. - Self-registration (
_register_subcmd_/@subcommand): a leaf command file can attach itself to the root's subcommand tree instead of the root listing every child centrally in_subcommands_. The two mechanisms compose (union + dedup).
LoggingArgs stays orthogonal (a separate data mixin) -- the
batteries-included recipe is class MyApp(LoggingArgs, Cli) (data
mixin first, executable/root base last), NOT a forced bundle. Every
member Cli adds is sandwich-named or dunder, so a Cli subclass's
field namespace stays 100% user-owned (annotated non-underscore attrs
still become CLI fields).
Source code in src/duho/args.py
1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 | |
subcommand(child)
classmethod
Decorator form of :meth:_register_subcmd_.
Lets a command file self-attach to the root::
@MyApp.subcommand
class Deploy(Cmd):
...
Returns child unchanged, so the decorated class keeps its
identity. Equivalent to calling MyApp._register_subcmd_(Deploy).
Source code in src/duho/args.py
1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 | |
Cmd(**kwargs)
Bases: Args
An executable command: a data Args plus the command contract.
Args (Plan 13) is pure data -- a Namespace of parsed values, not
required to run. Cmd adds the executable contract on top:
__call__(self) is the command entrypoint (instance() runs the
command).
The entrypoint is __call__ -- a dunder -- deliberately. A Cmd
subclass's namespace is user-owned: annotated non-underscore attributes
become CLI fields, so a plain method name like main would collide
with a user field main: str (--main). __call__ lives in the
dunder namespace duho's field introspection already skips, so it can
never clash with a declared flag.
A Cmd subclass that does not override __call__ raises
NotImplementedError naming the class when dispatched -- the same
loud-failure spirit as Plan 04's earlier "missing __call__". Data-only
Args subclasses stay non-runnable by design: duho.main rejects them
with a clear error rather than silently no-op'ing (the whole point of the
split is that "runnable" is explicit).
Base-order for the LoggingArgs mixin: class App(LoggingArgs, Cmd)
(data mixin first, executable base last). Both orders resolve the MRO
correctly since LoggingArgs defines no __call__; the recommended
order reads "add logging to a command".
Source code in src/duho/args.py
1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 | |
__call__()
Run the command. Override __call__ in a Cmd subclass.
The base raises NotImplementedError naming the concrete class,
so a Cmd that forgets to implement __call__ fails loud when
dispatched rather than silently doing nothing.
Source code in src/duho/args.py
1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 | |
Meta(help=_META_UNSET, env=_META_UNSET, conflicts=_META_UNSET, conflicts_required=_META_UNSET, group=_META_UNSET, action=_META_UNSET, nargs=_META_UNSET, const=_META_UNSET, choices=_META_UNSET, metavar=_META_UNSET, required=_META_UNSET, type=_META_UNSET, version=_META_UNSET, dest=_META_UNSET, kwargs=_META_UNSET)
dataclass
Typed, typo-safe alternative to NS(...) for field metadata (F5).
NS(...) is an untyped argparse.Namespace: a misspelled key
(NS(hlep="oops")) is silently dropped. Meta declares the known
metadata fields as a dataclass, so an unknown keyword is a TypeError at
class-definition time -- the whole point. Only the fields you set are merged
(each defaults to a private sentinel); everything NS accepts, Meta
accepts, and NS keeps working forever.
Use it exactly where NS goes::
level: Arg[int, Meta(help="verbosity", env="LEVEL")] = 0
("--level",)
Recommended over NS precisely because a typo fails loud instead of
vanishing. The kwargs field is the same raw add_argument escape hatch
NS(kwargs=...) provides.
action = _META_UNSET
class-attribute
instance-attribute
choices = _META_UNSET
class-attribute
instance-attribute
conflicts = _META_UNSET
class-attribute
instance-attribute
conflicts_required = _META_UNSET
class-attribute
instance-attribute
const = _META_UNSET
class-attribute
instance-attribute
dest = _META_UNSET
class-attribute
instance-attribute
env = _META_UNSET
class-attribute
instance-attribute
group = _META_UNSET
class-attribute
instance-attribute
help = _META_UNSET
class-attribute
instance-attribute
kwargs = _META_UNSET
class-attribute
instance-attribute
metavar = _META_UNSET
class-attribute
instance-attribute
nargs = _META_UNSET
class-attribute
instance-attribute
required = _META_UNSET
class-attribute
instance-attribute
type = _META_UNSET
class-attribute
instance-attribute
version = _META_UNSET
class-attribute
instance-attribute
UpdateAction
Bases: Action
Action that updates a dict instead of replacing it.
__call__(parser, namespace, values, option_string=None)
Source code in src/duho/args.py
2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 | |
Append(type=str, **kw)
Create an append-action argument, accumulating repeated flag values.
Explicitly clears nargs: a bare list/list[T] annotation's implicit
builder defaults to action="extend", nargs="" (space-separated), which
would make append() collect a list* per occurrence instead of a scalar.
Source code in src/duho/args.py
2016 2017 2018 2019 2020 2021 2022 2023 | |
Choice(*choices, **kw)
Restrict an argument's accepted values to choices.
Source code in src/duho/args.py
2031 2032 2033 | |
Const(value, **kw)
Create a store_const-action argument that stores value when present.
Source code in src/duho/args.py
2026 2027 2028 | |
Count(**kw)
Create a count-action argument (e.g. -vvv -> 3).
Source code in src/duho/args.py
2011 2012 2013 | |
Extend(split, **kwargs)
Create an extend-action argument with optional string splitting.
list[str]'s own default builder sets nargs="*" (so a plain list
field accepts both --x a --x b and --x a b); combined with a
type that SPLITS one token into several (as this factory's ty
does), that combination double-collects: argparse gathers nargs="*"
tokens first and applies type to EACH ONE individually, so a token's
split result (itself a list, e.g. "a,b" -> ["a", "b"]) is appended
to the destination as ONE nested element instead of being flattened --
--rcopts '!*,build' became [['!*', 'build']], not ['!*',
'build']. Explicitly overriding nargs=None here (a single string per
flag occurrence, argparse's own default) avoids the double-collection:
type splits that one occurrence into its own list, and argparse's
built-in extend action flattens a list-valued single occurrence into
the destination correctly (verified: repeated --x a,b --x c and a
single --x a,b both produce a flat list, no nested sub-lists).
Source code in src/duho/args.py
1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 | |
command(args_cls, func, *, name=None)
Build a Cmd subclass from a data Args class and a callable.
Lets a user attach behavior to an existing data Args without
rewriting it as a Cmd subclass ("build one from Args and a
method"). The returned class subclasses BOTH args_cls (to inherit
its declared fields / parsing machinery) and Cmd (for the
executable contract). Its __call__ calls func(self) -- the parsed
instance IS the parsed args -- so command(MyArgs, f) makes f
receive the parsed MyArgs-shaped instance and its return value becomes
the command's result.
name (optional) sets the built class's _parsername_ (the
subcommand name). When omitted, the usual
_parsername_/class-name rule applies to the generated class.
Source code in src/duho/args.py
1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 | |
main(cls, argv=None, *, setup_logging=True, config=None)
Build a parser for cls, parse argv, and dispatch the selected Cmd.
Module-level (not a classmethod) so the Args subclass namespace stays
entirely user-owned. Steps: build parser (auto-registers subcommands),
apply the env/config/class-default layers (config overrides cls._config_;
precedence CLI > env > config > class default), parse argv (SystemExit from
argparse propagates), optionally set up stderr logging + apply verbosity
when the resulting instance provides set_loglevels, then run the command
and map a None return to 0.
Since Plan 13's Args/Cmd split, dispatch expects the selected class to be
a Cmd (executable, defines __call__). A bare data Args -- with
no __call__ -- raises a clear NotImplementedError ("Args holds data;
make it a Cmd to run it") rather than silently doing nothing.
Source code in src/duho/args.py
2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 | |
parse(spec, argv=None, *, parser_kwargs=None, config=None)
Build a parser from spec and parse argv into a new instance.
spec may be:
- An Args subclass (type): equivalent to spec._parser_().parse_args(argv),
with the env/config/class-default layers applied first (see below).
- An instance of an Args subclass: the instance's current field values
are used as argparse defaults (via parser.set_defaults(**overrides),
filtered to actual CLI fields -- not vars(spec), which would include
framework attrs). CLI args still override those defaults. Returns a
NEW instance of type(spec); spec itself is never mutated.
config (a path, or None to fall back to cls._config_) layers config-file
and environment-variable defaults under the instance/CLI ones. Full
precedence: CLI args > instance field values > env > config file > class
defaults. Note this means a required field (no class default) that is
supplied by any layer becomes effectively optional for this call.
Source code in src/duho/args.py
2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 | |
parse_globals(cls, argv=None, **parser_kwargs)
Parse ONLY a root command's global args, ignoring/relaxing subcommands.
Builds cls's root parser (cls._parser_(**parser_kwargs)) and parses
argv with help suppressed and subcommand validation relaxed, so a
consumer can resolve config-file-driven command search paths (or any other
global) BEFORE building/committing to the full subcommand parser. This is
the documented, public form of the internal prepass duho.app already
runs -- it wraps :func:duho.parsers.prerun_parse verbatim rather than
reimplementing the _HelpAction/subparser patching (which
prerun_parse performs and restores in a finally).
Returns the parsed root instance with globals set. Subcommand arguments are
NOT validated in this pass: a missing subcommand does not error, and an
unknown trailing token does not crash the globals parse (it is simply
ignored here). A caller that also wants the leftover argv should call
parser.parse_known_args directly -- parse_globals deliberately
returns a single value (the globals-only instance), mirroring the shape
prerun_parse yields.
**parser_kwargs are forwarded to cls._parser_ (e.g. add_help=False),
mirroring :func:duho.parser.
Source code in src/duho/args.py
2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 | |
print_agent_help(cls, file=None)
Print a detailed, machine-readable (JSON) agent-help document for cls.
Standalone counterpart to the --help-agents flag / the AGENT_HELP
env-var trigger: builds cls's parser tree fresh and describes it
(independent of whether either trigger is wired up), then writes the JSON to
file (default sys.stdout). Delegates to
:func:duho.agenthelp.print_agent_help.
Source code in src/duho/args.py
2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 | |
print_completion(cls, shell, file=None)
Print a shell completion script for cls to file (default sys.stdout).
shell is one of "bash", "zsh", "fish", or "powershell".
Standalone counterpart to the --print-completion flag injected when
_completion_ = True -- builds cls's parser tree fresh (independent of
whether _completion_ is set) and delegates to duho.completion.<shell>.
Source code in src/duho/args.py
2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 | |
value_sources(parsed)
Report the origin layer ("cli", "env", "config", or "default") of each
field on a parsed instance produced by duho.parse/duho.main.
Looks up the owning parser via the per-class _duho_last_parser_
linkage stashed during dispatch (see _initparser_). Returns {} if
unavailable (e.g. the instance wasn't produced via a parser built by
this framework, or no parse has happened yet for its class).
A field is "cli" if its parsed value differs from the effective default that was in effect for that parse -- the merged env/config value when the field was touched by one of those layers, else the class default. Otherwise it's whatever layer contributed that default ("env"/"config"), or "default" if no layer touched it (value == the untouched class default).
Source code in src/duho/args.py
2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 | |