Search code examples
pythoncommand-line-interfacepython-click

Click type conversion


I am using click to define a CLI that takes datetimes and a comma seperated list of parameters.

import click

def valid_date(s):
    try:
        return dt.strptime(s, "%Y-%m-%d")
    except ValueError:
        msg = "Not a valid date: '{0}'.".format(s)
        raise Exception(msg)
    except TypeError:
        return None

split_parameter = lambda _, __, s: s.split(",")
check_date = lambda _, __, s: valid_date(s)


@click.command()
@click.argument('symbols', callback=split_parameter)
@click.option('--start_date', callback=check_date)
@click.option('--end_date', callback=check_date)
@click.option('--file_name')
def f(symbols, start_date, end_date, file_name):
     return None

It works but the logic around callbacks is a bit awkward. Both splitting the list and converting to datetime interrupts the nice simple structure of click. Is there a pythonic way of doing this?


Solution

  • You can encapsulate the ugly lambda _, __, x part in a decorator:

    import click
    from datetime import datetime as dt
    
    def click_callback(f):
        return lambda _, __, x: f(x)
    
    def check_date(s):
        try:
            return dt.strptime(s, "%Y-%m-%d")
        except ValueError:
            msg = "Not a valid date: '{0}'.".format(s)
            raise Exception(msg)
        except TypeError:
            return None
    
    @click.command()
    @click.argument('symbols', callback=click_callback(lambda s: s.split(',')))
    @click.option('--start_date', callback=click_callback(check_date))
    @click.option('--end_date', callback=click_callback(check_date))
    @click.option('--file_name')
    def f(symbols, start_date, end_date, file_name):
        print(symbols, start_date, end_date, file_name)
    
    if __name__ == '__main__':
        f()
    

    Or like this (only the relevant part is shown):

    def click_callback(f):
        return {'callback': lambda _, __, x: f(x)}
    
    @click.command()           #  vvvvvvvvvvvvvvvv
    @click.argument('symbols',    **click_callback(lambda s: s.split(',')))
    @click.option('--start_date', **click_callback(check_date))
    @click.option('--end_date',   **click_callback(check_date))
    @click.option('--file_name')
    def f(symbols, start_date, end_date, file_name):
        print(symbols, start_date, end_date, file_name)