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.
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
str
values toNone
. - Parse choices (i.e.,
Enum
s orLiteral
s) fromstr
s 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.