BrianPugh/cyclopts

[Feature Request] Dataclasses as commands

dominikandreas opened this issue ยท 3 comments

Given some existing config dataclass, I would like to parse arguments to create the config.

Example:

import sys

from cyclopts import App

@dataclass
class MyConfig:
    a: int = 1
    b: int = 2

config = object.__new__(MyConfig)

app = App()
app.default(config.__init__)

sys.argv = [__file__,  "--a", "3", "--b", "4"]
app()
print(config)

MyConfig(a=3, b=4)

This already works as is, but I was wondering if a prettier solution already exists, either currently working or planned for version 3?

Generally, I would prefer defining a CLI around dataclasses rather than function implementations (one reason being that it's easier to extend something based on classes using inheritance).

I assume someone else has already thought about this, so before I start reinventing the wheel, I thought I should ask here.

Thanks for the great work, really like it so far ๐Ÿ‘

This will kind of work in v3; not exactly as you describe, but the main big feature of v3 is that dataclasses/pydantic/attrs/typeddict will all be allowed as type-hints.

In v3 your example will look like:

from dataclasses import dataclass
from typing import Annotated

from cyclopts import App, Parameter

app = App(name="issue-228")


@dataclass
class Config:
    a: int = 1
    """Docstring for a."""

    b: Annotated[int, Parameter(name="bar")] = 2
    """This is the docstring for python parameter "b"."""


@app.default
def my_default_command(config: Annotated[Config, Parameter(name="*")]):
    # The name "*" is special and means "squash this name"
    # Without this, items would have to be identified as "--config.a" instead of "--a"
    print(f"{config=}")


if __name__ == "__main__":
    app()

And then invoking the program from the CLI:

$ python issue-228.py --help
Usage: issue-228 COMMAND [ARGS] [OPTIONS]

โ•ญโ”€ Commands โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ --help,-h  Display this message and exit.                                     โ”‚
โ”‚ --version  Display application version.                                       โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
โ•ญโ”€ Parameters โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ A,--a      Docstring for a.                                                   โ”‚
โ”‚ BAR,--bar  This is the docstring for python parameter "b".                    โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ


$ python issue-228.py --a=100 --bar=200
config=Config(a=100, b=200)

You can play around with v3 in the dev-3.0.0 branch. Everything is mostly working, and the primary work that remains is:

  • polishing everything up
  • finishing up a few edge-case-TODOs
  • reconsider what elements/attributes/classes should be public
  • update documentation.

For this specific example, it actually demonstrates that as of right now, positional arguments are not correctly being passed into Config, so I'll have to fix that! If you end up giving it a try, just know that things may change. If you have feedback, I would also be very open to it!

Thanks for the great work, really like it so far ๐Ÿ‘

Thank you!

ok, the positional arguments should now work on dev-3.0.0. The example provided above now fully works as expected.

v3.0.0 has been released! Please let me know if you run into any issues, thanks!