Skip to content

Validate positive CLI numeric options consistently (closes #15) - #20

Merged
Johnkothapalli merged 1 commit into
Johnkothapalli:mainfrom
dchaudhari7177:fix/validate-positive-cli-options
Aug 20, 2026
Merged

Validate positive CLI numeric options consistently (closes #15)#20
Johnkothapalli merged 1 commit into
Johnkothapalli:mainfrom
dchaudhari7177:fix/validate-positive-cli-options

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

Closes #15.

The two positive-integer options failed differently for the same class of bad input: --workers 0 was swallowed by args.workers or defaults.workers and silently fell back to the default worker count, while --max-complexity 0 reached AnalyzerConfig and surfaced as a ValueError traceback.

What changed

A small reusable argparse type:

def _positive_int(value: str) -> int:
    try:
        number = int(value)
    except ValueError:
        raise argparse.ArgumentTypeError(f"expected an integer, got {value!r}") from None
    if number < 1:
        raise argparse.ArgumentTypeError(f"must be at least 1, got {number}")
    return number

Applied to --workers and --max-complexity. Both now fail as argparse usage errors with exit code 2:

code-health scan: error: argument --workers: must be at least 1, got 0

It also catches non-integers with a clearer message than argparse's default invalid int value.

The or in main() is now an explicit is None check. _positive_int means 0 can't reach it any more, so this is belt-and-braces — but the or was the bug, and spelling out that None (option omitted) is the only fallback case keeps it from coming back.

AnalyzerConfig.__post_init__ validation is deliberately untouched: CLI validation is additive and direct API callers keep their own guard. There's a test pinning that, since "moved the check to the CLI" would be the tempting wrong refactor.

No dependency added.

Tests

10 added to tests/test_cli.py, parametrized over both option names:

  • 0 and -1 → exit code 2, stderr names the option and says "must be at least 1"
  • non-integer input → exit code 2, "expected an integer"
  • a valid explicit value (--workers 2, --max-complexity 1) still scans and returns 0
  • omitting both keeps workers is None and max_complexity == AnalyzerConfig().max_complexity
  • AnalyzerConfig(workers=0) / AnalyzerConfig(max_complexity=0) still raise ValueError

Red-before-green: with git stash push -- src/code_health/cli.py, all 6 rejection tests fail against pristine code.

Gate

Ran the full local gate from CONTRIBUTING on Windows / Python 3.12:

  • pytest — 24 passed, coverage 92.93% (threshold 85%)
  • ruff check . — clean
  • ruff format --check . — 28 files already formatted

🤖 Generated with Claude Code

--workers 0 was silently swallowed by `args.workers or defaults.workers`
and fell back to the default worker count; --max-complexity 0 reached
AnalyzerConfig and surfaced as a ValueError traceback. Two different
wrong behaviours for the same class of bad input.

Added a reusable `_positive_int` argparse type and used it for both, so
each is reported by argparse as a usage error with exit code 2 and no
traceback. It also rejects non-integers with a clearer message than
argparse's default.

The `or` in main() is now an explicit `is None` check. _positive_int
means 0 can no longer reach it, but None (option omitted) is the only
fallback case and saying so keeps the bug from coming back.

AnalyzerConfig's own validation is untouched — CLI validation is
additive, and direct API callers keep their guard. A test pins that.

No dependency added.

Tests: 10 added to tests/test_cli.py — both option names x {0, -1},
both x non-integer, both with a valid explicit value, defaults preserved
when omitted, and AnalyzerConfig still raising for API callers. The 6
rejection tests fail against pristine cli.py.

Gate: pytest 24 passed (coverage 92.93%, threshold 85%); ruff check and
ruff format --check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Johnkothapalli
Johnkothapalli merged commit d2451ef into Johnkothapalli:main Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants