Route a parsed command to a handler - the subcommand dispatch docopt itself omits.
Register one handler per command path with on. run
then parses argv and calls the handler for the most specific command path that matched.
The handler receives the parsed Arguments, or an instance of schema bound from the result when
the registration supplies one, so each subcommand gets its own typed view. An on() with no command
path registers a fallback, used when no more specific path matches.
Example
app = Dispatch(doc); @app.on("user", "create") def create(args): ...; app.run().
Source code in src/docopt2/_core.py
| def __init__(self, doc: str) -> None:
self.doc = doc
self._handlers: list[tuple[tuple[str, ...], _DispatchHandler, type[Any] | None]] = []
|
on
on(
*command_path: str, schema: type[Any] | None = None
) -> Callable[[_DispatchHandler], _DispatchHandler]
Register the decorated handler for command_path (empty path = fallback handler).
Source code in src/docopt2/_core.py
| def on(self, *command_path: str, schema: type[Any] | None = None) -> Callable[[_DispatchHandler], _DispatchHandler]:
"""Register the decorated handler for ``command_path`` (empty path = fallback handler)."""
def register(handler: _DispatchHandler) -> _DispatchHandler:
self._handlers.append((command_path, handler, schema))
return handler
return register
|
run
run(
argv: list[str] | tuple[str, ...] | str | None = None,
**options: Any,
) -> Any
Parse argv against the doc and invoke the handler for the matched command path.
Extra keyword arguments are forwarded to docopt (suggest, exit_code, ...).
schema is not among them, since dispatch matches on the mapping and binds per handler.
Source code in src/docopt2/_core.py
| def run(self, argv: list[str] | tuple[str, ...] | str | None = None, **options: Any) -> Any:
"""Parse ``argv`` against the doc and invoke the handler for the matched command path.
Extra keyword arguments are forwarded to [`docopt`][docopt2.docopt] (``suggest``, ``exit_code``, ...).
``schema`` is not among them, since dispatch matches on the mapping and binds per handler.
"""
if "schema" in options:
# Forwarding it would hand docopt() a schema, and dispatch would then try to route on the
# instance instead of the mapping - an AttributeError deep inside, for a legible mistake.
raise TypeError("Dispatch.run() takes no `schema`; register one per handler with `on(..., schema=...)`")
arguments = cast("Arguments", docopt(self.doc, argv, **options))
# Thread the usage and the caller's exit_code onto any exit raised HERE, as docopt()'s own _exit
# does - or str(exc) loses the Usage block and the process ignores the requested status.
usage = single_usage_section(self.doc) # the doc already parsed above, so this cannot raise
exit_code = options.get("exit_code", 1)
resolved = self._resolve(arguments)
if resolved is None:
summary = "no handler is registered for the given command"
raise DocoptExit(diagnostic=Diagnostic(summary=summary), usage=usage, exit_code=exit_code)
handler, schema = resolved
if schema is None:
return handler(arguments)
try:
bound = bind_schema(arguments, schema)
except _CoercionError as exc:
resolved_argv = sys.argv[1:] if argv is None else argv
diagnostic = _coercion_diagnostic(self.doc, resolved_argv, exc)
raise DocoptExit(diagnostic=diagnostic, usage=usage, exit_code=exit_code) from exc
return handler(bound)
|