Skip to content

Commit e30e294

Browse files
authored
Merge pull request #13 from roopahc/AllOptionGroup
Added a feature AllOptionGroup
2 parents a47e221 + 0d3b4e2 commit e30e294

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

click_option_group/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GroupedOption,
1616
OptionGroup,
1717
RequiredAnyOptionGroup,
18+
AllOptionGroup,
1819
RequiredAllOptionGroup,
1920
MutuallyExclusiveOptionGroup,
2021
RequiredMutuallyExclusiveOptionGroup,
@@ -29,6 +30,7 @@
2930
'GroupedOption',
3031
'OptionGroup',
3132
'RequiredAnyOptionGroup',
33+
'AllOptionGroup',
3234
'RequiredAllOptionGroup',
3335
'MutuallyExclusiveOptionGroup',
3436
'RequiredMutuallyExclusiveOptionGroup',

click_option_group/_core.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,30 @@ def handle_parse_result(self, option: GroupedOption, ctx: click.Context, opts: d
351351
f'"{self.get_default_name(ctx)}" option group:')
352352
error_text += f'\n{self.get_error_hint(ctx)}'
353353
raise click.UsageError(error_text, ctx=ctx)
354+
355+
356+
class AllOptionGroup(OptionGroup):
357+
"""Option group with required all/none options of this group
358+
359+
`AllOptionGroup` defines the behavior:
360+
- All options from the group must be set or None must be set.
361+
"""
362+
363+
@property
364+
def forbidden_option_attrs(self) -> ty.List[str]:
365+
return ['required', 'hidden']
366+
367+
@property
368+
def name_extra(self) -> ty.List[str]:
369+
return super().name_extra + ['all_or_none']
370+
371+
def handle_parse_result(self, option: GroupedOption, ctx: click.Context, opts: dict) -> None:
372+
option_names = set(self.get_options(ctx))
373+
374+
if not option_names.isdisjoint(opts) and option_names.intersection(opts) != option_names:
375+
error_text = f'All options should be specified or None should be specified from the group ' \
376+
f'"{self.get_default_name(ctx)}".'
377+
error_text += f'\nMissing required options from "{self.get_default_name(ctx)}" option group.'
378+
error_text += f'\n{self.get_error_hint(ctx)}'
379+
error_text += '\n'
380+
raise click.UsageError(error_text, ctx=ctx)

docs/api.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ API Reference
7979

8080
----
8181

82+
.. autoclass:: AllOptionGroup
83+
:members:
84+
85+
----
86+
8287
.. autoclass:: RequiredAllOptionGroup
8388
:members:
8489

tests/test_click_option_group.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
OptionGroup,
1111
GroupedOption,
1212
RequiredAnyOptionGroup,
13+
AllOptionGroup,
1314
RequiredAllOptionGroup,
1415
MutuallyExclusiveOptionGroup,
1516
RequiredMutuallyExclusiveOptionGroup,
@@ -282,6 +283,36 @@ def cli(foo, bar):
282283
assert 'foo,bar' in result.output
283284

284285

286+
def test_all_option_group(runner):
287+
group = AllOptionGroup()
288+
assert group.name_extra == ['all_or_none']
289+
290+
@click.command()
291+
@optgroup.group(cls=AllOptionGroup)
292+
@optgroup.option('--foo')
293+
@optgroup.option('--bar')
294+
def cli(foo, bar):
295+
click.echo(f'{foo},{bar}')
296+
result = runner.invoke(cli, ['--help'])
297+
assert '[all_or_none]' in result.output
298+
299+
result = runner.invoke(cli, [])
300+
assert not result.exception
301+
assert result.exit_code == 0
302+
303+
result = runner.invoke(cli, ['--foo', 'foo'])
304+
assert result.exception
305+
assert result.exit_code == 2
306+
assert 'All options should be specified or None should be specified from the group' in result.output
307+
assert '--foo' in result.output
308+
assert '--bar' in result.output
309+
310+
result = runner.invoke(cli, ['--foo', 'foo', '--bar', 'bar'])
311+
assert not result.exception
312+
assert result.exit_code == 0
313+
assert 'foo,bar' in result.output
314+
315+
285316
def test_required_all_option_group(runner):
286317
group = RequiredAllOptionGroup()
287318
assert group.name_extra == ['required_all']

0 commit comments

Comments
 (0)