mrkeyoor.com_
Tue 22 Sept 18:51 UTC
PyPICLI & Toolingupdated 22 Sept 2026

click-option-group review

Our click-option-group 0.5.9 sandbox install took 0.2 seconds, occupied 1 MB across 2 packages, and imported successfully in 0.18 seconds. The library is a focused Click extension: `optgroup.group` prints a named block in command help, while five supplied group classes enforce rules such as exactly one input or a complete set of credentials. Release 0.5.9 fixes mypy use without dropping pyright compatibility. It remains pure Python, supports Python 3.7 and newer, and ships the `py.typed` marker.

Verdict

click-option-group 0.5.9 installed in 0.2 seconds and used 1 MB in our sandbox, with a working import, typed packaging, and 0 audit findings. Install it for constrained option blocks in an existing Click CLI; choose a broader framework when you also need command-level structure or annotation-led declarations.

We installed it

Lab card: what happened when we installed click-option-groupScreenshot of click-option-group documentation
Install✓ · 0.2s2 packages on disk · 1 MB
Importimport click_option_group in 0.18s · pure Python · py.typed · requires Python >=3.7
Known vulns0(pip-audit)

Answers from our run

Does click-option-group install cleanly?

Yes. In a fresh container with an empty cache, pip install click-option-group finished in 0.2s, leaving 2 packages and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does click-option-group need to run?

Python >=3.7, and nothing compiled: it is pure Python. In our run import click_option_group succeeded in 0.18s, and the package ships py.typed for type checkers.

click-option-group or click: which should you use?

click: Keep plain Click when one callback check is clearer than a new help section and constraint class. click-option-group 0.5.9 installed in 0.2 seconds and used 1 MB in our sandbox, with a working import, typed packaging, and 0 audit findings.

When should you not use click-option-group?

Your flags need nested groups. The tutorial calls subgroups unsupported by design and recommends nested commands instead.

API stability4/5Version 0.5.9 exposes one `optgroup` helper, `OptionGroup`, `GroupedOption`, and five named constraint classes. The changelog shows the same decorator model back to the 0.5 line, with recent releases limited to return annotations, Click 8.1.8 test compatibility, and type-checker fixes. Its pre-1.0 version and dependence on Click's parsing internals still justify regression tests around error text and help output when Click changes.
Docs3/5The documentation gives a runnable command, its rendered help, all five bundled constraint meanings, both declaration styles, and explicit failure cases for mixed or unattached decorators. It also states that nested groups are unsupported. The API page is brief, though, and offers little help for subclass authors who need parse lifecycle details, environment-supplied values, callback ordering, or compatibility tests across Click versions.
Maintenance3/5Release 0.5.9 and the repository's last code push were both on 2025-10-09. That release repaired mypy use while retaining pyright support, following two other focused releases during 2025. GitHub currently shows 121 stars, 6 open issues and pull requests, and no archive flag. The package is maintained, but the small repository and modest release cadence leave less backup than Click itself has.
Ecosystem4/5The package records roughly 7.7 million weekly downloads and builds directly on Click options, types, environment variables, prompts, callbacks, completion, and `CliRunner`. Adoption inside a Click project therefore leaves the command body and most tests intact. Its reach ends at that boundary: argparse, Typer-style declarations, and other parsers cannot reuse the group classes or their decorator checks.

Use it if

  • An established Click command needs option headings without moving to another command framework.
  • Two input flags must be mutually exclusive, with the parser rejecting an empty choice as well.
  • A credential or connection group must be supplied completely instead of reaching application code half filled.
  • A project needs a custom `OptionGroup` subclass for a command-local rule that Click does not express itself.
Skip it if

Setup reality

Our fresh Python 3.12 install of click-option-group 0.5.9 succeeded in 0.2 seconds. It left 2 packages and 1 MB on disk, with 9 direct dependencies recorded by our lab run. The package is pure Python, needs Python 3.7 or newer, and carries a BSD license. import click_option_group worked in 0.18 seconds, and pip-audit found 0 known vulnerabilities. The wheel includes py.typed for type checkers.

No service, account, compiler, or project config is involved. Declaration order is the part that causes first-run failures. Every optgroup.option must sit next to the other options collected by its optgroup.group. Putting a normal click.option between them raises an exception. Calling optgroup.option without a matching group also raises, while declaring an empty group emits a runtime warning.

The five constraint classes disagree about the empty case. MutuallyExclusiveOptionGroup accepts 0 or 1 selected option; RequiredMutuallyExclusiveOptionGroup accepts exactly 1. AllOptionGroup permits none or all, RequiredAllOptionGroup demands the full set, and RequiredAnyOptionGroup needs at least 1. Click still supplies conversions, prompts, defaults, environment values, and callbacks, so a value provided outside argv can satisfy a group rule.

Grouping only changes help layout and option validation. It adds no nested-group model and does not coordinate values across subcommands. Exercise the empty, partial, valid, and conflicting paths with CliRunner, especially when defaults or environment variables are present. Version 0.5.9 changed typing behavior, so teams that run both mypy and pyright should keep both checks in CI rather than treating the bundled marker as proof that every custom subclass is sound.

Patterns

Print a connection section in help label-option-section

import click
from click_option_group import optgroup

@click.command()
@optgroup.group('Connection', help='Remote endpoint')
@optgroup.option('--host', default='127.0.0.1', show_default=True)
@optgroup.option('--port', type=click.IntRange(1, 65535), default=8080)
def main(host, port):
    click.echo(f'{host}:{port}')

The group decorator collects the adjacent `optgroup.option` declarations directly below it and prints one heading above them.

