From 5f1531eda08b8468b3fde5d1383c2c4ca65d81ee Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 27 Apr 2023 21:51:00 +0200 Subject: [PATCH 1/3] Adds `conf_path` arg to `add_config_cmd` This allows us to specify the config file that the `config` command should use. --- src/typerconf/init.nw | 120 ++++++++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 35 deletions(-) diff --git a/src/typerconf/init.nw b/src/typerconf/init.nw index 0a89fcc..9cfbb5a 100644 --- a/src/typerconf/init.nw +++ b/src/typerconf/init.nw @@ -49,9 +49,11 @@ config.add_config_cmd(cli) \end{minted} So we need to provide such a function. <>= -def add_config_cmd(cli: typer.Typer): +def add_config_cmd(cli: typer.Typer, conf_path: str = None): """ - Add config command to Typer cli + Add config command to Typer instance `cli`. + + If `conf_path` is not None, use that file instead of the default. """ <> @ @@ -619,17 +621,28 @@ def set(path: str, value: typing.Any): \section{The [[config]] command} We will provide the [[config]] command as outlined above. -If it gets a value, it will set it as the value at path. -Otherwise, it will print the current value at path. +If we get a path, but the user didn't use [[--set]] and provide a value, we +simply print the value at the end of the path. +If we get a value through [[--set]], we'll update the value at the end of the +path (or create it if it doesn't exist). +This corresponds to how the [[.set]] method of [[Config]] works. + +We have access to two variables: [[cli]], the Typer instance to which we want +to add the command; and [[conf_path]], which is the path to the config file. +If [[conf_path]] is [[None]], we should use the default config file (as +determined by the [[.read_config]] method of [[Config]]). +Otherwise, we supply [[.read_config]] with the [[conf_path]] value. <>= -path_arg = typer.Argument(..., - help="Path in config, e.g. 'courses.datintro22'. " - "Empty string is root of config.", - autocompletion=complete_path_callback) -value_arg = typer.Option([], "-s", "--set", - help="Values to store. " - "More than one value makes a list. " - "Values are treated as JSON if possible.") +conf = Config() +try: + if conf_path: + conf.read_config(conf_path) + else: + conf.read_config() +except ValueError: + pass + +<> @cli.command(name="config") def config_cmd(path: str = path_arg, @@ -640,9 +653,25 @@ def config_cmd(path: str = path_arg, if values: <> <> - set(path, values) + conf.set(path, values) else: - print_config(get(path), path) + print_config(conf.get(path), path) +@ + +The default values are the special Typer objects that specify how the command +arguments and options should behave. +We can autocomplete the path since we can predict the possible values. +The same cannot be said of the value to store, that can be arbitrary. +<>= +path_arg = typer.Argument("", + help="Path in config, e.g. 'courses.datintro22'. " + "Empty string is root of config. Defaults to " + "the empty string.", + autocompletion=complete_path_callback) +value_arg = typer.Option([], "-s", "--set", + help="Values to store. " + "More than one value makes a list. " + "Values are treated as JSON if possible.") @ If the user supplies only one argument on the command line, we don't want it to @@ -661,35 +690,56 @@ if values == "": @ Let's test this command. +We'll set up the testing. <>= runner = CliRunner() def test_cli(): - # set example data - result = runner.invoke(cli, - ["courses.datintro22.url", "--set", "https://..."]) - assert result.exit_code == 0 - - # try access nonexisting - result = runner.invoke(cli, ["courses.datintro.url"]) - assert result.exit_code == 1 - - # access existing - result = runner.invoke(cli, ["courses.datintro22.url"]) - assert "courses.datintro22.url = https://..." in result.stdout - - # clear config - result = runner.invoke(cli, - ["courses", "--set", None]) - assert result.exit_code == 0 - - # check that it's cleared - result = runner.invoke(cli, ["courses"]) - assert "courses" not in result.stdout + <> <>= from typer.testing import CliRunner @ +Let's look at the actual tests. +<>= +# set example data +result = runner.invoke(cli, + ["courses.datintro22.url", "--set", "https://..."]) +assert result.exit_code == 0 + +# try access nonexisting +result = runner.invoke(cli, ["courses.datintro.url"]) +assert result.exit_code == 1 + +# access existing +result = runner.invoke(cli, ["courses.datintro22.url"]) +assert "courses.datintro22.url = https://..." in result.stdout + +# clear config +result = runner.invoke(cli, + ["courses", "--set", None]) +assert result.exit_code == 0 + +# check that it's cleared +result = runner.invoke(cli, ["courses"]) +assert "courses" not in result.stdout +@ + +Let's also test it with the non-default config. +Note that we want the temporary file to open it ourselves, hence we close it +immediately (through an empty [[with]] statement) and ensure it's not deleted +on closing. +<>= +def test_cli_not_default_conf(): + with tempfile.NamedTemporaryFile(delete=False) as tmp_conf: + pass + + cli = typer.Typer() + add_config_cmd(cli, tmp_conf.name) + + <> +@ + \subsection{Autocompleting the path} From 1318f326c19659a203558555e284a0e2ad1dd469 Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Thu, 27 Apr 2023 21:51:39 +0200 Subject: [PATCH 2/3] Fixes test: must create list out of complete_path generator --- src/typerconf/init.nw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typerconf/init.nw b/src/typerconf/init.nw index 9cfbb5a..d6deaf7 100644 --- a/src/typerconf/init.nw +++ b/src/typerconf/init.nw @@ -773,7 +773,7 @@ def test_complete_path(): incomplete = "courses.datintro22.T" assert "courses.datintro22.TAs" in complete_path(incomplete, conf) assert "courses.datintro22.url" not in complete_path(incomplete, conf) - assert len(complete_path(incomplete, conf)) >= 0 + assert len(list(complete_path(incomplete, conf))) >= 0 @ Now, the callback must not have any additional arguments, like we want that From 71c7816c6868ca2e36f4247bd3dcc9462389af9b Mon Sep 17 00:00:00 2001 From: Daniel Bosk Date: Wed, 3 May 2023 10:42:25 +0200 Subject: [PATCH 3/3] Bumps version number --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3e131ad..7d383a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "typerconf" -version = "1.10" +version = "1.11" description = "Library to read and write configs using API and CLI with Typer" authors = ["Daniel Bosk "] license = "MIT"