hydra_zen.builds#

hydra_zen.builds(hydra_target, /, *pos_args, zen_partial=None, zen_wrappers=(), zen_meta=None, populate_full_signature=False, zen_exclude=(), hydra_recursive=None, hydra_convert=None, hydra_defaults=None, builds_bases=(), zen_dataclass=None, **kwargs_for_target)#

builds(target, *args, **kw) returns a Hydra-compatible config that, when instantiated, returns target(*args, **kw).

I.e., instantiate(builds(target, *args, **kw)) == target(*args, **kw)

Consult the Notes section for more details, and the Examples section to see the various features of builds in action.

Parameters:
hydra_targetT (Callable)

The target object to be configured. This is a required, positional-only argument.

*pos_argsSupportedPrimitive

Positional arguments passed as <hydra_target>(*pos_args, ...) upon instantiation.

Arguments specified positionally are not included in the config’s signature and are stored as a tuple bound to in the _args_ field.

**kwargs_for_targetSupportedPrimitive

The keyword arguments passed as <hydra_target>(..., **kwargs_for_target) upon instantiation.

The arguments specified here determine the signature of the resulting config, unless populate_full_signature=True is specified (see below).

Named parameters of the forms that have the prefixes hydra_, zen_ or _zen_ are reserved to ensure future-compatibility, and thus cannot be specified by the user.

zen_partialOptional[bool]

If True, then the resulting config will instantiate as functools.partial(<hydra_target>, *pos_args, **kwargs_for_target). Thus this enables the partial-configuration of objects.

Specifying zen_partial=True and populate_full_signature=True together will populate the config’s signature only with parameters that: are explicitly specified by the user, or that have default values specified in the target’s signature. I.e. it is presumed that un-specified parameters that have no default values are to be excluded from the config.

zen_wrappersNone | Callable | Builds | InterpStr | Sequence[None | Callable | Builds | InterpStr]

One or more wrappers, which will wrap hydra_target prior to instantiation. E.g. specifying the wrappers [f1, f2, f3] will instantiate as:

f3(f2(f1(<hydra_target>)))(*args, **kwargs)

Wrappers can also be specified as interpolated strings [2] or targeted configs.

zen_metaOptional[Mapping[str, SupportedPrimitive]]

Specifies field-names and corresponding values that will be included in the resulting config, but that will not be used to instantiate <hydra_target>. These are called “meta” fields.

populate_full_signaturebool, optional (default=False)

If True, then the resulting config’s signature and fields will be populated according to the signature of <hydra_target>; values also specified in **kwargs_for_target take precedent.

This option is not available for objects with inaccessible signatures, such as NumPy’s various ufuncs.

zen_excludeCollection[str | int] | Callable[[str], bool], optional (default=[])

Specifies parameter names and/or indices, or a function for checking names, to exclude those parameters from the config-creation process.

Note that inherited fields cannot be excluded.
zen_convertOptional[ZenConvert]

A dictionary that modifies hydra-zen’s value and type conversion behavior. Consists of the following optional key-value pairs (hydra_zen.typing.ZenConvert):

  • dataclassbool (default=True):

    If True any dataclass type/instance without a _target_ field is automatically converted to a targeted config that will instantiate to that type/instance. Otherwise the dataclass type/instance will be passed through as-is.

  • flat_target: bool (default=True)

    If True (default), builds(builds(f)) is equivalent to builds(f). I.e. the second builds call will use the _target_ field of its input, if it exists.

builds_basesTuple[Type[DataClass], …]

Specifies a tuple of parent classes that the resulting config inherits from.

hydra_recursiveOptional[bool], optional (default=True)

If True, then Hydra will recursively instantiate all other hydra-config objects nested within this config [3].

If None, the _recursive_ attribute is not set on the resulting config.

hydra_convertOptional[Literal[“none”, “partial”, “all”, “object”]], optional (default=”none”)

Determines how Hydra handles the non-primitive, omegaconf-specific objects passed to <hydra_target> [4].

  • "none": No conversion occurs; omegaconf containers are passed through (Default)

  • "partial": DictConfig and ListConfig objects converted to dict and

