Skip to content

Parameter decorator factories shouldn't pop cls from attrs #2294

@janluke

Description

@janluke

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions