Args
duho.args
AUTO = _AutoVersion()
module-attribute
Factory = _ty.Callable[[str], _T]
module-attribute
NOT_DEFINED = _introspect.NOT_DEFINED
module-attribute
__all__ = ['Append', 'app_name', 'Argument', 'ArgumentBuilder', 'ArgumentMeta', 'Args', 'Arg', 'AUTO', 'Choice', 'ClsArgDeclaration', 'Cli', 'Cmd', 'command', 'Const', 'Count', 'Extend', 'Factory', 'finish_parse', 'main', 'InitParserKwargs', 'Meta', 'NOT_DEFINED', 'parse', 'parse_globals', 'print_agent_help', 'print_completion', 'subcommand', 'UpdateAction', 'ConfigSource', 'config_source', 'value_sources']
module-attribute
Args(**kwargs)
Bases: Namespace
Base class for a duho command's declared fields.
Subclass this and declare CLI fields as annotated, non-underscore class
attributes. Everything duho reads or sets is sandwich-named (_x_) or a
dunder, so the field namespace stays user-owned; only help,
version and print_completion, which would clash with a flag duho
adds, are rejected as field names (see docs/guide/arguments.md). An
optional flags tuple and a trailing docstring string customize each
field's add_argument call. Args itself is data-only -- it defines
no __call__ (:func:duho.main/:func:duho.run_command raise a clear
NotImplementedError for a bare Args); subclass :class:Cmd
instead for a RUNNABLE command, or :class:Cli for an app root.
Args subclasses argparse.Namespace, whose stub declares
__getattr__(self, name) -> Any -- so a type checker does NOT catch a
typo'd field read (self.regoin on a class with a region field);
only a real run surfaces it, as an AttributeError.
Source code in src/duho/args/_argsclass.py
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
Argument
Bases: Protocol
The customization protocol for a field's declared type.
Any object with a classmethod _argbuilder_(name, decl, factory=None) ->
ArgumentBuilder satisfies this protocol (see :class:ArgumentMeta) and
can be used as a field's type -- wrapped in Arg[CustomType, ...], or
returned by :meth:from_type for a plain type -- to fully control how
that field's :class:ArgumentBuilder is built. This is how duho's own
type ladder (int, list[T], Literal[...], ...) is implemented,
and the extension point a user-defined type (e.g. a Port value type)
hooks into for the exact same treatment.
from_type(factory, **kwargs)
classmethod
Wrap a plain type/text-factory as an :class:Argument.
Returns an Argument subclass whose _argbuilder_ builds via
cls's own (Argument's base) logic using factory, then
applies **kwargs (the Meta(...) metadata dict) onto
the result via :func:_apply_argument_options. Every plain-typed
field (i.e. one not ALREADY a custom :class:Argument) is built
through this single path -- see Args._getargs_.
Source code in src/duho/args/_argument.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
ArgumentBuilder
Bases: Namespace
One declared field's build state: raw add_argument kwargs plus
duho's own metadata (collection/split/env/conflicts/...).
Built by :meth:Argument._argbuilder_ (via :meth:Argument.from_type for
a plain type) from a field's _introspect.ClsArgDeclaration, then
post-processed with any Meta(...) metadata
(:func:_apply_argument_options). One instance per declared field,
cached on the owning class (Args._getargs_). :meth:_kwargs resolves
it to the exact keyword arguments :meth:add_to_parser passes to
parser.add_argument; the env/config layering
(_raw_env_values/_raw_config_values/convert_layered) reads it
to convert a non-CLI value the same way a CLI occurrence would.
action = None
class-attribute
instance-attribute
choices = None
class-attribute
instance-attribute
collection = None
class-attribute
instance-attribute
conflicts = None
class-attribute
instance-attribute
conflicts_required = False
class-attribute
instance-attribute
const = NOT_DEFINED
class-attribute
instance-attribute
default
instance-attribute
enum_by = 'name'
class-attribute
instance-attribute
env = None
class-attribute
instance-attribute
flags
instance-attribute
group = None
class-attribute
instance-attribute
help
instance-attribute
is_bare_bool_flag
property
True when this field compiles to a bare store_true/
BooleanOptionalAction flag with no value of its own -- as opposed
to a bool that carries choices (a Literal[True, False] field,
which must go through type=+choices= like any other Literal) or
one with an explicit action= override.
is_positional
property
True when this field's final, post-override flags are positional.
kwargs = None
class-attribute
instance-attribute
literal_value = False
class-attribute
instance-attribute
metavar = None
class-attribute
instance-attribute
name
instance-attribute
nargs = None
class-attribute
instance-attribute
required = None
class-attribute
instance-attribute
split = None
class-attribute
instance-attribute
type
instance-attribute
version = None
class-attribute
instance-attribute
add_to_parser(parser, *, layered=False)
Call parser.add_argument(*self.flags, ...) for this field.
layered=True when an env/config layer can ALSO supply a value for
this field (see :meth:_kwargs's layered parameter -- it changes
a bare bool field's action so the CLI can still turn a layer-supplied
True back off). Returns the argparse.Action that was added.
Source code in src/duho/args/_argument.py
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 | |
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 the strict :func:_bool_from_texttable (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.
The result is checked against self.choices before it is returned,
the same membership check argparse itself would apply to a
CLI-supplied value.
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/_argument.py
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 | |
ArgumentMeta
Bases: _ProtocolMeta
Metaclass backing :class:Argument's duck-typed isinstance check.
An object satisfies :class:Argument simply by having a callable
_argbuilder_ -- no explicit subclassing/registration needed, so any
custom type can opt in by defining that one classmethod.
__instancecheck__(instance)
Source code in src/duho/args/_argument.py
30 31 32 | |
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)(duho.args/duho.runtime), 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
OTHER member Cli adds is sandwich-named or dunder, so a Cli
subclass's field namespace stays user-owned (annotated non-underscore
attrs still become CLI fields) -- with exactly ONE reserved, plain name:
subcommand (the @Root.subcommand decorator). A CLI field named
subcommand silently replaces the decorator instead of erroring, so
avoid that one field name on a Cli subclass.
Source code in src/duho/args/_argsclass.py
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
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/_cmd.py
317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
ClsArgDeclaration(default, type, annotations, docstring, exprs)
dataclass
annotations
instance-attribute
default
instance-attribute
docstring
instance-attribute
exprs
instance-attribute
type
instance-attribute
Cmd(**kwargs)
Bases: Args
An executable command: a data Args plus the command contract.
Args 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 an 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/_argsclass.py
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 | |
__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/_cmd.py
59 60 61 62 63 64 65 66 67 68 69 70 | |
ConfigSource
Bases: NamedTuple
The config file read for a parse and where its path came from.
path
instance-attribute
source
instance-attribute
InitParserKwargs
Bases: TypedDict
The keywords _parser_ passes to _initparser_, every one optional.
Annotate an override's **kwargs with Unpack[InitParserKwargs] and
forward them to super()._initparser_(parser, **kwargs). The base method
is where they end up: it raises TypeError for a key that is not listed
here and uses each key's default when it is missing.
agent_root_cls
instance-attribute
explicit_prog
instance-attribute
external_config
instance-attribute
is_subcommand
instance-attribute
parent_dests
instance-attribute
Meta
Typed, typo-safe field metadata: Arg[int, Meta(help="...")].
Meta takes keyword arguments only, each one a documented metadata
field; an unknown name (Meta(hlep="oops")) is a TypeError, raised
as soon as the annotation is evaluated: at class-definition time on Python
3.9-3.13 with eager annotations, or at first parser build on 3.14+ (PEP
649), under string annotations, or with from __future__ import
annotations. Only the fields you set are merged (each defaults to a
private sentinel), so an unset field never overrides a type-derived value::
level: Arg[int, Meta(help="verbosity", env="LEVEL")] = 0
("--level",)
The kwargs field is the raw add_argument escape hatch. A plain
dict is the permissive alternative: a key no Meta field and no
custom argument claims is ignored with a warning.
There is deliberately no dest field: an argument's dest is always
its declared field name (the parsed instance attribute), so Meta raises
a dedicated TypeError for dest=.
enum_by="value" matches an Enum field against str(member.value)
(command line, env and config alike) instead of the member name; the
default, "name", is the rule when it is not set.
literal_value=True on an option that takes one value makes the token
after it always that value, even when it looks like an option (--k --,
--k -x); the default is the usual argparse rule.
split is the callable Extend sets: it turns one raw token into
several values.
flags is the typed, lint-clean way to give an explicit flag tuple
(equivalent to the bare ("-n", "--times") statement in the class body,
which some checkers flag as an unused expression)::
times: Arg[int, Meta(flags=("-n", "--times"))] = 1
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
default = _META_UNSET
class-attribute
instance-attribute
enum_by = _META_UNSET
class-attribute
instance-attribute
env = _META_UNSET
class-attribute
instance-attribute
flags = _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
literal_value = _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
split = _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 merges dict occurrences, replacing any layered default on
the first CLI occurrence -- the same "CLI wins" semantics
_CollectionAction gives list/set/tuple fields.
__call__(parser, namespace, values, option_string=None)
Source code in src/duho/_fieldspec.py
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 | |
Append(type=str, **kw)
Create an append-action argument, accumulating repeated flag values.
Explicitly clears nargs to None (one scalar value per flag occurrence)
regardless of the field's own declared collection kind or any ambient
Meta(nargs=...), so append() always collects one scalar per occurrence
instead of gathering a list of tokens per occurrence.
Source code in src/duho/args/_meta.py
313 314 315 316 317 318 319 320 321 | |
Choice(*choices, **kw)
Restrict an argument's accepted values to choices.
Source code in src/duho/args/_meta.py
329 330 331 | |
Const(value, **kw)
Create a store_const-action argument that stores value when present.
Source code in src/duho/args/_meta.py
324 325 326 | |
Count(**kw)
Create a count-action argument (e.g. -vvv -> 3).
Source code in src/duho/args/_meta.py
308 309 310 | |
Extend(split, **kwargs)
Create a collection argument whose text is split on split first.
A list[str] OPTION's own default builder already takes nargs=None
(one value per flag occurrence); a VARIADIC positional, or an option with
an explicit Meta(nargs="*") override, instead gathers several raw
tokens per occurrence. Combined with a factory that SPLITS one token into
several, that shape would double-collect: argparse applies the factory to
EACH gathered token individually, so a token's split result (itself a
list, e.g. "a,b" -> ["a", "b"]) would be appended as ONE nested
element instead of being flattened -- --rcopts '!*,build' becoming
[['!*', 'build']], not ['!*', 'build']. Explicitly overriding
nargs=None here (a single string per flag occurrence) avoids the
double-collection regardless of what the field's own default would be.
The split parts are mapped through the field's OWN element factory (so
Arg[list[int], Extend(",")] yields ints, not strings) and fed to
whichever collection action the field's declared type already uses --
list, set, or tuple -- so this composes with a typed
or non-list collection instead of forcing a stdlib list-only action. The
field's own declared default (or the type ladder's empty collection when
none is declared) is used as-is; it is NOT overridden here, so it is kept
when the flag is absent and replaced -- like any other collection option
-- on the first CLI occurrence.
Source code in src/duho/args/_meta.py
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | |
app_name(obj)
The name the application goes by: its prog, the same on every surface.
For an instance a parser produced (a root or a subcommand), the root
parser's prog, which is app(name=...) when given. For a class, or an
instance built directly, the application name of the class: its own
_parsername_, else its top-level import package, else its kebab-case
class name.
Source code in src/duho/args/_entry.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
command(args_cls, func, *, name=None, module=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.
module (optional): overrides the built class's __module__.
type(cls_name, bases, namespace) -- with no __module__ in
namespace -- otherwise makes Python fill it in from THIS function's
OWN globals (duho.args), the same gotcha collections.namedtuple/
dataclasses.make_dataclass solve by resolving the CALLER's module via
sys._getframe. Left as duho.args, this broke two things: (1)
discovery's module-boundary filter keeps only classes whose __module__
equals the scanned module, so a command()-built class in a discovered
command file was silently never registered (no warning -- it looked like
"not a command" the same way a helpers-only module does); (2)
_version_ = duho.AUTO derives the lookup distribution from
cls.__module__, so it resolved to duho's OWN installed version instead
of the app's. __qualname__ is also set to a value that can never
collide with a real ClassDef in the caller's source (AST-based
introspection looks classes up by __module__ + __qualname__, and
could otherwise misattribute a same-named sibling class's body to this
synthesized one). _duho_constants_ is seeded empty for the same
reason Cmd/Cli seed it: without it, a bare
command()-built class (whose __module__ now points at the CALLER's
real file) would still fall through to AST-parsing that file looking for
a body this synthesized class never had.
Source code in src/duho/args/_cmd.py
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 | |
config_source(parsed)
The config file in force for a parsed (sub)command instance, or None.
None when no file was read (none named, an empty path, an absent default
file or no _config_) and for an instance that was not produced by a parse.
Source code in src/duho/_layers.py
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 | |
finish_parse(namespace)
Build the selected Cmd/Args instance from a Namespace
produced by attaching a duho subparser to a PLAIN, non-duho argparse
parser -- the documented "manual subparsers" recipe
(Serve._parser_(subparsers, name="serve") attached to a
hand-built argparse.ArgumentParser() root).
A duho root (Args._parser_'s own patched parse_known_args) does
this automatically as part of normal dispatch: the internal "#cls"
marker (recording which subcommand class argparse selected) is popped and
turned into the real instance. A PLAIN argparse root has no such
hook, so root.parse_args(...) returns a raw Namespace still
carrying "#cls" -- not a Serve instance, with no _passthrough_
and no methods, and a stray "#cls" key that breaks
func(**vars(args))/json.dumps(vars(args)). Call this once, right
after parse_args, to get the real instance the manual recipe was
always meant to produce.
Raises ValueError if namespace carries no "#cls" marker (e.g. no
duho subparser was ever reached for this invocation).
Source code in src/duho/args/_entry.py
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 | |
main(cls, argv=None, *, setup_logging=True, config=None, utf8_stdio=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,
then run the command and map a None return to 0. Logging setup runs when
the resulting instance provides _set_loglevels_ directly, OR -- when the
selected leaf is a plain Cmd under a LoggingArgs root -- when cls
itself is a LoggingArgs (see _setup_instance_logging).
Since the 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.
Typed -> Any, not -> int: a None result maps to 0, but
any OTHER value the command returns (an int, or anything else destined
for sys.exit) passes straight through unchanged. Any (rather than
object) keeps sys.exit(duho.main(...)) clean under a strict-mypy
consumer, since sys.exit does not accept object.
UTF-8 stdio: :func:duho.utf8_stdio runs FIRST, before the MCP
launch trigger and before argv is parsed, unless opted out --
utf8_stdio=False here, or cls's own _utf8_stdio_ = False
when the kwarg is left at its default None. Opted out, duho does not
touch stdio at all (the app may call :func:duho.utf8_stdio itself).
MCP launch trigger: checked next, before argv
is even parsed -- see :func:_maybe_serve_mcp_trigger. When the trigger
fires this returns the MCP server's own exit code instead of running any
command; otherwise nothing about the rest of this function changes.
Opt-in MCP subcommand (cls's own _mcp_command_, mirroring
duho.app(..., mcp_command=...) -- there is no separate kwarg here,
since main has no other command-source parameters to sit next to).
False (the default): unchanged behavior, and duho.mcp is never
imported. Otherwise a fresh duho.mcp.McpCmd subclass is registered
as an extra top-level subcommand under the resolved name, going through
the exact same resolution/validation
(:func:duho.runtime._resolve_mcp_command_name) and collision/leaf
checks (:func:duho.runtime._build_mcp_command_class) app() uses,
so the ValueError messages match. Running <prog> <name> serves
cls's own static tree (excluding that subcommand itself) over
stdio via :func:duho.mcp.serve_running_app, through the same
_MCP_CONTEXT ContextVar app() sets.
Source code in src/duho/args/_entry.py
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | |
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 field values the caller EXPLICITLY
passed to its __init__ (not a placeholder Args.__init__ itself seeded
for an omitted field -- a bare bool/collection field left at its own
default is not "an instance value" just because it materializes onto
vars(spec)) are used as argparse defaults. 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.
duho.value_sources reports such a field as "instance".
With subcommands the instance returned is the SELECTED LEAF command's, not
an instance of spec's own class: it is typed as spec's class, but a
chosen subcommand's class need not derive from it.
Source code in src/duho/args/_entry.py
350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
parse_globals(cls, argv=None, *, config=None, **parser_kwargs)
Parse ONLY a root command's global args, ignoring/relaxing subcommands.
Builds cls's root parser (cls._parser_(**parser_kwargs)), applies
the same env/config/class-default layers duho.main/duho.parse do
(config overrides cls._config_; precedence CLI > env > config >
class default), and parses argv with help/version/print-
completion suppressed and subcommand descent skipped entirely, so a
consumer can resolve config-file-driven command search paths (or any
other global, including one backed by Meta(env=...) or only made
non-required by a layer) 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
subparser-detach/terminal-action patching it 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/_entry.py
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | |
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/_actions.py
184 185 186 187 188 189 190 191 192 193 194 195 | |
print_completion(cls, shell, file=None, *, prog=None)
Print a shell completion script for cls to file (default sys.stdout).
shell is one of "bash", "zsh", "fish", or "powershell"
-- an unrecognized name raises a clear ValueError listing the
valid ones, instead of an AttributeError about duho.completion's
internals -- or, worse, silently calling one of that module's PRIVATE
helpers as if it were an emitter. 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>.
prog overrides the command name the emitted script binds to; by
default it is the root parser's prog, the application's name (see
:func:_app_name), however the program was launched.
Source code in src/duho/args/_actions.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
subcommand(parent)
Decorator factory: register the decorated class as a subcommand of parent.
parent is any Cmd (or Cli) class acting as a group::
@duho.subcommand(Tools)
class Build(duho.Cmd):
...
The decorated class is returned unchanged. Equivalent to
parent._register_subcmd_(Build); registering twice is a no-op.
Source code in src/duho/args/_cmd.py
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | |
value_sources(parsed)
Report the origin layer ("cli", "env", "config", "instance", or
"default") of each field on a parsed instance produced by
duho.parse/duho.main.
Looks up the owning parser via the per-INSTANCE
_duho_instance_last_parser_ linkage stashed during dispatch (see
_initparser_) first -- falling back to the per-class
_duho_last_parser_ one, read through type(parsed).__dict__ rather
than getattr -- getattr follows the MRO, so a never-parsed
SUBCLASS of an already-parsed base (or of bare Args, parsed every
time duho.app runs without a root) would otherwise inherit its base's
stale parser and report bogus sources instead of the documented {}.
The per-instance lookup matters because the per-class one is shared by
EVERY instance of that class: without it, parsing the same class a
second time (a different config file, say) would silently change what an
OLDER, already-returned instance reports here too. Returns {} when
unavailable (e.g. the instance wasn't produced via a parser built by this
framework, or no parse has happened yet for its class).
For a SUBCOMMAND instance, root/global fields are included too:
_merge_layers_upward folds each actually-selected parser's own
provenance/builders up into its parent's as dispatch unwinds, so by the
time the root's own _duho_last_parser_ linkage is read here it already
holds the whole selected chain, not just the deepest class's own fields.
A field is "cli" if its parsed value differs from the effective default that was in effect for that parse -- the merged env/config/instance 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"/"instance"), or "default" if no layer touched it (value == the untouched class default).
Source code in src/duho/_layers.py
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 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 | |
duho.parser(cls, *args, **kwargs)
Build an ArgumentParser for an Args class.
Public module-level entry point (delegates to cls._parser_, matching
its own _Parser[Self] return typing so duho.parser(MyApp) keeps
MyApp's type instead of widening to Any). With subcommands, the
instance parse_args returns is the SELECTED LEAF command's, not a
MyApp: it is typed as MyApp, but a chosen subcommand's class need
not derive from it.
Source code in src/duho/__init__.py
87 88 89 90 91 92 93 94 95 96 97 | |