list, respectively. Structured configs and their fields are passed without conversion. - "all": All passed objects are converted to dicts, lists, and primitives, without a trace of OmegaConf containers. - "object": Passed objects are converted to dict and list. Structured Configs are converted to instances of the backing dataclass / attr class.

If None, the _convert_ attribute is not set on the resulting config.

hydra_defaultsNone | list[str | dict[str, str | list[str] | None ]], optional (default = None)

A list in an input config that instructs Hydra how to build the output config [6] [7]. Each input config can have a Defaults List as a top level element. The Defaults List itself is not a part of output config.

zen_dataclassOptional[DataclassOptions]

A dictionary that can specify any option that is supported by dataclasses.make_dataclass() other than fields. The default value for unsafe_hash is True.

target can be specified as a string to override the _target_ field set on the dataclass type returned by builds.

The module field can be specified to enable pickle compatibility. See hydra_zen.typing.DataclassOptions for details.

frozenbool, optional (default=False)

Deprecated since version 0.9.0: frozen will be removed in hydra-zen 0.10.0. It is replaced by zen_dataclass={'frozen': <bool>}.

If True, the resulting config will create frozen (i.e. immutable) instances. I.e. setting/deleting an attribute of an instance will raise dataclasses.FrozenInstanceError at runtime.

dataclass_nameOptional[str]

Deprecated since version 0.9.0: dataclass_name will be removed in hydra-zen 0.10.0. It is replaced by zen_dataclass={'cls_name': <str>}.

If specified, determines the name of the returned class object.

Returns:
ConfigType[Builds[Type[T]]] | Type[PartialBuilds[Type[T]]]

A dynamically-generated structured config (i.e. a dataclass type) that describes how to build hydra_target.

Raises:
hydra_zen.errors.HydraZenUnsupportedPrimitiveError

The provided configured value cannot be serialized by Hydra, nor does hydra-zen provide specialized support for it. See Configuration-Value Types Supported by Hydra and hydra-zen for more details.

See also

instantiate

Instantiates a configuration created by builds, returning the instantiated target.

make_custom_builds_fn

Returns a new builds function with customized default values.

make_config

Creates a general config with customized field names, default values, and annotations.

get_target

Returns the target-object from a targeted structured config.

just

Produces a config that, when instantiated by Hydra, “just” returns the un-instantiated target-object.

to_yaml

Serialize a config as a yaml-formatted string.

Notes

The following pseudo code conveys the core functionality of builds:

from dataclasses import make_dataclass

def builds(self,target, populate_full_signature=False, **kw):
    # Dynamically defines a Hydra-compatible dataclass type.
    # Akin to doing:
    #
    # @dataclass
    # class Builds_thing:
    #     _target_: str = get_import_path(target)
    #     # etc.

    _target_ = get_import_path(target)

    if populate_full_signature:
        sig = get_signature(target)
        kw = {**sig, **kw}  # merge w/ preference for kw

    type_annots = [get_hints(target)[k] for k in kw]

    fields = [("_target_", str, _target_)]
    fields += [
        (
            field_name,
            hydra_compat_type_annot(hint),
            hydra_compat_val(v),
        )
        for hint, (field_name, v) in zip(type_annots, kw.items())
    ]

    Config = make_dataclass(f"Builds_{target}", fields)
    return Config

The resulting “config” is a dynamically-generated dataclass type [5] with Hydra-specific attributes attached to it [1]. It possesses a _target_ attribute that indicates the import path to the configured target as a string.

Using any of the zen_xx features will result in a config that depends explicitly on hydra-zen. I.e. hydra-zen must be installed in order to instantiate the resulting config, including its yaml version.

For details of the annotation SupportedPrimitive, see Configuration-Value Types Supported by Hydra and hydra-zen.

Type annotations are inferred from the target’s signature and are only retained if they are compatible with Hydra’s limited set of supported annotations; otherwise an annotation is automatically ‘broadened’ until it is made compatible with Hydra.

