From 4bb82de3a9d1e355a0eb0048d10d4246d57a5c22 Mon Sep 17 00:00:00 2001 From: Matthew Anderson Date: Thu, 25 May 2023 07:55:44 -0500 Subject: [PATCH] feat: loader conditionals (#23) --- tests/test_example.py | 17 +++++++++++++++-- typer_config/__init__.py | 26 +++++++++++++++++++++----- typer_config/__typing.py | 3 +++ typer_config/loaders.py | 13 ++++++++++++- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/tests/test_example.py b/tests/test_example.py index be3e557..8be9d78 100644 --- a/tests/test_example.py +++ b/tests/test_example.py @@ -41,9 +41,13 @@ def main( (str(HERE.joinpath("config.env")), typer_config.dotenv_conf_callback), ( str(HERE.joinpath("config.ini")), + # Have to make one dynamically because of the required INI section typer_config.conf_callback_factory( - typer_config.loaders.subpath_loader( - typer_config.loaders.ini_loader, ["simple_app"] + typer_config.loaders.loader_transformer( + typer_config.loaders.subpath_loader( + typer_config.loaders.ini_loader, ["simple_app"] + ), + loader_conditional=lambda param_value: param_value, ) ), ), @@ -55,6 +59,11 @@ def test_simple_example(simple_app, confs): conf, callback = confs _app = simple_app(callback) + result = RUNNER.invoke(_app, ["--help"]) + assert ( + result.exit_code == 0 + ), f"Couldn't get to `--help` for {conf}\n\n{result.stdout}" + result = RUNNER.invoke(_app, ["--config", conf]) assert result.exit_code == 0, f"Loading failed for {conf}\n\n{result.stdout}" assert ( @@ -90,6 +99,10 @@ def test_pyproject_example_deprecated(simple_app): _app = simple_app(pyproject_callback) + result = RUNNER.invoke(_app, ["--help"]) + + assert result.exit_code == 0, f"{result.stdout}" + result = RUNNER.invoke(_app) assert result.exit_code == 0, f"{result.stdout}" diff --git a/typer_config/__init__.py b/typer_config/__init__.py index b44e18b..275d445 100644 --- a/typer_config/__init__.py +++ b/typer_config/__init__.py @@ -5,7 +5,13 @@ from typer import BadParameter, CallbackParam, Context from .__typing import ConfigLoader, ConfigParameterCallback, TyperParameterValue -from .loaders import dotenv_loader, json_loader, toml_loader, yaml_loader +from .loaders import ( + dotenv_loader, + json_loader, + toml_loader, + yaml_loader, + loader_transformer, +) def conf_callback_factory(loader: ConfigLoader) -> ConfigParameterCallback: @@ -48,7 +54,9 @@ def _callback( return _callback -yaml_conf_callback: ConfigParameterCallback = conf_callback_factory(yaml_loader) +yaml_conf_callback: ConfigParameterCallback = conf_callback_factory( + loader_transformer(yaml_loader, loader_conditional=lambda param_value: param_value) +) """YAML typer config parameter callback. Args: @@ -64,7 +72,9 @@ def _callback( TyperParameterValue: must return back the given parameter """ -json_conf_callback: ConfigParameterCallback = conf_callback_factory(json_loader) +json_conf_callback: ConfigParameterCallback = conf_callback_factory( + loader_transformer(json_loader, loader_conditional=lambda param_value: param_value) +) """JSON typer config parameter callback. Args: @@ -81,7 +91,9 @@ def _callback( """ -toml_conf_callback: ConfigParameterCallback = conf_callback_factory(toml_loader) +toml_conf_callback: ConfigParameterCallback = conf_callback_factory( + loader_transformer(toml_loader, loader_conditional=lambda param_value: param_value) +) """TOML typer config parameter callback. Args: @@ -97,7 +109,11 @@ def _callback( TyperParameterValue: must return back the given parameter """ -dotenv_conf_callback: ConfigParameterCallback = conf_callback_factory(dotenv_loader) +dotenv_conf_callback: ConfigParameterCallback = conf_callback_factory( + loader_transformer( + dotenv_loader, loader_conditional=lambda param_value: param_value + ) +) """Dotenv typer config parameter callback. Args: diff --git a/typer_config/__typing.py b/typer_config/__typing.py index 4e80256..e2cffd9 100644 --- a/typer_config/__typing.py +++ b/typer_config/__typing.py @@ -38,6 +38,9 @@ ConfigLoader: TypeAlias = Callable[[TyperParameterValue], ConfigDict] """Configuration loader function.""" +ConfigLoaderConditional: TypeAlias = Callable[[TyperParameterValue], bool] +"""Configuration loader conditional function.""" + ConfigParameterCallback: TypeAlias = Callable[ [Context, CallbackParam, TyperParameterValue], TyperParameterValue ] diff --git a/typer_config/loaders.py b/typer_config/loaders.py index c3492ec..7361a9f 100644 --- a/typer_config/loaders.py +++ b/typer_config/loaders.py @@ -14,6 +14,7 @@ ConfigDictAccessorPath, ConfigDictTransformer, ConfigLoader, + ConfigLoaderConditional, NoArgCallable, TyperParameterValue, TyperParameterValueTransformer, @@ -58,6 +59,7 @@ def loader_transformer( loader: ConfigLoader, + loader_conditional: Optional[ConfigLoaderConditional] = None, param_transformer: Optional[TyperParameterValueTransformer] = None, config_transformer: Optional[ConfigDictTransformer] = None, ) -> ConfigLoader: @@ -94,6 +96,8 @@ def loader_transformer( Args: loader (ConfigLoader): Loader to transform. + loader_condtional (Optional[ConfigLoaderConditional], optional): Function + to determine whether to execute loader. Defaults to None (no-op). param_transformer (Optional[TyperParameterValueTransformer], optional): Typer parameter transformer. Defaults to None (no-op). config_transformer (Optional[ConfigDictTransformer], optional): Config @@ -104,11 +108,18 @@ def loader_transformer( """ def _loader(param_value: TyperParameterValue) -> ConfigDict: + # Transform input if param_transformer is not None: param_value = param_transformer(param_value) - conf: ConfigDict = loader(param_value) + # Decide whether to execute loader + # NOTE: bad things can happen when `param_value=''` + # such as `--help` not working + conf: ConfigDict = {} + if loader_conditional is None or loader_conditional(param_value): + conf = loader(param_value) + # Transform output if config_transformer is not None: conf = config_transformer(conf)