edornd / argdantic

Typed command line interfaces with argparse and pydantic
MIT License
38 stars 4 forks source link

Add support for dynamic source for my pydantic config #40

Closed gblanco10 closed 3 weeks ago

gblanco10 commented 6 months ago

Speaking of my typical use case scenario I would have a main defined like this:


class Config(BaseModel):
    data: str
    in_dim : int
    out_dim : int

@parser.command()
def main(config: Config, name: str, [other args]):

and I would love to have the possibility to specify via CLI args the path to use for loading the base config. In this scenario, I would like to have the help like this

usage: cli.py [-h] --config PATH --config.data TEXT --config.in-dim INT--config.out-dim INT

where the first argument specifies where to load the original pydantic Config that will then be overridden by the cli args eventually specified.

In this case I can use different base config specified at runtime by the first argument which are customized by the specified command line arguments

edornd commented 6 months ago

I'm not 100% conviced by the implementation, but it looks like it could work by using ad-hoc BaseModel classes. Something like this:

from typing import Set

from pydantic import BaseModel

from argdantic import ArgParser
from argdantic.sources.yaml import YamlModel

class Image(YamlModel):
    url: str = None
    name: str = "default"

class Item(BaseModel):
    name: str = "a"
    description: str = None
    price: float = 0.1
    tags: Set[str] = set()
    image: Image = None

cli = ArgParser()

@cli.command()
def create_item(item: Item = None):
    print(item)

if __name__ == "__main__":
    cli()

Returns an help output such as this>

usage: example.py [-h] [--item.name TEXT] [--item.description TEXT] [--item.price FLOAT] [--item.tags [TEXT ...]] [--item.image.url TEXT]
                  [--item.image.name TEXT] [--item.image PATH]

options:
  -h, --help            show this help message and exit
  --item.name TEXT           (default: a)
  --item.description TEXT
  --item.price FLOAT         (default: 0.1)
  --item.tags [TEXT ...]     (default: set())
  --item.image.url TEXT
  --item.image.name TEXT     (default: default)
  --item.image PATH

Where --item.image expects a YAML file that only contains its subset, e.g.:

url: "example.com"
name: "test.png"

On top of that, the subfields can be overridden from CLI, allowing to do something like --item.image file.yml --item.image.name another. This will ignore the file content for the item.image.name field, and use the result from the CLI instead.

It needs thorough testing, but looks like working!