Skip to content

Commit

Permalink
Feature: Extra custom aliases (#75)
Browse files Browse the repository at this point in the history
This is a follow-up PR to #73 which aims to address any bugs, add unit tests and add documentation.
  • Loading branch information
SupImDos authored Feb 9, 2025
1 parent 977bc47 commit fceab7d
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 45 deletions.
29 changes: 16 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ import pydantic_argparse

class Arguments(pydantic.BaseModel):
# Required Args
string: str = pydantic.Field(description="a required string")
integer: int = pydantic.Field(description="a required integer")
flag: bool = pydantic.Field(description="a required flag")
string: str = pydantic.Field(description="a required string", aliases=["-s"])
integer: int = pydantic.Field(description="a required integer", aliases=["-i"])
flag: bool = pydantic.Field(description="a required flag", aliases=["-f"])

# Optional Args
second_flag: bool = pydantic.Field(False, description="an optional flag")
Expand Down Expand Up @@ -69,29 +69,32 @@ if __name__ == "__main__":

```console
$ python3 example.py --help
usage: Example Program [-h] [-v] --string STRING --integer INTEGER --flag |
--no-flag [--second-flag] [--no-third-flag]
usage: Example Program [-h] [-v] [-s STRING] [-i INTEGER] [-f | --flag | --no-flag]
[--second-flag] [--no-third-flag]

Example Description

required arguments:
--string STRING a required string
--integer INTEGER a required integer
--flag, --no-flag a required flag
-s STRING, --string STRING
a required string
-i INTEGER, --integer INTEGER
a required integer
-f, --flag, --no-flag
a required flag

optional arguments:
--second-flag an optional flag (default: False)
--no-third-flag an optional flag (default: True)
--second-flag an optional flag (default: False)
--no-third-flag an optional flag (default: True)

help:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-h, --help show this help message and exit
-v, --version show program's version number and exit

Example Epilog
```

```console
$ python3 example.py --string hello --integer 42 --flag
$ python3 example.py --string hello -i 42 -f
string='hello' integer=42 flag=True second_flag=False third_flag=True
```

Expand Down
23 changes: 13 additions & 10 deletions docs/examples/simple.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,32 @@
### Check Help
```console
$ python3 simple.py --help
usage: Example Program [-h] [-v] --string STRING --integer INTEGER --flag |
--no-flag [--second-flag] [--no-third-flag]
usage: Example Program [-h] [-v] [-s STRING] [-i INTEGER] [-f | --flag | --no-flag]
[--second-flag] [--no-third-flag]

Example Description

required arguments:
--string STRING a required string
--integer INTEGER a required integer
--flag, --no-flag a required flag
-s STRING, --string STRING
a required string
-i INTEGER, --integer INTEGER
a required integer
-f, --flag, --no-flag
a required flag

optional arguments:
--second-flag an optional flag (default: False)
--no-third-flag an optional flag (default: True)
--second-flag an optional flag (default: False)
--no-third-flag an optional flag (default: True)

help:
-h, --help show this help message and exit
-v, --version show program's version number and exit
-h, --help show this help message and exit
-v, --version show program's version number and exit

Example Epilog
```

### Parse Arguments
```console
$ python3 simple.py --string hello --integer 42 --flag
$ python3 simple.py --string hello -i 42 -f
string='hello' integer=42 flag=True second_flag=False third_flag=True
```
12 changes: 12 additions & 0 deletions docs/usage/arguments/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,18 @@ class Arguments(BaseModel):
You can see the list of reserved keywords in Python at any time by typing
`:::python help("keywords")` into the Python interpreter.

A field can also be provided with a list of `aliases`, which will allow the
argument to be provided via multiple different (potentially shorter) aliases in
the command-line interface.

```python
class Arguments(BaseModel):
# We want our argument to be named `my_long_argument_name` (i.e.,
# `--my-long-argument-name`), but we also want to provide the argument via
# the aliases `-m` and `-mlan`.
my_long_argument_name: int = Field(aliases=["-m", "-mlan"])
```

## Environment Variables
Functionality to parse both required and optional arguments from environment
variables is provided via the `pydantic.BaseSettings` base class.
Expand Down
6 changes: 3 additions & 3 deletions examples/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ class Arguments(pydantic.BaseModel):
"""Simple Command-Line Arguments."""

# Required Args
string: str = pydantic.Field(description="a required string")
integer: int = pydantic.Field(description="a required integer")
flag: bool = pydantic.Field(description="a required flag")
string: str = pydantic.Field(description="a required string", aliases=["-s"])
integer: int = pydantic.Field(description="a required integer", aliases=["-i"])
flag: bool = pydantic.Field(description="a required flag", aliases=["-f"])

# Optional Args
second_flag: bool = pydantic.Field(False, description="an optional flag")
Expand Down
25 changes: 12 additions & 13 deletions src/pydantic_argparse/utils/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,29 @@

from pydantic_argparse.compatibility import pydantic

from typing import List

def names(field: pydantic.fields.ModelField, invert: bool = False) -> list[str]:
"""Standardises argument name.

def names(field: pydantic.fields.ModelField, invert: bool = False) -> List[str]:
"""Standardises the argument name and any custom aliases.
Args:
field (pydantic.fields.ModelField): Field to construct name for.
invert (bool): Whether to invert the name by prepending `--no-`.
Returns:
str: Standardised name of the argument.
List[str]: Standardised names for the argument.
"""
# Construct Prefix
prefix = "--no-" if invert else "--"

flags = []
# Add any custom aliases first
# We trust that the user has provided these correctly
flags: List[str] = []
flags.extend(field.field_info.extra.get("aliases", []))

# Add custom aliases
aliases = field.field_info.extra.get("aliases", [])
for alias in aliases:
flags.append(f"{prefix}{alias.replace('_', '-')}")

# Prepend prefix, replace '_' with '-'
# Construct prefix, prepend it, replace '_' with '-'
prefix = "--no-" if invert else "--"
flags.append(f"{prefix}{field.alias.replace('_', '-')}")

# Return the standardised name and aliases
return flags


Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def create_test_field(
type: Type[Any] = str, # noqa: A002
default: Any = ...,
description: Optional[str] = None,
aliases: Optional[list[str]] = None,
aliases: Optional[List[str]] = None,
) -> pydantic.fields.ModelField:
"""Constructs a `pydantic` field with sensible defaults for testing.
Expand All @@ -57,7 +57,7 @@ def create_test_field(
type (Type[Any]): Type of the field.
default (Any): Default value for the field.
description (Optional[str]): Description for the field.
aliases (Optional[list[str]]): List of flag aliases.
aliases (Optional[List[str]]): List of flag aliases.
Returns:
pydantic.fields.ModelField: Dynamically constructed `pydantic` model.
Expand Down
8 changes: 4 additions & 4 deletions tests/utils/test_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pydantic_argparse import utils
from tests import conftest as conf

from typing import Any, Optional
from typing import Any, List, Optional


@pytest.mark.parametrize(
Expand All @@ -28,15 +28,15 @@
)
def test_argument_names(
name: str,
aliases: list[str],
aliases: List[str],
invert: bool,
expected: str,
) -> None:
"""Tests `utils.arguments.name` Function.
"""Tests `utils.arguments.names` Function.
Args:
name (str): Argument name to test.
aliases (list[str]): List of aliases.
aliases (List[str]): List of aliases.
invert (bool): Whether to invert the name.
expected (str): Expected result of the test.
"""
Expand Down

0 comments on commit fceab7d

Please sign in to comment.