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:

Contents of my_app.py#
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:

  1. Annotating mode with Literal will restrict the permitted values for this parameter.

  2. We can configure gear with a list of strings at the CLI and - because of the tuple[str, ...] annotation - it will be coerced into a tuple of strings before being passed to main.

  3. We can use a pydantic.PositiveInt annotation for the character.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:

Running the app with an invalid value for mode#
$ 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:

Running the app with a list of strings for gear#
$ 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:

Running the app with an invalid value for character.age#
$ 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.