diff --git a/README.md b/README.md index e96d295..6d24557 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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: @@ -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')) ``` @@ -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`. @@ -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 @@ -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**. @@ -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. @@ -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: @@ -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): @@ -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. diff --git a/pydantic_cli/__init__.py b/pydantic_cli/__init__.py index 4cdf1b4..3d4cd82 100644 --- a/pydantic_cli/__init__.py +++ b/pydantic_cli/__init__.py @@ -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, @@ -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: diff --git a/pydantic_cli/_version.py b/pydantic_cli/_version.py index 76ad18b..7039708 100644 --- a/pydantic_cli/_version.py +++ b/pydantic_cli/_version.py @@ -1 +1 @@ -__version__ = "4.0.1" +__version__ = "4.1.0" diff --git a/pydantic_cli/examples/simple_schema.py b/pydantic_cli/examples/simple_schema.py index 4539841..5d211df 100644 --- a/pydantic_cli/examples/simple_schema.py +++ b/pydantic_cli/examples/simple_schema.py @@ -21,7 +21,7 @@ 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( @@ -29,14 +29,14 @@ class Config(ExampleConfigDefaults): 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', )} ) @@ -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', )} ) diff --git a/pydantic_cli/examples/simple_with_boolean_custom.py b/pydantic_cli/examples/simple_with_boolean_custom.py index 0443353..120ef8c 100644 --- a/pydantic_cli/examples/simple_with_boolean_custom.py +++ b/pydantic_cli/examples/simple_with_boolean_custom.py @@ -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 @@ -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 @@ -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] diff --git a/pydantic_cli/examples/simple_with_custom.py b/pydantic_cli/examples/simple_with_custom.py index 1aee667..b6ab7ff 100644 --- a/pydantic_cli/examples/simple_with_custom.py +++ b/pydantic_cli/examples/simple_with_custom.py @@ -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"] diff --git a/pydantic_cli/examples/simple_with_custom_and_setup_log.py b/pydantic_cli/examples/simple_with_custom_and_setup_log.py index 8f45067..d22f29d 100644 --- a/pydantic_cli/examples/simple_with_custom_and_setup_log.py +++ b/pydantic_cli/examples/simple_with_custom_and_setup_log.py @@ -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 diff --git a/pydantic_cli/examples/simple_with_shell_autocomplete_support.py b/pydantic_cli/examples/simple_with_shell_autocomplete_support.py index 1270ee0..0ef55df 100644 --- a/pydantic_cli/examples/simple_with_shell_autocomplete_support.py +++ b/pydantic_cli/examples/simple_with_shell_autocomplete_support.py @@ -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: diff --git a/pydantic_cli/examples/subparser.py b/pydantic_cli/examples/subparser.py index 2e0a416..8c64395 100644 --- a/pydantic_cli/examples/subparser.py +++ b/pydantic_cli/examples/subparser.py @@ -28,8 +28,8 @@ 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 @@ -37,8 +37,8 @@ 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