Skip to content

Commit

Permalink
Merge pull request #38 from mpkocher/improve-ergonomics
Browse files Browse the repository at this point in the history
Improve ergonomics by using cli= in Field.
  • Loading branch information
mpkocher authored Aug 26, 2021
2 parents abdbbcc + 1d599ce commit ff8755a
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 41 deletions.
42 changes: 22 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ If the Pydantic data model fields are reasonable well named (e.g., 'min_score',

Customizing the commandline flags or the description can be done by leveraging `description` keyword argument in `Field` from `pydantic`. See [`Field` model in Pydantic](https://pydantic-docs.helpmanual.io/usage/schema/) more details.

Custom 'short' or 'long' forms of the commandline args can be provided by using a `Tuple[str]` or `Tuple2[str, str]`. For example, `cli=('-m', '--max-records')` or `cli=('--max-records',)`.

**Note**, Pydantic interprets `...` as a "required" value when used in `Field`.

```python
Expand All @@ -91,9 +93,9 @@ from pydantic_cli import run_and_exit


class MinOptions(BaseModel):
input_file: str = Field(..., description="Path to Input H5 file", extras={'cli':('-i', '--input-file')})
max_records: int = Field(..., description="Max records to process", extras={'cli':('-m', '--max-records')})
debug: bool = Field(False, description="Enable debugging mode", extras={'cli': ('-d', '--debug')})
input_file: str = Field(..., description="Path to Input H5 file", cli=('-i', '--input-file'))
max_records: int = Field(..., description="Max records to process", cli=('-m', '--max-records'))
debug: bool = Field(False, description="Enable debugging mode", cli= ('-d', '--debug'))


def example_runner(opts: MinOptions) -> int:
Expand All @@ -114,8 +116,8 @@ from pydantic import BaseModel, Field


class MinOptions(BaseModel):
input_file: str = Field(..., description="Path to Input H5 file", extras={'cli':('-i', '--input-file')})
max_records: int = Field(..., gt=0, lte=1000, description="Max records to process", extras={'cli':('-m', '--max-records')})
input_file: str = Field(..., description="Path to Input H5 file", cli=('-i', '--input-file'))
max_records: int = Field(..., gt=0, lte=1000, description="Max records to process", cli=('-m', '--max-records'))

```

Expand Down Expand Up @@ -311,7 +313,7 @@ from pydantic import BaseModel, Field


class MinOptions(BaseModel):
debug: bool = Field(False, description="Enable debug mode", extras={'cli':('-d', '--debug')})
debug: bool = Field(False, description="Enable debug mode", cli=('-d', '--debug'))
```

If the default is `True`, running the example below with `--disable-debug` will set `debug` to `False`.
Expand All @@ -321,7 +323,7 @@ from pydantic import BaseModel, Field


class MinOptions(BaseModel):
debug: bool = Field(True, description="Disable debug mode", extras={'cli':('-d', '--disable-debug')})
debug: bool = Field(True, description="Disable debug mode", cli=('-d', '--disable-debug'))
```

### Boolean Required Field
Expand All @@ -335,7 +337,7 @@ from pydantic import BaseModel, Field


class MinOptions(BaseModel):
debug: bool = Field(..., description="Enable/Disable debugging", extras={'cli': ('--enable-debug', '--disable-debug')})
debug: bool = Field(..., description="Enable/Disable debugging", cli= ('--enable-debug', '--disable-debug'))
```
**Currently, supplying the short form of each "enable" and "disable" is not supported**.

Expand All @@ -351,9 +353,9 @@ from pydantic import BaseModel, Field
class MinOptions(BaseModel):
a: Optional[bool]
b: Optional[bool] = None
c: Optional[bool] = Field(None, extras={'cli': ('--yes-c', '--no-c')})
d: Optional[bool] = Field(False, extras={'cli':('--enable-d', '--disable-d')})
e: Optional[bool] = Field(..., extras={'cli':('--enable-e', '--disable-e')})
c: Optional[bool] = Field(None, cli= ('--yes-c', '--no-c'))
d: Optional[bool] = Field(False, cli=('--enable-d', '--disable-d'))
e: Optional[bool] = Field(..., cli=('--enable-e', '--disable-e'))
```
Note, that `x:Optional[bool]`, `x:Optional[bool] = None`, `x:Optional[bool] = Field(None)` semantically mean the same thing in Pydantic.

Expand Down Expand Up @@ -417,8 +419,8 @@ from pydantic_cli import run_and_exit


class MinOptions(BaseModel):
input_file: str = Field(..., extras={'cli':('-i',)})
max_records: int = Field(10, extras={'cli':('-m', '--max-records')})
input_file: str = Field(..., cli=('-i',))
max_records: int = Field(10, cli=('-m', '--max-records'))


def example_runner(opts: MinOptions) -> int:
Expand Down Expand Up @@ -485,13 +487,13 @@ from pydantic_cli import run_sp_and_exit, SubParser


class AlphaOptions(BaseModel):
input_file: str = Field(..., extras={'cli':('-i',)})
max_records: int = Field(10, extras={'cli':('-m', '--max-records')})
input_file: str = Field(..., cli=('-i',))
max_records: int = Field(10, cli=('-m', '--max-records'))


class BetaOptions(BaseModel):
url: AnyUrl = Field(..., extras={'cli':('-u', '--url')})
num_retries: int = Field(3, extras={'cli':('-n', '--num-retries')})
url: AnyUrl = Field(..., cli=('-u', '--url'))
num_retries: int = Field(3, cli=('-n', '--num-retries'))


def printer_runner(opts: T.Any):
Expand Down Expand Up @@ -663,9 +665,9 @@ class MinOptions(BaseModel):
class Config(DefaultConfig):
CLI_JSON_ENABLE = True

input_file: str = Field(..., extras={'cli':('-i', )})
input_hdf: str = Field(..., extras={'cli':('-d', '--hdf')})
max_records: int = Field(100, extras={'cli':('-m', '--max-records')})
input_file: str = Field(..., cli=('-i', ))
input_hdf: str = Field(..., cli=('-d', '--hdf'))
max_records: int = Field(100, cli=('-m', '--max-records'))
```

Running with the `preset.json` defined above, works as expected.
Expand Down
10 changes: 9 additions & 1 deletion pydantic_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ def __add_boolean_arg_to_parser(
return parser


def __get_cli_key_by_alias(d: T.Dict) -> T.Any:
# for backwards compatibility
try:
return d["extras"]["cli"]
except KeyError:
return d["cli"]


def _add_pydantic_field_to_parser(
parser: CustomArgumentParser,
field_id: str,
Expand Down Expand Up @@ -269,7 +277,7 @@ def _add_pydantic_field_to_parser(
try:
# cli_custom Should be a tuple2[Str, Str]
cli_custom: CustomOptsType = __process_tuple(
extra["extras"]["cli"], default_long_arg
__get_cli_key_by_alias(extra), default_long_arg
)
except KeyError:
if override_cli is None:
Expand Down
2 changes: 1 addition & 1 deletion pydantic_cli/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.0.1"
__version__ = "4.1.0"
8 changes: 4 additions & 4 deletions pydantic_cli/examples/simple_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ class Config(ExampleConfigDefaults):
title="Input File",
description="Path to the input file",
# required=True, # this is implicitly set by ...
extras={"cli": ("-f", "--input-file")},
cli=("-f", "--input-file"),
)

max_records: int = Field(
123,
title="Max Records",
description="Max number of records",
gt=0,
extras={"cli": ("-m",)},
cli=("-m",),
)

min_filter_score: float = Field(
...,
title="Min Score",
description="Minimum Score Filter that will be applied to the records",
extras={"cli": ("-s",)},
cli=("-s",),
gt=0
# or extras={'cli': ('-s', '--min-filter-score', )}
)
Expand All @@ -46,7 +46,7 @@ class Config(ExampleConfigDefaults):
title="Max Score",
description="Maximum Score Filter that will be applied to the records",
gt=0,
extras={"cli": ("-S",)}
cli=("-S",)
# or extras={'cli': ('-S', '--min-filter-score', )}
)

Expand Down
6 changes: 3 additions & 3 deletions pydantic_cli/examples/simple_with_boolean_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class Config(DefaultConfig):

# Or customizing the CLI flag with a Tuple[str, str] of (short, long), or Tuple[str] of (long, )
input_file3: str = Field(
..., description="Path to input H5 file", extras={"cli": ("-f", "--hdf5")}
..., description="Path to input H5 file", cli=("-f", "--hdf5")
)

# https://pydantic-docs.helpmanual.io/usage/models/#required-optional-fields
Expand All @@ -48,7 +48,7 @@ class Config(DefaultConfig):
beta_filter: bool = Field(
False,
description="Enable beta filter mode",
extras={"cli": ("-b", "--beta-filter")},
cli=("-b", "--beta-filter"),
)

# Again, note Pydantic will treat these as indistinguishable
Expand All @@ -70,7 +70,7 @@ class Config(DefaultConfig):
epsilon: Optional[bool] = Field(
False,
description="Enable epsilon meta-analysis.",
extras={"cli": ("--epsilon", "--disable-epsilon")},
cli=("--epsilon", "--disable-epsilon"),
)

states: Set[State]
Expand Down
6 changes: 3 additions & 3 deletions pydantic_cli/examples/simple_with_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ class Options(BaseModel):
class Config(ExampleConfigDefaults, DefaultConfig):
pass

input_file: str = Field(..., extras={"cli": ("-i", "--input")})
max_records: int = Field(10, extras={"cli": ("-m", "--max-records")})
min_filter_score: float = Field(..., extras={"cli": ("-f", "--filter-score")})
input_file: str = Field(..., cli=("-i", "--input"))
max_records: int = Field(10, cli=("-m", "--max-records"))
min_filter_score: float = Field(..., cli=("-f", "--filter-score"))
alpha: Union[int, str] = 1
values: List[str] = ["a", "b", "c"]

Expand Down
4 changes: 2 additions & 2 deletions pydantic_cli/examples/simple_with_custom_and_setup_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class Options(BaseModel):
class Config(ExampleConfigDefaults):
pass

input_file: str = Field(..., extras={"cli": ("-i", "--input")})
max_records: int = Field(10, extras={"cli": ("-m", "--max-records")})
input_file: str = Field(..., cli=("-i", "--input"))
max_records: int = Field(10, cli=("-m", "--max-records"))
# this leverages Pydantic's fundamental understanding of Enums
log_level: LogLevel = LogLevel.INFO

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class Options(BaseModel):
class Config(ExampleConfigDefaults, DefaultConfig):
CLI_SHELL_COMPLETION_ENABLE = HAS_AUTOCOMPLETE_SUPPORT

input_file: str = Field(..., extras={"cli": ("-i", "--input")})
min_filter_score: float = Field(..., extras={"cli": ("-f", "--filter-score")})
max_records: int = Field(10, extras={"cli": ("-m", "--max-records")})
input_file: str = Field(..., cli=("-i", "--input"))
min_filter_score: float = Field(..., cli=("-f", "--filter-score"))
max_records: int = Field(10, cli=("-m", "--max-records"))


def example_runner(opts: Options) -> int:
Expand Down
8 changes: 4 additions & 4 deletions pydantic_cli/examples/subparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ class AlphaOptions(BaseModel):
class Config(CustomConfig):
pass

input_file: str = Field(..., extras={"cli": ("-i", "--input")})
max_records: int = Field(10, extras={"cli": ("-m", "--max-records")})
input_file: str = Field(..., cli=("-i", "--input"))
max_records: int = Field(10, cli=("-m", "--max-records"))
log_level: LogLevel = LogLevel.DEBUG


class BetaOptions(BaseModel):
class Config(CustomConfig):
pass

url: AnyUrl = Field(..., extras={"cli": ("-u", "--url")})
num_retries: int = Field(3, extras={"cli": ("-n", "--num-retries")})
url: AnyUrl = Field(..., cli=("-u", "--url"))
num_retries: int = Field(3, cli=("-n", "--num-retries"))
log_level: LogLevel = LogLevel.INFO


Expand Down

0 comments on commit ff8755a

Please sign in to comment.