builds provides runtime validation of user-specified arguments against the target’s signature. E.g. specifying mis-named arguments or too many arguments will cause builds to raise.

References

Examples

These examples describe:

  • Basic usage

  • Creating a partial config

  • Auto-populating parameters

  • Composing configs via inheritance

  • Runtime validation performed by builds

  • Using meta-fields

  • Using zen-wrappers

  • Creating a pickle-compatible config

  • Creating a frozen config

  • Support for partial’d targets

A helpful utility for printing examples

>>> from hydra_zen import builds, instantiate, to_yaml
>>> def pyaml(x):
...     # for pretty printing configs
...     print(to_yaml(x))

Basic Usage

Lets create a basic config that describes how to ‘build’ a particular dictionary.

>>> Conf = builds(dict, a=1, b='x')

The resulting config is a dataclass with the following signature and attributes:

>>> Conf  # signature: Conf(a: Any = 1, b: Any = 'x')
<class 'types.Builds_dict'>
>>> pyaml(Conf)
_target_: builtins.dict
a: 1
b: x

The instantiate function is used to enact this build – to create the dictionary.

>>> instantiate(Conf)  # calls: `dict(a=1, b='x')`
{'a': 1, 'b': 'x'}

The default parameters that we provided can be overridden.

>>> new_conf = Conf(a=10, b="hi")  # an instance of our dataclass
>>> instantiate(new_conf)  # calls: `dict(a=10, b='hi')`
{'a': 10, 'b': 'hi'}

Positional arguments are supported.

>>> Conf = builds(len, [1, 2, 3])
>>> Conf._args_  # type: ignore
[1, 2, 3]
>>> instantiate(Conf)
3

Creating a Partial Config

builds can be used to partially-configure a target. Let’s create a config for the following function

>>> def a_two_tuple(x: int, y: float): return x, y

such that we only configure the parameter x.

>>> PartialConf = builds(a_two_tuple, x=1, zen_partial=True)  # configures only `x`
>>> pyaml(PartialConf)
_target_: __main__.a_two_tuple
_partial_: true
x: 1

Instantiating this config will return functools.partial(a_two_tuple, x=1).

>>> partial_func = instantiate(PartialConf)
>>> partial_func
functools.partial(<function a_two_tuple at 0x00000220A7820EE0>, x=1)

And thus the remaining parameter can be provided post-instantiation.

>>> partial_func(y=22.0)  # providing the remaining parameter
(1, 22.0)

Auto-populating parameters

The configurable parameters of a target can be auto-populated in our config. Suppose we want to configure the following function.

>>> def bar(x: bool, y: str = 'foo'): return x, y

The following config will have a signature that matches f; the annotations and default values of the parameters of f are explicitly incorporated into the config.

>>> # signature: `Builds_bar(x: bool, y: str = 'foo')`
>>> Conf = builds(bar, populate_full_signature=True)
>>> pyaml(Conf)
_target_: __main__.bar
x: ???
'y': foo

zen_exclude can be used to name parameter to be excluded from the auto-population process:

>>> Conf2 = builds(bar, populate_full_signature=True, zen_exclude=["y"])
>>> pyaml(Conf2)
_target_: __main__.bar
x: ???

to exclude parameters by index:

>>> Conf2 = builds(bar, populate_full_signature=True, zen_exclude=[-1])
>>> pyaml(Conf2)
_target_: __main__.bar
x: ???

or to specify a pattern - via a function - for excluding parameters:

>>> Conf3 = builds(bar, populate_full_signature=True,
...                zen_exclude=lambda name: name.startswith("x"))
>>> pyaml(Conf3)
_target_: __main__.bar
'y': foo

Annotations will be used by Hydra to provide limited runtime type-checking during instantiation. Here, we’ll pass a float for x, which expects a boolean value.

>>> instantiate(Conf(x=10.0))  # type: ignore
ValidationError: Value '10.0' is not a valid bool (type float)
    full_key: x
    object_type=Builds_func

Composing configs via inheritance

