maxb2/typer-config

[FEATURE REQUEST] Create a way to save params into config file?

Closed this issue · 4 comments

This is a great library and I'm very glad that you've created it. Would it at all to add the future of automatically saving params into some format that could be read again later? This way we'll have serialization that very well complements the existing deserialization.

Current (taken from https://maxb2.github.io/typer-config/0.5.0/examples/simple_yaml/):

import typer
from typer_config import yaml_conf_callback

app = typer.Typer()


@app.command()
def main(
    arg1: str,
    config: str = typer.Option(
        "",
        callback=yaml_conf_callback,
        is_eager=True,  # THIS IS REALLY IMPORTANT 
    ),
    opt1: str = typer.Option(...),
    opt2: str = typer.Option("hello"),
):
    typer.echo(f"{opt1} {opt2} {arg1}")


if __name__ == "__main__":
    app()

Possible new API

import typer
from typer_config import yaml_conf_callback

app = typer.Typer()


@app.command()
def main(
    arg1: str,
    config: str = typer.Option(
        "",
        callback=yaml_conf_callback(save="./config_save_folder"),
        is_eager=True,  # THIS IS REALLY IMPORTANT 
    ),
    opt1: str = typer.Option(...),
    opt2: str = typer.Option("hello"),
):
    typer.echo(f"{opt1} {opt2} {arg1}")


if __name__ == "__main__":
    app()

So the callback would be a higher order function. Could this potentially make sense?

maxb2 commented

I'm not entirely sure what you mean by saving params. Do you mean that whenever you run the command like cmd --opt1 foo --opt2 bar baz it saves a file like this:

# ./config_save_folder/params.yaml

opt1: foo
opt2: bar
arg1: baz

?

If that is the case, the current design of typer-config would not be able to do this. This is because the config callbacks are executed before the rest of the parameters are parsed. The callback function is never aware of the parameters passed on the command line. The way typer-config works is that it reads parameters from a file first (this is why you must use is_eager=True) and modifies the internal state of the click app to dynamically set new default values (overriding what you specified in your source file). After this, the command line parameters are parsed and will override the new defaults.

I think the easiest way to achieve what you want is to create a function in the body of your typer command that saves the parameters passed to it. Those values would be set after command line parsing. Something like this:

import typer
from typer_config import yaml_conf_callback

app = typer.Typer()


@app.command()
def main(
    arg1: str,
    config: str = typer.Option(
        "",
        callback=yaml_conf_callback,
        is_eager=True,  # THIS IS REALLY IMPORTANT 
    ),
    opt1: str = typer.Option(...),
    opt2: str = typer.Option("hello"),
):
    save_conf("./config_save_folder", {"opt1" : opt1, "opt2" : opt2, "arg1" : arg1})
    typer.echo(f"{opt1} {opt2} {arg1}")


if __name__ == "__main__":
    app()
maxb2 commented

If you still wanted to use a higher order function, it would make the most sense to create a decorator that operates on the command functions rather than the config callbacks:

import typer
from typer_config import yaml_conf_callback

app = typer.Typer()

@app.command()
@save_params(location="./config_save_folder")
def main(
    arg1: str,
    config: str = typer.Option(
        "",
        callback=yaml_conf_callback,
        is_eager=True,  # THIS IS REALLY IMPORTANT 
    ),
    opt1: str = typer.Option(...),
    opt2: str = typer.Option("hello"),
):
    typer.echo(f"{opt1} {opt2} {arg1}")


if __name__ == "__main__":
    app()

I could look into adding this if you want.

Thank you for your detailed response and for providing examples of how to achieve the requested feature. I understand now that typer-config cannot read command line parameters before executing config callbacks due to its structure, where it reads parameters from a file first and then parses command line parameters, overriding the new defaults.

If you could look into implementing this functionality within typer-config, that would be fantastic. I believe it would be a valuable addition to the library and benefit many users. I greatly appreciate your suggestion to use a higher-order function with a decorator; it seems like a clean and efficient approach.

Once again, thank you for your time and expertise. I'm looking forward to seeing this feature implemented, and please let me know if you need any further input or clarification.

maxb2 commented

@ezhang7423 see #27 for my first pass at this feature. Any input is welcome!