Arguments
Overview
At the core of pydantic-argparse is the pydantic model, in which
arguments are declared with pydantic fields. This combination of the
model and its fields defines the schema for your command-line arguments.
Pydantic
Models
A pydantic model is simply a dataclass-like class that inherits from the
pydantic.BaseModel base class. In pydantic-argparse, this model is used to
declaratively define your command-line arguments.
class Arguments(BaseModel):
# Required
string: str
integer: int
number: float
# Optional
boolean: bool = False
Arbitrary data, such as raw command-line arguments, can be passed to a model.
After parsing and validation pydantic guarantees that the fields of the
resultant model instance will conform to the field types defined on the model.
Info
For more information about pydantic models, see the pydantic docs.
Fields
A pydantic model contains fields, which are the model class attributes.
These fields define each pydantic-argparse command-line argument, and they
can be declared either implicitly (as above), or explicitly (as below).
class Arguments(BaseModel):
# Required
string: str = Field(description="this argument is a string")
integer: int = Field(description="this argument is an integer")
number: float = Field(description="this argument is a number")
# Optional
boolean: bool = Field(False, description="this argument is a boolean")
Explicitly defining fields can provide extra information about an argument, either for the command-line interface, the model schema or features such as complex validation.
Info
For more information about pydantic fields, see the pydantic docs.
Arguments
Required
A field defines a required argument if it has no default value, or a default
value of the Ellipses (...) singleton object.
class Arguments(BaseModel):
a: int
b: int = ...
c: int = Field()
d: int = Field(...)
Optional
A field defines an optional argument if it has a default value.
class Arguments(BaseModel):
a: int = 42
b: int = Field(42)
A field can also define an optional argument if it is type-hinted as
Optional. This type-hinting also allows the value of None for the field.
class Arguments(BaseModel):
a: Optional[int]
b: Optional[int] = None
c: Optional[int] = Field()
d: Optional[int] = Field(None)
Descriptions
A field can be provided with a description, which will appear in the
command-line interface help message.
class Arguments(BaseModel):
a: int = Field(description="this is the command-line description!")
Aliases
A field can be provided with an alias, which will change the argument name in
the command-line interface.
class Arguments(BaseModel):
# We want our argument to be named `class` (i.e., `--class`), but `class`
# is a reserved keyword in Python. To accomplish this, we can use the Field
# `alias` to override the argument name.
class_argument: int = Field(alias="class")
Tip
This feature allows you to define arguments that use a reserved python
keyword as the name. For example: class, continue, async.
You can see the list of reserved keywords in Python at any time by typing
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.
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.
Simply inherit from pydantic.BaseSettings instead of pydantic.BaseModel:
class Arguments(BaseSettings):
integer: int
Arguments can then be provided via environment variables:
$ export INTEGER=123
$ python3 example.py
Arguments(integer=123)
$ INTEGER=456 python3 example.py
Arguments(integer=456)
Arguments supplied via the command-line take precedence over environment variables:
$ export INTEGER=123
$ python3 example.py --integer 42
Arguments(integer=42)
$ INTEGER=456 python3 example.py --integer 42
Arguments(integer=42)
Validation
When parsing command-line arguments with parser.parse_typed_args(), the raw
values are parsed and validated using pydantic. The parser has different
behaviours depending on whether the supplied command-line arguments are valid.
Consider the following example model:
class Arguments(BaseModel):
integer: int
Success
When the provided command-line arguments satisfy the pydantic model, a
populated instance of the model is returned
$ python3 example.py --integer 42
Arguments(integer=42)
Failure
When the provided command-line arguments do not satisfy the pydantic model,
the ArgumentParser will provide an error to the user. For example:
$ python3 example.py
usage: example.py [-h] --integer INTEGER
example.py: error: 1 validation error for Arguments
integer
field required (type=value_error.missing)
$ python3 example.py --integer hello
usage: example.py [-h] --integer INTEGER
example.py: error: 1 validation error for Arguments
integer
value is not a valid integer (type=type_error.integer)
Note
The validation error shown to the user is the same as the error that
pydantic provides with its pydantic.ValidationError.
Under the Hood
Under the hood pydantic-argparse dynamically generates extra custom
@pydantic.validator class methods for each of your argument fields.
These validators behave slightly differently for each argument type, but in general they:
- Parse empty
strvalues toNone. - Parse choices (i.e.,
Enums orLiterals) fromstrs to their respective types (if applicable). - Otherwise, pass values through unchanged.
The validators are constructed with the pre=True argument, ensuring that they
are called before any of the user's @pydantic.validator class methods and
the built-in pydantic field validation. This means they are provided with the
most raw input data possible.
After the generated validators have been called, the fields are parsed as per
usual by the built-in pydantic field validation for their respective types.
Note
pydantic-argparse also enhances pydantic's built-in
environment variable parsing capabilities.
By default, pydantic attempts to parse complex types as json values. If
this parsing fails a pydantic.env_settings.SettingsError is raised and
the argument parsing fails immediately with an obscure error message. This
means the values never reach the generated pydantic-argparse validators,
the user's custom validators or the built-in pydantic field validation.
As a solution, pydantic-argparse wraps the existing
pydantic.BaseSettings.parse_env_var() environment variable parsing class
method to handle this situation. The wrapped parser passes through raw
str values unchanged if the json parsing fails. This allows the raw
string values to be parsed, validated and handled by the generated
pydantic-argparse validators and the built-in pydantic field validators
if applicable.