-
-
Notifications
You must be signed in to change notification settings - Fork 733
Is there a way to construct a dataclass directly from the command line inputs? #197
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This would be really helpful; in particular it would be easier to re-use a set of command line flags across different commands. |
Maybe it's better to add helper that runs callable ( I found With helper: from dataclasses import dataclass, is_dataclass
from typing import Any, Callable, NoReturn, Type
import typer
T = TypeVar('T')
def run_dataclass(tp: Type[T], callback: Callable[[T], Any]) -> NoReturn:
assert is_dataclass(tp)
@dataclass
class BindType(tp): # type: ignore
def __post_init__(self):
super().__post_init__()
callback(self)
typer.run(BindType) First snippet starts working: from dataclasses import dataclass
@dataclass
class BaseFlags:
arg1: int = typer.Argument(help='something', default=1)
arg2: int = typer.Argument(help='something', default=2)
@dataclass
class Flags(BaseFlags):
arg3: int = typer.Argument(help='something', default=3)
arg4: int = typer.Argument(help='something', default=4)
def main(args: Flags):
print(args)
if __name__ == '__main__':
run_dataclass(Flags, main) |
Hi @arquolo, thanks for your answer! If I try your code snipped, I get the error |
Here's another helper which is based upon decorators. It might still be a bit rough around the edges (and I didn't test it with Typer because I need to get back to work) but at least the general idea should be clear: from dataclasses import dataclass, is_dataclass
from typing import Callable, Type, TypeVar
@dataclass
class MyArguments:
name: str
formal: bool = False
T = TypeVar('T')
R = TypeVar('R')
# decorator generator
def inject_dataclass_args(cls: Type[T]):
assert is_dataclass(cls)
def decorator(func: Callable[[T], R]) -> R:
def wrapped(*args, **kwargs):
args_as_data_obj = cls(*args, **kwargs)
return func(args_as_data_obj)
wrapped.__annotations__ = cls.__init__.__annotations__
return wrapped
return decorator
# Usage:
@inject_dataclass_args(MyArguments)
def goodbye(data_obj: MyArguments):
if data_obj.formal:
print(f"Goodbye Ms. {data_obj.name}. Have a good day.")
else:
print(f"Bye {data_obj.name}!")
if __name__ == "__main__":
typer.run(goodbye) One issue that I'm already foreseeing is that Typer won't notice that A quick test shows that @arquolo's solution works better here because def inject_dataclass_args(cls: Type[T]):
assert is_dataclass(cls)
def decorator(func: Callable[[T], R]) -> R:
@dataclass
class wrapped(cls):
def __post_init__(self):
super().__post_init__()
func(self)
return wrapped
return decorator Either way, the nice thing about a decorator here is that it keeps the signature rewriting close to the function whose signature it's changing. |
Side note: On a meta level this issue is somewhat related to determining the full type of a function (and "copying" it to another function) which is being discussed here. |
I tweaked @codethief 's code and came up with this: https://gist.github.com/tbenthompson/9db0452445451767b59f5cb0611ab483 |
I have an already productionized version of this idea, and it's here: https://github.com/rec/dtyper |
@rec From a skim of the docs, I think dtyper is doing the reverse of what's asked for here. It's making a dataclass from a CLI function. Instead, I already have a dataclass and I want to create a CLI function from that existing dataclass. Am I misunderstanding? |
Oops, no, the misreading is mine. You are exactly right that is the reverse
thing.
However, I don't quite see how you can really get this to work. How can you
set the help for each argument? Or the names of command line flags?
…On Fri, May 12, 2023 at 11:18 PM Ben Thompson ***@***.***> wrote:
I have an already productionized version of this idea, and it's here:
https://github.com/rec/dtyper
@rec <https://github.com/rec> From a skim of the docs, I think dtyper is
doing the reverse of what's asked for here. It's making a dataclass from a
CLI function. I already have a dataclass and I want to create a CLI
function from that existing dataclass. Am I misunderstanding?
—
Reply to this email directly, view it on GitHub
<#197 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAB53MQ7FAFIDNAEJXB3DF3XF2SKTANCNFSM4UBMIWRA>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
/t
PGP Key: ***@***.***
*https://tom.ritchford.com <https://tom.ritchford.com>*
*https://tom.swirly.com <https://tom.swirly.com>*
|
The solution above manages this by copying the type signature of the dataclass'
you get a CLI like:
This is enough for making a quick and easy CLI for personal use or a rough project. |
Hi, is there a way to directly construct a dataclass from the command line inputs with typer? Like an argparser would do.
For a large amount of arguments this would improve readability of the code, and would permit inheritances of dataclasses like in the example below:
Thanks a lot for the great package and your help!
The text was updated successfully, but these errors were encountered: