Popping cls introduces a side-effect that prevents the reuse of the returned decorator. For example, consider the code below. The function my_shared_argument is a decorator that is expected to register an argument of type ArgumentWithHelp. Instead, what happens is:
- the first time the decorator is used,
cls=ArgumentWithHelp will be popped from attrs -> attrs is now modified in-place
- the following times the decorator is used,
cls won't be in attrs.
import click
class ArgumentWithHelp(click.Argument):
def __init__(self, *args, help=None, **attrs):
super().__init__(*args, **attrs)
self.help = help
def argument_with_help(*args, cls=ArgumentWithHelp, **kwargs):
return click.argument(*args, cls=cls, **kwargs)
my_shared_argument = argument_with_help("pippo", help="very useful help")
@click.command()
@my_shared_argument
def foo(pippo):
print(pippo)
@click.command()
@my_shared_argument
def bar(pippo):
print(pippo)
Running this file as it is:
Traceback (most recent call last):
File "C:/Users/sboby/AppData/Roaming/JetBrains/PyCharmCE2022.1/scratches/scratch_4.py", line 27, in <module>
def bar(pippo):
File "H:\Repo\unbox\UnboxTranslate\venv\lib\site-packages\click\decorators.py", line 287, in decorator
_param_memo(f, ArgumentClass(param_decls, **attrs))
File "H:\Repo\unbox\UnboxTranslate\venv\lib\site-packages\click\core.py", line 2950, in __init__
super().__init__(param_decls, required=required, **attrs)
TypeError: __init__() got an unexpected keyword argument 'help'
Process finished with exit code 1
The @option decorator is affected by the same problem but it won't crash in a similar situation because click.Option.__init__ ignore extra arguments: it will just silently use click.Option as class.
The @option decorator is actually not affected because attrs is copied. This copy is nonetheless unnecessary, since adding cls to the signature of the function would be a more straightforward solution.
Environment:
- Python version: irrelevant
- Click version: 8.1.3 (but all versions are affected)
Popping
clsintroduces a side-effect that prevents the reuse of the returned decorator. For example, consider the code below. The functionmy_shared_argumentis a decorator that is expected to register an argument of typeArgumentWithHelp. Instead, what happens is:cls=ArgumentWithHelpwill be popped fromattrs->attrsis now modified in-placeclswon't be inattrs.Running this file as it is:
The@optiondecorator is affected by the same problem but it won't crash in a similar situation becauseclick.Option.__init__ignore extra arguments: it will just silently useclick.Optionas class.The
@optiondecorator is actually not affected becauseattrsis copied. This copy is nonetheless unnecessary, since addingclsto the signature of the function would be a more straightforward solution.Environment: