Skip to content

Docs: cw.ARGH reproduces argh's EXPLICIT BY_NAME_IF_HAS_DEFAULT, not argh's no-policy legacy mode #39

Description

@thorwhalen

Not a bug in cw's grammar — cw is faithful to what it says it implements. It is a
documentation gap that silently breaks migrations, found while migrating
i2mint/http2py (wave apps-2).

The trap

"This repo used argh, so cw.ARGH reproduces it" is false. It depends on which argh
entry point the repo called, because argh's own default is not one thing:

what the repo wrote policy argh actually used
argh.dispatch_commands(funcs) explicitly BY_NAME_IF_HAS_DEFAULT (from argh's own old_name_mapping_policy=True default)
ArghParser(); parser.add_commands(funcs) None — argh's legacy mode

Those two are not the same grammar. They diverge on one shape: a keyword-only
parameter with no default
.

def greet(*, who: str, punct: str = "!"):
    """Greet WHO."""
argh, dispatch_commands (=BY_NAME_IF_HAS_DEFAULT):   greet [-h] [-p PUNCT] who
argh, add_commands with no policy (legacy):          greet [-h] -w WHO [-p PUNCT]
cw.ARGH (naming='by_name_if_has_default'):           greet [-h] [-p PUNCT] who
cw + naming=BY_NAME_IF_KWONLY:                       greet [-h] -w WHO [-p PUNCT]

cw.ARGH matches argh's explicit BY_NAME_IF_HAS_DEFAULT exactly — correct, and
cw.grammar's KEYWORD_ONLY branch is doing precisely what the policy name says. But a
repo in the second row that migrates to plain cw.dispatch gets a silently different
command line
: every required argument stops being -w WHO and becomes a bare positional.
No error, no warning.

Why this is not theoretical

http2py.cli_maker.mk_cli is exactly the second row, and every function it generates is
all-keyword-only (Sig.merge_with_sig(..., kind=KO)). A naive migration would have changed
the spelling of every required argument of every generated API client:

before:  get-thing -u UID -p PID [-l LIMIT]
after:   get-thing [-l LIMIT] uid pid

Caught only by diffing argh against cw before editing. i2mint/http2py#17 pins
BY_NAME_IF_KWONLY for that module and carries a regression test.

The two repos in the same wave landed on opposite answers, which is the point:

  • thorwhalen/muvid — argh.dispatch_commands → cw.ARGH, no override. Correct.
  • i2mint/http2py — ArghParser().add_commands → BY_NAME_IF_KWONLY required.
  • i2mint/ir — explicit BY_NAME_IF_KWONLY already, preserved.

Suggested fix

Docs, not code — the grammar is right.

  1. In cw.convention / cw.grammar next to BY_NAME_IF_HAS_DEFAULT, state that it
    reproduces argh's explicit BY_NAME_IF_HAS_DEFAULT (and therefore
    argh.dispatch_commands), and that argh's no-policy legacy mode differs for
    keyword-only parameters without defaults — for which BY_NAME_IF_KWONLY is the match.
  2. In cw.compat, if ArghParser.add_commands is emulated, consider defaulting it to the
    legacy-equivalent convention rather than cw.ARGH, since that is what the code it is
    replacing did.
  3. Add the shape to cw/tests/ as a pinned fixture: def f(*, who: str) under both
    namings. It currently has no direct coverage that names this divergence.

Migration-guide wording

Check which argh entry point the repo used before choosing a convention.
argh.dispatch_commands → cw.ARGH. ArghParser() + add_commands() with no
name_mapping_policy → argh's legacy mode; if any command has a keyword-only parameter
with no default, you need naming=cw.BY_NAME_IF_KWONLY. Diff --help either way.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions