Skip to content

Commit 69998cc

Browse files
authored
Merge pull request #10 from ButterflyNetwork/support-wrappers
Support multi-layer wrapped functions.
2 parents 401ba13 + 1ae608f commit 69998cc

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed

click_option_group/_core.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
get_callback_and_params,
1313
get_fake_option_name,
1414
raise_mixing_decorators_error,
15+
resolve_wrappers
1516
)
1617

1718

@@ -182,7 +183,7 @@ def decorator(func):
182183
def get_options(self, ctx: click.Context) -> ty.Dict[str, GroupedOption]:
183184
"""Returns the dictionary with group options
184185
"""
185-
return self._options.get(ctx.command.callback, {})
186+
return self._options.get(resolve_wrappers(ctx.command.callback), {})
186187

187188
def get_option_names(self, ctx: click.Context) -> ty.List[str]:
188189
"""Returns the list with option names ordered by addition in the group

click_option_group/_helpers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def get_callback_and_params(func) -> ty.Tuple[abc.Callable, ty.List[click.Option
2323
else:
2424
params = getattr(func, '__click_params__', [])
2525

26+
func = resolve_wrappers(func)
2627
return func, params
2728

2829

@@ -37,3 +38,8 @@ def raise_mixing_decorators_error(wrong_option: click.Option, callback: abc.Call
3738
"Grouped options must not be mixed with regular parameters while adding by decorator. "
3839
f"Check decorator position for {error_hint} option in '{callback.__name__}'."
3940
))
41+
42+
43+
def resolve_wrappers(f):
44+
"""Get the underlying function behind any level of function wrappers."""
45+
return resolve_wrappers(f.__wrapped__) if hasattr(f, "__wrapped__") else f

tests/test_click_option_group.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# -*- coding: utf-8 -*-
22

3+
from functools import wraps
4+
35
import pytest
46
import click
57

@@ -695,3 +697,55 @@ def cli(foo, bar):
695697
assert "Group 1" in result.output
696698
assert "foo" in result.output
697699
assert "bar" not in result.output
700+
701+
702+
def test_wrapped_functions(runner):
703+
def make_z():
704+
"""A unified option interface for making a `z`."""
705+
706+
def decorator(f):
707+
@optgroup.group("Group xyz")
708+
@optgroup.option("-x", type=int)
709+
@optgroup.option("-y", type=int)
710+
@wraps(f)
711+
def new_func(*args, x=0, y=0, **kwargs):
712+
# Here we handle every detail about how to make a `z` from the given options
713+
f(*args, z=x + y, **kwargs)
714+
715+
return new_func
716+
717+
return decorator
718+
719+
def make_c():
720+
"""A unified option interface for making a `c`."""
721+
722+
def decorator(f):
723+
@optgroup.group("Group abc")
724+
@optgroup.option("-a", type=int)
725+
@optgroup.option("-b", type=int)
726+
@wraps(f)
727+
def new_func(*args, a=0, b=0, **kwargs):
728+
# Here we do the same, but for another object `c` (and many others to come)
729+
f(*args, c=a * b, **kwargs)
730+
731+
return new_func
732+
733+
return decorator
734+
735+
# Here I want to create a script that has a commen UI to make a `z`.
736+
# I want to reuse a common set of options for how to make a `z` and don't want
737+
# to sweat the details. Also, I've decided that I also want a `c` for this script.
738+
@click.command()
739+
@make_z()
740+
@make_c()
741+
def f(z, c):
742+
print(z, c)
743+
744+
# Test
745+
result = runner.invoke(f, ["--help"])
746+
assert "Group xyz" in result.output
747+
assert "-x" in result.output
748+
assert "-y" in result.output
749+
assert "Group abc" in result.output
750+
assert "-a" in result.output
751+
assert "-b" in result.output

0 commit comments

Comments
 (0)