Source code for hydra_zen.structured_configs._make_config

# Copyright (c) 2024 Massachusetts Institute of Technology
# SPDX-License-Identifier: MIT
from typing import Optional, Tuple, Type, Union

from typing_extensions import Literal

from hydra_zen.typing import DataclassOptions, SupportedPrimitive
from hydra_zen.typing._implementations import (
    AllConvert,
    DataClass,
    DataClass_,
    DefaultsList,
    ZenConvert,
)

from ._implementations import DefaultBuilds, ZenField

__all__ = ["ZenField", "make_config"]


class NOTHING:
    def __init__(self) -> None:
        raise TypeError("`NOTHING` cannot be instantiated")


_MAKE_CONFIG_SETTINGS = AllConvert(dataclass=False, flat_target=False)


[docs] def make_config( *fields_as_args: Union[str, ZenField], hydra_recursive: Optional[bool] = None, hydra_convert: Optional[Literal["none", "partial", "all", "object"]] = None, hydra_defaults: Optional[DefaultsList] = None, zen_dataclass: Optional[DataclassOptions] = None, bases: Tuple[Type[DataClass_], ...] = (), zen_convert: Optional[ZenConvert] = None, **fields_as_kwargs: Union[SupportedPrimitive, ZenField], ) -> Type[DataClass]: """ Returns a config with user-defined field names and, optionally, associated default values and/or type annotations. Unlike `builds`, `make_config` is not used to configure a particular target object; rather, it can be used to create more general configs [1]_. Parameters ---------- *fields_as_args : str | ZenField The names of the fields to be be included in the config. Or, `ZenField` instances, each of which details the name and their default value and/or the type annotation of a given field. **fields_as_kwargs : SupportedPrimitive | ZenField Like ``fields_as_args``, but field-name/default-value pairs are specified as keyword arguments. `ZenField` can also be used here to express a field's type-annotation and/or its default value. Named parameters of the forms ``hydra_xx``, ``zen_xx``, and ``_zen_xx`` are reserved to ensure future-compatibility, and cannot be specified by the user. zen_convert : Optional[ZenConvert] A dictionary that modifies hydra-zen's value and type conversion behavior. Consists of the following optional key-value pairs (:ref:`zen-convert`): - `dataclass` : `bool` (default=False): 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. bases : Tuple[Type[DataClass], ...], optional (default=()) Base classes that the resulting config class will inherit from. hydra_recursive : Optional[bool], optional (default=True) If ``True``, then Hydra will recursively instantiate all other hydra-config objects nested within this dataclass [2]_. If ``None``, the ``_recursive_`` attribute is not set on the resulting config. hydra_convert : Optional[Literal["none", "partial", "all", "object"]], optional (default="none") Determines how Hydra handles the non-primitive objects passed to configuration [3]_. - ``"none"``: Passed objects are DictConfig and ListConfig, default - ``"partial"``: Passed objects are converted to dict and list, with the exception of Structured Configs (and their fields). - ``"all"``: Passed objects are 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_defaults : None | 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 [7]_ [8]_. 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_dataclass : Optional[DataclassOptions] A dictionary can specify any option that is supported by :py:func:`dataclasses.make_dataclass` other than `fields`. Additionally, a ``'module': <str>`` entry can be specified to enable pickle compatibility. See `hydra_zen.typing.DataclassOptions` for details. frozen : bool, optional (default=False) .. deprecated:: 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 class will produce 'frozen' (i.e. immutable) instances. I.e. setting/deleting an attribute of an instance of the config will raise :py:class:`dataclasses.FrozenInstanceError` at runtime. config_name : str, optional (default="Config") .. deprecated:: 0.9.0 `config_name` will be removed in hydra-zen 0.10.0. It is replaced by ``zen_dataclass={'cls_name': <str>}``. The class name of the resulting config class. Returns ------- Config : Type[DataClass] The resulting config class; a dataclass that possess the user-specified fields. Raises ------ hydra_zen.errors.HydraZenUnsupportedPrimitiveError The provided configured value cannot be serialized by Hydra, nor does hydra-zen provide specialized support for it. See :ref:`valid-types` for more details. Notes ----- The resulting "config" is a dataclass-object [4]_ with Hydra-specific attributes attached to it, along with the attributes specified via ``fields_as_args`` and ``fields_as_kwargs``. **Unlike std-lib dataclasses, the default value for unsafe_hash is True.** Any field specified without a type-annotation is automatically annotated with :py:class:`typing.Any`. Hydra only supports a narrow subset of types [5]_; `make_config` will automatically 'broaden' any user-specified annotations so that they are compatible with Hydra. `make_config` will automatically manipulate certain types of default values to ensure that they can be utilized in the resulting config and by Hydra: - Mutable default values will automatically be packaged in a default factory function [6]_ - A default value that is a class-object or function-object will automatically be wrapped by `just`, to ensure that the resulting config is serializable by Hydra. For finer-grain control over how type annotations and default values are managed, consider using :func:`dataclasses.make_dataclass`. For details of the annotation `SupportedPrimitive`, see :ref:`valid-types`. See Also -------- builds : Create a targeted structured config designed to "build" a particular object. just : Create a config that "just" returns a class-object or function, without instantiating/calling it. References ---------- .. [1] https://hydra.cc/docs/tutorials/structured_config/intro/ .. [2] https://hydra.cc/docs/advanced/instantiate_objects/overview/#recursive-instantiation .. [3] https://hydra.cc/docs/advanced/instantiate_objects/overview/#parameter-conversion-strategies .. [4] https://docs.python.org/3/library/dataclasses.html .. [5] https://hydra.cc/docs/tutorials/structured_config/intro/#structured-configs-supports .. [6] https://docs.python.org/3/library/dataclasses.html#default-factory-functions .. [7] https://hydra.cc/docs/tutorials/structured_config/defaults/ .. [8] https://hydra.cc/docs/advanced/defaults_list/ Examples -------- >>> from hydra_zen import make_config, to_yaml >>> def pp(x): ... return print(to_yaml(x)) # pretty-print config as yaml **Basic Usage** Let's create a bare-bones config with two fields, named 'a' and 'b'. >>> Conf1 = make_config("a", "b") # sig: `Conf(a: Any, b: Any)` >>> pp(Conf1) a: ??? b: ??? Now we'll configure these fields with particular values: >>> conf1 = Conf1(1, "hi") >>> pp(conf1) a: 1 b: hi >>> conf1.a 1 >>> conf1.b 'hi' We can also specify fields via keyword args; this is especially convenient for providing associated default values. >>> Conf2 = make_config("unit", data=[-10, -20]) >>> pp(Conf2) unit: ??? data: - -10 - -20 Configurations can be nested >>> Conf3 = make_config(c1=Conf1(a=1, b=2), c2=Conf2) >>> pp(Conf3) c1: a: 1 b: 2 c2: unit: ??? data: - -10 - -20 >>> Conf3().c1.a 1 Configurations can be composed via inheritance >>> ConfInherit = make_config(c=2, bases=(Conf2, Conf1)) >>> pp(ConfInherit) a: ??? b: ??? unit: ??? data: - -10 - -20 c: 2 >>> issubclass(ConfInherit, Conf1) and issubclass(ConfInherit, Conf2) # type: ignore True **Support for Additional Types** Types like :py:class:`complex` and :py:class:`pathlib.Path` are automatically supported by hydra-zen. >>> ConfWithComplex = make_config(a=1+2j) >>> pp(ConfWithComplex) a: real: 1.0 imag: 2.0 _target_: builtins.complex See :ref:`additional-types` for a complete list of supported types. **Using ZenField to Provide Type Information** The `ZenField` class can be used to include a type-annotation in association with a field. >>> from hydra_zen import ZenField as zf >>> ProfileConf = make_config(username=zf(str), age=zf(int)) >>> # signature: ProfileConf(username: str, age: int) Providing type annotations is optional, but doing so enables Hydra to perform checks at runtime to ensure that a configured value matches its associated type [4]_. >>> pp(ProfileConf(username="piro", age=False)) # age should be an integer <ValidationError: Value 'False' could not be converted to Integer> These default values can be provided alongside type annotations >>> C = make_config(age=zf(int, 0)) # signature: C(age: int = 0) `ZenField` can also be used to specify ``fields_as_args``; here, field names must be specified as well. >>> C2 = make_config(zf(name="username", hint=str), age=zf(int, 0)) >>> # signature: C2(username: str, age: int = 0) See :ref:`data-val` for more general data validation capabilities via hydra-zen. """ _locals = locals().copy() fields_as_args = _locals.pop("fields_as_args") fields_as_kwargs = _locals.pop("fields_as_kwargs") return DefaultBuilds.make_config(*fields_as_args, **_locals, **fields_as_kwargs) # type: ignore