Demand exactly one input source require-one-source

import click
from click_option_group import RequiredMutuallyExclusiveOptionGroup, optgroup

@click.command()
@optgroup.group('Input', cls=RequiredMutuallyExclusiveOptionGroup)
@optgroup.option('--json-file', type=click.File('r'))
@optgroup.option('--csv-file', type=click.File('r'))
def load(json_file, csv_file):
    source = json_file or csv_file
    click.echo(source.name)

`RequiredMutuallyExclusiveOptionGroup` rejects 0 selections and also rejects 2 selections before the callback runs.

Allow zero or one format switch allow-optional-format

import click
from click_option_group import MutuallyExclusiveOptionGroup, optgroup

@click.command()
@optgroup.group('Format', cls=MutuallyExclusiveOptionGroup)
@optgroup.option('--json', 'output_format', flag_value='json')
@optgroup.option('--yaml', 'output_format', flag_value='yaml')
def render(output_format):
    click.echo(output_format or 'text')

`MutuallyExclusiveOptionGroup` allows 0 or 1 option, so the callback can retain a default format when neither flag appears.

Require at least one output require-any-destination

import click
from click_option_group import RequiredAnyOptionGroup, optgroup

@click.command()
@optgroup.group('Destination', cls=RequiredAnyOptionGroup)
@optgroup.option('--path', type=click.Path(dir_okay=False))
@optgroup.option('--stdout', is_flag=True)
def export(path, stdout):
    pass

`RequiredAnyOptionGroup` accepts 1 or 2 supplied destinations; it only rejects the empty invocation.

Accept both proxy fields or neither pair-optional-credentials

import click
from click_option_group import AllOptionGroup, optgroup

@click.command()
@optgroup.group('Proxy', cls=AllOptionGroup)
@optgroup.option('--proxy-user')
@optgroup.option('--proxy-password', hide_input=True)
def fetch(proxy_user, proxy_password):
    pass

`AllOptionGroup` permits 0 or 2 values in this pair and reports which option is missing from a partial invocation.

Demand every database field require-complete-connection

import click
from click_option_group import RequiredAllOptionGroup, optgroup

@click.command()
@optgroup.group('Database', cls=RequiredAllOptionGroup)
@optgroup.option('--host')
@optgroup.option('--database')
def migrate(host, database):
    pass

`RequiredAllOptionGroup` makes the 2-option set mandatory, so separate `required=True` attributes are unnecessary.

Keep a Click flag outside the group separate-plain-option

import click
from click_option_group import optgroup

@click.command()
@click.option('--verbose', is_flag=True)
@optgroup.group('Server')
@optgroup.option('--host', default='localhost')
@optgroup.option('--port', type=int, default=8000)
def main(verbose, host, port):
    pass

A normal `click.option` may sit above or below the complete group, but placing it between grouped options raises a declaration error.

Read a grouped token from the environment supply-value-from-env

import click
from click_option_group import RequiredAnyOptionGroup, optgroup

@click.command()
@optgroup.group('Authentication', cls=RequiredAnyOptionGroup)
@optgroup.option('--token', envvar='APP_TOKEN', hide_input=True)
@optgroup.option('--profile')
def main(token, profile):
    pass

`APP_TOKEN` can satisfy the required group without a token flag because Click resolves environment values during parsing.

Place help inside a named block group-help-option

import click
from click_option_group import optgroup

@click.command(add_help_option=False)
@optgroup.group('General')
@optgroup.help_option('--help', '-h')
@optgroup.option('--version', is_flag=True)
def main(version):
    pass

`add_help_option=False` prevents Click from registering a second help flag when `optgroup.help_option` supplies one.

Declare a reusable group instance reuse-group-object

import click
from click_option_group import OptionGroup

network = OptionGroup('Network', help='Connection controls')

@click.command()
@network.option('--host', default='localhost')
@network.option('--timeout', type=float, default=5.0)
def main(host, timeout):
    pass

`OptionGroup.option` is the documented second API and avoids the global helper when a named group object is easier to share.

Test empty and conflicting choices test-constraint-errors

from click.testing import CliRunner

def test_sources():
    runner = CliRunner()
    assert runner.invoke(load, []).exit_code != 0
    ok = runner.invoke(load, ['--json-file', 'one.json'])
    assert ok.exit_code == 0
    conflict = runner.invoke(load, ['--json-file', 'one.json', '--csv-file', 'two.csv'])
    assert conflict.exit_code != 0

A required exclusive group has 3 distinct paths to test: empty, exactly one value, and conflicting values.

Reject more than two selected options define-custom-rule

from click_option_group import OptionGroup

class AtMostTwo(OptionGroup):
    @property
    def name_extra(self):
        return ['at_most_two']

    def handle_parse_result(self, option, ctx, opts):
        selected = [name for name in self.get_options(ctx) if name in opts]
        if len(selected) > 2:
            self.fail('choose no more than two options', ctx)
        super().handle_parse_result(option, ctx, opts)

Custom constraints override `handle_parse_result`, which ties them to click-option-group's parser lifecycle more closely than the five supplied classes.

Alternatives

PackageRegistryPick it when
clickPyPIKeep plain Click when one callback check is clearer than a new help section and constraint class.
typerPyPIUse Typer for a new Click-backed CLI whose commands and parameters should come from Python annotations.
cycloptsPyPIUse Cyclopts when annotation parsing, structured parameters, and grouping should share one framework.
cloupPyPIUse Cloup when grouped options are one requirement among aliases, command sections, styled help, and constraints.

More cli & tooling guides

chalk · commander · typescript · esbuild · yargs · click · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.