tl;dr
When wrapping your main function with hydra_zen.zen
, pass in hydra_zen.third_party.pydantic.pydantic_parser
to the instantiation_wrapper
argument of
zen
. Additionally, for any hydra_zen.instantiate
calls you make, pass
this parser in as the _target_wrapper_
argument.
Prerequisites
Your must install pydantic in your Python environment in order to complete this How-To guide.
Add Pydantic Type Checking and Parsing to Your Hydra App#
Hydra’s runtime type checking is limited to only a narrow subset of Python’s typing features. By contrast, Pydantic provides much more comprehensive type checking
capabilities; additionally, it is capable of parsing CLI inputs into complex data
structures (e.g., convert a string to a pathlib.Path
). In this how-to
guide, we will add pydantic parsing to all config-instantiation sites in our
Hydra-app.
Consider the following simple hydra-zen app:
from hydra_zen import store
from hydra_zen.third_party.pydantic import pydantic_parser
from typing import Literal
from pydantic import PositiveInt
from dataclasses import dataclass
@dataclass
class Character:
age: PositiveInt = 22
name: str = "Bobby"
def main(
character: Character,
gear: tuple[str, ...] = (),
mode: Literal["easy", "hard"] = "easy",
):
print(f"{character=!r} {gear=!r} {mode=!r}")
if __name__ == "__main__":
from hydra_zen import zen
store(main, hydra_defaults=["_self_", {"character": "base"}])
store(Character, group="character", name="base")
store.add_to_hydra_store()
zen(
main,
# This is the key ingredient
instantiation_wrapper=pydantic_parser,
).hydra_main(
config_name="main",
config_path=None,
version_base="1.3",
)
By specifying instantiation_wrapper=pydantic_parser
in our hydra_zen.zen
call, we
insure that all config-instantiation sites in our Hydra-app will use pydantic parsing.
Because of this, we will benefit from the following features:
Annotating
mode
withLiteral
will restrict the permitted values for this parameter.We can configure
gear
with a list of strings at the CLI and - because of thetuple[str, ...]
annotation - it will be coerced into a tuple of strings before being passed tomain
.We can use a
pydantic.PositiveInt
annotation for thecharacter.age
nested field; this will ensure that the age is a valid value.
Let’s check that mode
is restricted to the values easy
and hard
:
$ python my_app.py mode='medium'
Traceback (most recent call last):
...
pydantic_core._pydantic_core.ValidationError: 1 validation error for main
mode
Input should be 'easy' or 'hard'
Next, let’s see that gear
can be passed a list, which will be coerced into a tuple:
$ python my_app.py gear='[sword,shield]'
character=Character(age=22, name='Bobby') gear=('sword', 'shield') mode='easy'
Finally, let’s see that character.age
is correctly parsed as a PositiveInt
:
$ python my_app.py character.age=-1
Traceback (most recent call last):
...
Error in call to target '__main__.Character':
1 validation error for Character
age
Input should be greater than 0
In this way, we can use Pydantic to add powerful type checking and parsing to our Hydra apps.
If your app includes manual calls to hydra_zen.instantiate
, you can also pass in the
pydantic_parser
as the _target_wrapper_
argument to ensure that these
config-instantiation calls also use Pydantic parsing.
Note
Keep in mind that this pydantic parsing layer only occurs when we instantiate
configs that have _target_
fields, and that it uses the annotations of the
_target_
objects.