## Implicit argument references
Other than the base config, the sweep config and the patch config, `py-gen-ml` generates a CLI argument parser via another kind of Pydantic base model.

As CLI arguments should be quick and easy to use, `py-gen-ml` tries to find 'shortcuts' for fields that are deeply nested in the config. A shortcut is automatically created if there's exactly one path that leads to a given field _and_ the field name is unique across the entire nested structure.

Imagine we have the following protobuf structure:

```protobuf
--8<-- "docs/snippets/proto/cli_demo.proto"
```

This creates a CLI args class that looks like this:

```python
--8<-- "docs/snippets/src/pgml_out/cli_demo_cli_args.py"
```

This we can use as shown below:

```python linenums="1" hl_lines="4 13 16 21"
# cli_demo.py
from pgml_out.cli_demo_cli_args import ProjectArgs
from pgml_out.cli_demo_base import Project
import py_gen_ml as pgml
import typer


app = typer.Typer(pretty_exceptions_enable=False)

@pgml.pgml_cmd(app)
def main(
    config_path: str = "config.yaml",
    cli_args: ProjectArgs = typer.Option(...),
):
    project = Project.from_yaml_file(config_path)
    project = project.apply_args(cli_args)
    print(project)


if __name__ == "__main__":
    app()
```

You can the get a quick view of the shortcuts that `py-gen-ml` generated by running:

```bash
python cli_demo.py --help
```

This will print out the following:

```
 Usage: cli_demo.py [OPTIONS]

╭─ Options ──────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --config-path        TEXT     [default: config.yaml]                                                               │
│ --num-layers         INTEGER  Number of layers. Maps to 'model.num_layers' [default: None]                         │
│ --path               TEXT     Path to the dataset. Maps to 'data.dataset.path' [default: None]                     │
│ --num-epochs         INTEGER  Number of epochs. Maps to 'training.num_epochs' [default: None]                      │
│ --num-workers        INTEGER  Number of workers for loading the dataset. Maps to 'data.num_workers'                │
│                               [default: None]                                                                      │
│ --help                        Show this message and exit.                                                          │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```

As you can see, `py-gen-ml` automatically generated a shortcut for the `path` field, since only one path leads to it.

You can also see that the `num_layers` field is mapped to the `model.num_layers` field, etc.

!!! note
    We chose to build on top of [Typer](https://typer.tiangolo.com/) for the CLI argument parsing, since it has a easy to use API and is well integrated with Pydantic.

## Explicit argument references

You can also specify explicit argument reeferences in the proto file. This is useful if the argument is not a direct field in the CLI args, e.g. because there are multiple paths to the same field.

Imagine we have the following protobuf structure:

```protobuf linenums="1" hl_lines="11-14"
--8<-- "docs/snippets/proto/cli_extension_demo.proto"
```

Note the highlighted section where we added explicit argument references. With these, we can map to fields that are nested deep in the message structure. Now `--train-path` and `--test-path` become available as CLI arguments.

Without the explicit argument references, the CLI args class would look like this:

```python
# cli_demo_extension_cli_args.py
class ProjectArgs(pgml.YamlBaseModel):
    """Global configuration"""

    num_epochs: typing.Annotated[typing.Optional[int], typer.Option(help="Number of epochs. Maps to 'training.num_epochs'"), pydantic.Field(None), pgml.ArgRef("training")]
    """Number of epochs"""

    num_layers: typing.Annotated[typing.Optional[int], typer.Option(help="Number of layers. Maps to 'model.num_layers'"), pydantic.Field(None), pgml.ArgRef("model")]
    """Number of layers"""

    num_workers: typing.Annotated[typing.Optional[int], typer.Option(help="Number of workers for loading the dataset. Maps to 'data.num_workers'"), pydantic.Field(None), pgml.ArgRef("data")]
    """Number of workers for loading the dataset"""
```

With the explicit argument references, the CLI args class will look like this:

```python linenums="1" hl_lines="14-18"
# cli_demo_extension_cli_args.py
class ProjectArgs(pgml.YamlBaseModel):
    """Global configuration"""

    num_epochs: typing.Annotated[typing.Optional[int], typer.Option(help="Number of epochs. Maps to 'training.num_epochs'"), pydantic.Field(None), pgml.ArgRef("training")]
    """Number of epochs"""

    num_layers: typing.Annotated[typing.Optional[int], typer.Option(help="Number of layers. Maps to 'model.num_layers'"), pydantic.Field(None), pgml.ArgRef("model")]
    """Number of layers"""

    num_workers: typing.Annotated[typing.Optional[int], typer.Option(help="Number of workers for loading the dataset. Maps to 'data.num_workers'"), pydantic.Field(None), pgml.ArgRef("data")]
    """Number of workers for loading the dataset"""

    train_path: typing.Annotated[typing.Optional[str], typer.Option(help="Path to the dataset. Maps to 'path.train_dataset.data.path'"), pydantic.Field(None), pgml.ArgRef("path.train_dataset.data")]
    """Path to the dataset"""

    test_path: typing.Annotated[typing.Optional[str], typer.Option(help="Path to the dataset. Maps to 'path.test_dataset.data.path'"), pydantic.Field(None), pgml.ArgRef("path.test_dataset.data")]
    """Path to the dataset"""
```

Note that there are two additional arguments that are not present in the class above. These are the `train_path` and `test_path` arguments. These are explicit argument references that map to the `train_dataset` and `test_dataset` fields in the `data` field of the `Project` message.

Printing out the help string for the CLI args will show the explicit argument references:


```python
# cli_demo_extension.py
from pgml_out.cli_demo_extension_cli_args import ProjectArgs
from pgml_out.cli_demo_extension_base import Project
import py_gen_ml as pgml
import typer


app = typer.Typer(pretty_exceptions_enable=False)


def main(
    config_path: str = "config.yaml",
    cli_args: ProjectArgs = typer.Option(...),
):
    project = Project.from_yaml_file(config_path)
    project = project.apply_args(cli_args)
    print(project)


if __name__ == "__main__":
    pgml.command_from_func(app, main)
    app()
```

```bash
python cli_demo_extension.py --help
```

```
 Usage: cli_demo_extension.py [OPTIONS]

╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --config-path        TEXT     [default: config.yaml]                                                                 │
│ --num-epochs         INTEGER  Number of epochs. Maps to 'training.num_epochs' [default: None]                        │
│ --num-layers         INTEGER  Number of layers. Maps to 'model.num_layers' [default: None]                           │
│ --num-workers        INTEGER  Number of workers for loading the dataset. Maps to 'data.num_workers' [default: None]  │
│ --train-path         TEXT     Path to the dataset. Maps to 'path.train_dataset.data.path' [default: None]            │
│ --test-path          TEXT     Path to the dataset. Maps to 'path.test_dataset.data.path' [default: None]             │
│ --help                        Show this message and exit.                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
