2

I am trying to use decorators with the Click framework to perform work common to multiple commands without using arguments in the root element of the group (see https://github.com/pallets/click/issues/295 for why). In short, something like this:

@click.group()
def main():
  pass

@main.command()
@click.option('--argument-a')
@parse_config_file
@init_session
def do_something_in_session(argument_a, config, session):
   # code
   return

where the decorators also have arguments:

def init_session(f):
  @wraps(f)
  @click.option('--argument-B')
  def wrapper(*args, **kwargs):
    # do something with argument-B, and add session to list of arguments.
    del kwargs['argument-B']
    kwargs['session'] = session_created_above

    return f(*args,**kwargs)
  return wrapper

def parse_config_file(f):
  @wraps(f)
  @click.option('--argument-C')
  def wrapper(*args, **kwargs):
    # do something with argument-C, and add config to list of arguments.
    del kwargs['argument-C']
    kwargs['config'] = config_parsed_above

    return f(*args,**kwargs)
  return wrapper

However, when running do_something_in_session --help, only the arguments of the decorator just above the function definition, in this case @init_session, are shown. Is there a way for me to decorate the decorators so that Click parses the arguments properly?

Joey Dumont
  • 898
  • 2
  • 7
  • 25
  • The decorators need to be applied to the bare function. So you can not easily stack your custom decorators. – Stephen Rauch Sep 03 '19 at 14:38
  • More explanation: https://stackoverflow.com/a/56317633/7311767 – Stephen Rauch Sep 03 '19 at 14:39
  • So, from what I've read in the other thread, it should be possible if I could find way to update the proper `vars` that `@click.option` uses. From what I can see, `@click.option` adds a new `__click_params__` entry in the `__dict__` of the functions it wraps. I think I just have to make sure that my decorators propagate that. – Joey Dumont Sep 03 '19 at 16:33

1 Answers1

1

This is an example of how this works in our codebase (using click 7.1.2).

def decorator_example(f):

    @pass_context
    @click.option("--testy")
    def wrapper(ctx, *args, testy, **kwargs):
        # Do stuff with testy here
        testy = f"pre{testy}"
    
    # Think this is the bit that allows click to be aware of options defined on the wrapper
    return ctx.invoke(wrapped_function, *args, testy=testy, **kwargs)

# Using functools to update the wrapper as required - think this is the same as @wraps(f)?
return update_wrapper(wrapper, wrapped_function)
Dave Birch
  • 469
  • 4
  • 10
  • This is nominally what the click documentation recommends https://click.palletsprojects.com/en/8.1.x/commands/#decorating-commands – blueskyjunkie Oct 30 '22 at 17:34