pythonpython-click

create structured data directly from python @click?


As discussed one may reuse python click decorators from several scripts easily. However, with growing number of parameters

Hence, is there a way to create a class objects directly in the decorators to group parameters?

so, from a decorator function like this:

def common_options(mydefault=True):
    def inner_func(function):
        function = click.option('--unique-flag-1', is_flag=True)(function)
        function = click.option('--bar', is_flag=True)(function)
        function = click.option('--foo', is_flag=True, default=mydefault)(function)
        return function
    return inner_func

directly emit a class like this:

class CommonOptions:
    def __init__(unique_flag_1, bar, foo):
        self.unique_flag_1 = .... 

could be directly emitted to

@click.command
@common_options()
def main(common_options: CommonOptions):
  ...

Solution

  • You can use **kwargs in a click command, so you could write something like:

    import click
    from dataclasses import dataclass
    
    
    @dataclass
    class CommonOptions:
        unique_flag_1: bool
        bar: bool
        foo: bool
    
    
    def common_options(mydefault=True):
        def inner_func(function):
            function = click.option("--unique-flag-1", is_flag=True)(function)
            function = click.option("--bar", is_flag=True)(function)
            function = click.option("--foo", is_flag=True, default=mydefault)(function)
            return function
    
        return inner_func
    
    
    @click.command()
    @common_options()
    def main(**kwargs):
        options = CommonOptions(**kwargs)
        print(options)
    
    
    if __name__ == "__main__":
        main()
    

    W/r/t to your comment, if we borrow an idea from here we can write such that we can have multiple option groups, but for each group we just pass all of **kwargs and let the receiver sort it out:

    import click
    
    from dataclasses import dataclass
    
    
    class OptionGroup:
        @classmethod
        def from_dict(cls, **options):
            return cls(
                **{k: v for k, v in options.items() if k in cls.__dataclass_fields__}
            )
    
    
    @dataclass
    class OptionGroup1(OptionGroup):
        unique_flag_1: bool
        bar: bool
        foo: bool
    
    
    @dataclass
    class OptionGroup2(OptionGroup):
        count: int
        size: int
    
    
    def option_group_1(mydefault=True):
        def _(function):
            function = click.option("--unique-flag-1", is_flag=True)(function)
            function = click.option("--bar", is_flag=True)(function)
            function = click.option("--foo", is_flag=True, default=mydefault)(function)
            return function
    
        return _
    
    
    def option_group_2():
        def _(function):
            function = click.option("--count", type=int)(function)
            function = click.option("--size", type=int)(function)
            return function
    
        return _
    
    
    @click.command()
    @option_group_2()
    @option_group_1()
    def main(**kwargs):
        o1 = OptionGroup1.from_dict(**kwargs)
        o2 = OptionGroup2.from_dict(**kwargs)
        print("group1:", o1)
        print("group2:", o2)
    
    
    if __name__ == "__main__":
        main()
    

    Some example output:

    $ python example.py
    group1: OptionGroup1(unique_flag_1=False, bar=False, foo=True)
    group2: OptionGroup2(count=None, size=None)
    $ python example.py --count=3 --bar
    group1: OptionGroup1(unique_flag_1=False, bar=True, foo=True)
    group2: OptionGroup2(count=3, size=None)