Because a config produced via builds is simply a class-object, we can compose configs via class inheritance.

>>> ParentConf = builds(dict, a=1, b=2)
>>> ChildConf = builds(dict, b=-2, c=-3, builds_bases=(ParentConf,))
>>> instantiate(ChildConf)
{'a': 1, 'b': -2, 'c': -3}
>>> issubclass(ChildConf, ParentConf)
True

Runtime validation performed by builds

Misspelled parameter names and other invalid configurations for the target’s signature will be caught by builds so that such errors are caught prior to instantiation.

>>> def func(a_number: int): pass
>>> builds(func, a_nmbr=2)  # misspelled parameter name
TypeError: Building: func ..
>>> builds(func, 1, 2)  # too many arguments
TypeError: Building: func ..
>>> BaseConf = builds(func, a_number=2)
>>> builds(func, 1, builds_bases=(BaseConf,))  # too many args (via inheritance)
TypeError: Building: func ..
>>> # value type not supported by Hydra
>>> builds(int, (i for i in range(10)))  # type: ignore
hydra_zen.errors.HydraZenUnsupportedPrimitiveError: Building: int ..

Using meta-fields

Meta-fields are fields that are included in a config but are excluded by the instantiation process. Thus arbitrary metadata can be attached to a config.

Let’s create a config whose fields reference a meta-field via relative-interpolation [2].

>>> Conf = builds(dict, a="${.s}", b="${.s}", zen_meta=dict(s=-10))
>>> instantiate(Conf)
{'a': -10, 'b': -10}
>>> instantiate(Conf, s=2)
{'a': 2, 'b': 2}

Using zen-wrappers

Zen-wrappers enables us to make arbitrary changes to <hydra_target>, its inputs, and/or its outputs during the instantiation process.

Let’s use a wrapper to add a unit-conversion step to a config. We’ll modify a config that builds a function, which converts a temperature in Fahrenheit to Celsius, and add a wrapper to it so that it will convert from Fahrenheit to Kelvin instead.

>>> def faren_to_celsius(temp_f):  # our target
...     return ((temp_f - 32) * 5) / 9
>>> def change_celcius_to_kelvin(celc_func):  # our wrapper
...     def wraps(*args, **kwargs):
...         return 273.15 + celc_func(*args, **kwargs)
...     return wraps
>>> AsCelcius = builds(faren_to_celsius)
>>> AsKelvin = builds(faren_to_celsius, zen_wrappers=change_celcius_to_kelvin)
>>> instantiate(AsCelcius, temp_f=32)
0.0
>>> instantiate(AsKelvin, temp_f=32)
273.15

Creating a pickle-compatible config

The dynamically-generated classes created by builds can be made pickle-compatible by specifying the name of the symbol that it is assigned to and the module in which it was defined.

# contents of mylib/foo.py
from pickle import dumps, loads

DictConf = builds(dict,
                    zen_dataclass={'module': 'mylib.foo',
                                    'cls_name': 'DictConf'})

assert DictConf is loads(dumps(DictConf))

Creating a frozen config

Let’s create a config object whose instances will by “frozen” (i.e., immutable).

>>> RouterConfig = builds(dict, ip_address=None, zen_dataclass={'frozen': True})
>>> my_router = RouterConfig(ip_address="192.168.56.1")  # an immutable instance

Attempting to overwrite the attributes of my_router will raise.

>>> my_router.ip_address = "148.109.37.2"
FrozenInstanceError: cannot assign to field 'ip_address'

Support for partial’d targets

Specifying builds(functools.partial(<target>, ...), ...) is supported; builds will automatically “unpack” a partial’d object that is passed as its target.

>>> import functools
>>> partiald_dict = functools.partial(dict, a=1, b=2)
>>> Conf = builds(partiald_dict)  # signature: (a = 1, b = 2)
>>> instantiate(Conf)  # equivalent to calling: `partiald_dict()`
{'a': 1, 'b': 2}
>>> instantiate(Conf(a=-4))  # equivalent to calling: `partiald_dict(a=-4)`
{'a': -4, 'b': 2}