wadebug/cli_param.py (37 lines of code) (raw):
# Copyright (c) Facebook, Inc. and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import absolute_import, division, print_function, unicode_literals
import functools
import click
from wadebug import exceptions
"""Module to create re-usable CLI parameters for wadebug"""
# This works by creating two new decorators: wadebug_option and wadebug_argument
# They are used exactly like click.option or click. argument, with two differences:
# 1. They save the value os a parameter on dict ctx.obj as well.
# 2. They take a ReusableParam as argument
# Usage example:
# json_output = ReusableParam(
# '--json',
# 'json',
# help='Pass this flag to output results in json format. This enables '
# 'automation and integration with other applications if needed.',
# is_flag=True,
# default=False,
# )
# @click.option('--specific_param', 'specific_param', help='used only on my_new_command')
# @wadebug_option(json_output)
# def my_new_command(ctx, specific_param, **kwargs):
# print('This is my_new_command')
# print('param_specific_to_this_command is {}'.format(param_specific_to_this_command))
# print('--json is {}'.format(ctx.obj['json']))
# By using the decorator above, we can make the two following commands to be equivalent
# wadebug --json my_new_command
# wadebug my_new_command --json
# if `json` appears before `ls`, `json=True` is set on dict `ctx.obj`
# on function `my_new_command`, value of `kwargs['json']` is False as it wasn't provided,
# but code will prioritize value from ctx.obj as it was set
class ReusableParam:
"""A click command-line parameter that can be used for multiple click commands."""
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class _WADebugParam(object):
"""Decorator to use click with ReusableParam. Values should always be accessed through ctx.obj and NEVER kwargs."""
decorator_mapping = {"option": click.option, "argument": click.argument}
def __init__(self, reusable_param, param_type):
self.reusable_param = reusable_param
if param_type not in self.decorator_mapping.keys():
raise exceptions.InvalidParamType(
"param_type {} is invalid. options are {}".format(
param_type, self.self.decorator_mapping.keys()
)
)
self.param_type = param_type
def __call__(self, func):
decorator_to_apply = self.decorator_mapping[self.param_type]
@decorator_to_apply(*self.reusable_param.args, **self.reusable_param.kwargs)
@functools.wraps(func)
def wrapper(ctx, *args, **kwargs):
if not ctx.obj:
ctx.obj = {}
for k, v in kwargs.items():
if v:
ctx.obj[k] = v
return func(ctx, *args, **kwargs)
return wrapper
class wadebug_option(_WADebugParam):
def __init__(self, reusable_param):
super(wadebug_option, self).__init__(reusable_param, "option")
class wadebug_argument(_WADebugParam):
def __init__(self, reusable_param):
super(wadebug_option, self).__init__(reusable_param, "argument")