diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index 79e701b..e8b5d2d 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1 +1,11 @@ # Unreleased + +## Features + +* #79 Added function get_cli_arg that makes a string CLI argument from an option and its value. + Also allowed passing an option name instead of the StdParams in the following two functions: + create_std_option, check_params. + +## Bug fixing + +* #78 Missing default value in the definition of StdParams.path_in_bucket. diff --git a/exasol/python_extension_common/cli/std_options.py b/exasol/python_extension_common/cli/std_options.py index 84dcec2..0f7e2c8 100644 --- a/exasol/python_extension_common/cli/std_options.py +++ b/exasol/python_extension_common/cli/std_options.py @@ -118,6 +118,13 @@ def __init__(self, tags: StdTags, value): self.tags = tags +StdParamOrName = StdParams | str + + +def _get_param_name(std_param: StdParamOrName) -> str: + return std_param if isinstance(std_param, str) else std_param.name + + """ Standard options defined in the form of key-value pairs, where key is the option's StaParam key and the value is a kwargs for creating the click.Options(...). @@ -135,7 +142,7 @@ def __init__(self, tags: StdTags, value): StdParams.saas_database_id: {'type': str, 'hide_input': True}, StdParams.saas_database_name: {'type': str}, StdParams.saas_token: {'type': str, 'hide_input': True}, - StdParams.path_in_bucket: {'type': str}, + StdParams.path_in_bucket: {'type': str, 'default': ''}, StdParams.container_file: {'type': click.Path(exists=True, file_okay=True)}, StdParams.version: {'type': str, 'expose_value': False}, StdParams.dsn: {'type': str}, @@ -172,23 +179,26 @@ def make_option_secret(option_params: dict[str, Any], prompt: str) -> None: option_params['callback'] = secret_callback -def create_std_option(std_param: StdParams, **kwargs) -> click.Option: +def create_std_option(std_param: StdParamOrName, **kwargs) -> click.Option: """ Creates a Click option. Parameters: std_param: - The option's StdParam key. + The option's StdParam key or its name. If the name is provided it doesn't have + to be the name of a defined StdParams. It can be an arbitrary identifier which + will be passed to the callback function of the CLI command in the kwargs. kwargs: The option properties. """ - option_name = std_param.name.replace('_', '-') + std_param_name = _get_param_name(std_param) + option_name = std_param_name.replace('_', '-') if kwargs.get('type') == bool: param_decls = [f'--{option_name}/--no-{option_name}'] else: param_decls = [f'--{option_name}'] if kwargs.get('hide_input', False): - make_option_secret(kwargs, prompt=std_param.name.replace('_', ' ')) + make_option_secret(kwargs, prompt=std_param_name.replace('_', ' ')) return click.Option(param_decls, **kwargs) @@ -238,7 +248,20 @@ def option_params(std_param: StdParams) -> dict[str, Any]: for std_param in filtered_params] -def check_params(std_params: StdParams | list[StdParams | list[StdParams]], +def get_cli_arg(std_param: StdParamOrName, param_value: Any) -> str: + """ + Makes a CLI args string from an option and its value. + An option can be given as either an StdParams or its string name. + For boolean values the args string takes the form --option-name/--no-option-name. + """ + + option_name = _get_param_name(std_param).replace("_", "-") + if isinstance(param_value, bool): + return f'--{option_name}' if param_value else f'--no-{option_name}' + return f'--{option_name} "{param_value}"' + + +def check_params(std_params: StdParamOrName | list[StdParamOrName | list[StdParamOrName]], param_kwargs: dict[str, Any]) -> bool: """ Checks if the kwargs contain specified StdParams keys. The intention is to verify @@ -256,16 +279,18 @@ def check_params(std_params: StdParams | list[StdParams | list[StdParams]], Parameters: std_params: Required options. This can be either a single option or a list of options - representing a logical expression. + representing a logical expression. An option can be given as either an StdParams + or its string name. param_kwargs: A dictionary of provided values (kwargs). """ - def check_param(std_param: StdParams | list[StdParams]) -> bool: + def check_param(std_param: StdParamOrName | list[StdParamOrName]) -> bool: if isinstance(std_param, list): return any(check_param(std_param_i) for std_param_i in std_param) - return ((std_param.name in param_kwargs) and - (isinstance(param_kwargs[std_param.name], bool) or - bool(param_kwargs[std_param.name]))) + std_param_name = _get_param_name(std_param) + return ((std_param_name in param_kwargs) and + (isinstance(param_kwargs[std_param_name], bool) or + bool(param_kwargs[std_param_name]))) if isinstance(std_params, list): return all(check_param(std_param) for std_param in std_params) diff --git a/test/unit/cli/test_std_options.py b/test/unit/cli/test_std_options.py index 45ff517..c8690b0 100644 --- a/test/unit/cli/test_std_options.py +++ b/test/unit/cli/test_std_options.py @@ -10,6 +10,7 @@ StdParams, create_std_option, select_std_options, + get_cli_arg, check_params ) @@ -59,6 +60,12 @@ def test_create_std_option_secret(): assert opt.default == SECRET_DISPLAY +def test_create_std_option_arbitrary_name(): + opt_name = 'xyz' + opt = create_std_option(opt_name, type=str) + assert opt.name == opt_name + + def test_select_std_options(): for tag in StdTags: opts = {opt.name for opt in select_std_options(tag)} @@ -148,6 +155,19 @@ def func(**kwargs): os.environ.pop(envar_name) +@pytest.mark.parametrize( + ['std_param', 'param_value', 'expected_result'], + [ + (StdParams.db_user, 'Me', '--db-user "Me"'), + ('user_rating', 5, '--user-rating "5"'), + (StdParams.use_ssl_cert_validation, True, '--use-ssl-cert-validation'), + (StdParams.use_ssl_cert_validation, False, '--no-use-ssl-cert-validation') + ] +) +def test_get_cli_arg(std_param, param_value, expected_result): + assert get_cli_arg(std_param, param_value) == expected_result + + @pytest.mark.parametrize( ['std_params', 'param_kwargs', 'expected_result'], [ @@ -185,7 +205,19 @@ def func(**kwargs): StdParams.dsn, {StdParams.dsn.name: 'my_dsn', StdParams.use_ssl_cert_validation.name: False}, True - ) + ), + ( + [[StdParams.dsn.name, StdParams.db_user.name], + [StdParams.saas_url.name, StdParams.saas_account_id.name]], + {StdParams.dsn.name: 'my_dsn', StdParams.db_user.name: 'my_user_name'}, + False, + ), + ( + [[StdParams.dsn.name, StdParams.saas_url.name], + [StdParams.db_user.name, StdParams.saas_account_id.name]], + {StdParams.dsn.name: 'my_dsn', StdParams.db_user.name: 'my_user_name'}, + True, + ), ] ) def test_check_params(std_params, param_kwargs, expected_result):