Repository: mit-ll-responsible-ai/hydra-zen

Issue:
Nested Partial Builds not Converted when Using Stores and Hydrated_dataclass with Zen_partial
When using stores and hydrated_dataclass together with zen_partial, it seems that the _target_ field gets assigned the Config instead of the actual target. This results in functools.partial[<class '__main__.NestedModelConf'>] instead of functools.partial[<class '__main__.NestedModel'>]. However, NestedModel is also a partial, which results in a double partial, and the need for manually initializing the config in the outer model.

```python
from dataclasses import dataclass, field
from hydra_zen import (
    MISSING,
    hydrated_dataclass,
    instantiate,
    zen,
    store,
)
from hydra_zen.typing import Partial
from omegaconf import DictConfig, OmegaConf
from hydra.core.config_store import ConfigStore
from torch import nn


class NestedModel(nn.Module):
    def __init__(self, **kwargs):
        super().__init__()
        params = NestedModelConf(**kwargs)
        self.linear = nn.Linear(params.in_features, 10)

    def forward(self, x):
        return self.linear(x)


class Model(nn.Module):
    def __init__(self, **kwargs):
        super().__init__()
        params = ModelConf(**kwargs)
        self.linear = nn.Linear(params.in_features, params.out_features)
        self.nested = params.nested(params.out_features)

    def forward(self, x):
        return self.linear(x)


@store(name="x", group="model/layer")
@hydrated_dataclass(NestedModel, zen_partial=True)
class NestedModelConf:
    in_features: int = MISSING


@hydrated_dataclass(nn.Module)
class BaseModelConf:
    pass


@store(name="mod", group="model", in_features=10, out_features=20)
@hydrated_dataclass(Model)
class ModelConf(BaseModelConf):
    in_features: int = 1
    out_features: int = 1
    nested: Partial[NestedModel] = MISSING


def task(zen_cfg: DictConfig):
    cfg: Exp = instantiate(zen_cfg)
    print(cfg.x + cfg.y)
    model = instantiate(cfg.model)


@dataclass
class Exp:
    x: int
    y: int
    model: BaseModelConf


cs = ConfigStore.instance()
cs.store(name="my_app", node=Exp)

@store(name=lambda cfg: cfg.__name__, group="exp", package="_global_")
@dataclass
class OverrideExp(Exp):
    x: int = 52
    y: int = 30
    model: BaseModelConf = MISSING
    defaults: list = field(
        default_factory=lambda: [
            "_self_",
            {
                "/model": "mod",
            },
            {
                "/model/layer@model.nested": "x",
            },
        ]
    )


if __name__ == "__main__":
    store.add_to_hydra_store()
    zen(task).hydra_main(config_name="my_app", config_path=None, version_base="1.3")
```

The line `self.nested = params.nested(params.out_features)` is not valid, as it simply returns NestedModelConf(in_features=20, _target_='__main__.NestedModel', _partial_=True). In order to get the actual target, I have to execute instantiate(self.nested). This will give me functools.partial(<class '__main__.NestedModel'>, in_features=20), which again I have to call.

I hope this makes sense and that I used the library correctly.

To run this code, I executed: python main.py +exp=OverrideExp

For completion's sake, this is the produced job YAML:

```yaml
x: 52
'y': 30
model:
  _target_: __main__.ModelConf
  in_features: 10
  out_features: 20
  nested:
    in_features: ???
    _target_: __main__.NestedModelConf
    _partial_: true
_target_: __main__.OverrideExp
```
For now I resolved the problem by applying this function to the task config: 

```python
def fix_partials(cfg: DictConfig):
    for k, v in cfg.items():
        if isinstance(v, DictConfig):
            if "_partial_" in v:
                v._target_ = v._target_.replace("Conf", "")
                return cfg
            cfg[k] = fix_partials(v)

    return cfg 
```

However, this is a rather hacky solution and requires exact naming.

A few additional notes:
```python
@dataclass
class Exp:
    x: int
    y: int
    model: BaseModelConf


cs = ConfigStore.instance()
cs.store(name="my_app", node=Exp)
```
is required, because If I use the builtin hydra_zen store on either the task or this base experiment, OmegaConf, cannot merge any subclass Experiment with this one. Apparently `Builds_Exp` and `Builds_OverrideExp` are not of the same base type.


Thank you for your help.


Discussion:

Patch:
diff --git a/docs/source/changes.rst b/docs/source/changes.rst
index bc98b949..56ef9293 100644
--- a/docs/source/changes.rst
+++ b/docs/source/changes.rst
@@ -20,15 +20,57 @@ This release drops support for hydra-core 1.1 and for omegaconf 2.1; this enable
 removal of a lot of complex compatibility logic from hydra-zen's source code, and to 
 improve the behavior of :func:`~hydra_zen.zen`.
 
+
+---------------------
+0.11.0rc - 2023-04-09
+---------------------
+This release drops support for hydra-core 1.1 and for omegaconf 2.1; this enables hydra-zen to remove a lot of complex compatibility logic and to improve the behavior
+of :func:`~hydra_zen.zen`.
+
+Release Highlights
+------------------
+:func:`~hydra_zen.builds` now has special behavior when it is passed a dataclass type that already possesses a `_target_` field: `_target_` is treated in a transitive way. E.g. `build(builds(int))` is equivalent to `builds(int)`. This enables a powerful "builder" pattern where configs can be populated in iterative, branching ways (with full type-checking support 😎). 
+
+.. code-block:: python
+   :caption: Basic 'builder' pattern
+
+   from hydra_zen import make_custom_builds_fn, instantiate
+
+   fbuilds = make_custom_builds_fn(populate_full_signature=True)
+
+   def foo(x, y, z): return x, y, z
+
+   base_cfg = fbuilds(foo, x=0, y=0, z=0)
+
+   c1 = fbuilds(base_cfg, x=1)
+   c2 = fbuilds(c1, y=2)
+   c3 = fbuilds(c2, z=3)
+
+.. code-block:: pycon
+
+   >>> [instantiate(c) for c in [c1, c2, c3]]
+   [(1, 0, 0), (1, 2, 0), (1, 2, 3)]
+
+
+Improvements
+------------
+- :func:`~hydra_zen.builds` now has a transitive property that enables iterative build patterns. See :pull:`455`
+- :func:`~hydra_zen.zen`'s instantiation phase has been improved so that dataclass objects and stdlib containers are returned instead of omegaconf objects. See :pull:`#448`. 
+
 Bug Fixes
 ---------
 - Configs produced by `~hydra_zen.just` will no longer cause a `ReadonlyConfigError` during Hydra's config-composition process. See :pull:`459`
+- :class:`~hydra_zen.ZenStore` now works with :func:`~hydra_zen.hydrated_dataclass`. See :issue:`453` and :pull:`455`.
 
 Compatibility-Breaking Changes
 ------------------------------
+Most of these changes will not have any impact on users, based on download statistics and the particular code patterns impacted by the following changes.
+
+- hydra-core 1.2.0 and omegaconf 2.2.1 are now the minimum supported versions of hydra-zen's dependencies.
 - The auto-instantiation behavior of :class:`~hydra_zen.wrapper.Zen` and :func:`~hydra_zen.zen` have been updated so that nested dataclasses (nested within lists, dicts, and other dataclasses) will no longer be returned as omegaconf configs (see :pull:`448`).
-- hydra-core 1.2.0 and omegaconf 2.2.1 are now the minimum supported versions.
 - :func:`~hydra_zen.just` not longer returns a frozen dataclass (see :pull:`459`).
+- Users that relied on patterns like `builds(builds(...))` will find that :pull:`455` has changed their behaviors. This new behavior can be disabled via `builds(..., zen_convert={'flat_target': False})`
+- :func:`~hydra_zen.zen`'s instantiation behavior was changed by :pull:`448`. See that PR for instructions on restoring the old behavior.
 
 --------------------------
 Documentation - 2023-03-11
diff --git a/pyproject.toml b/pyproject.toml
index 931de448..a1f1a81f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -112,6 +112,9 @@ exclude = [
        "**/__pycache__",
        "src/hydra_zen/_version.py",
        "**/third_party",
+       "**/.hypothesis",
+       "**/docs",
+       "**/.tox",
 ]
 reportUnnecessaryTypeIgnoreComment = true
 reportUnnecessaryIsInstance = false
diff --git a/src/hydra_zen/structured_configs/_implementations.py b/src/hydra_zen/structured_configs/_implementations.py
index 01c86425..b189c9ae 100644
--- a/src/hydra_zen/structured_configs/_implementations.py
+++ b/src/hydra_zen/structured_configs/_implementations.py
@@ -57,6 +57,7 @@ from hydra_zen.typing import (
 )
 from hydra_zen.typing._implementations import (
     AllConvert,
+    AnyBuilds,
     BuildsWithSig,
     DataClass_,
     DefaultsList,
@@ -96,7 +97,7 @@ R = TypeVar("R")
 Field_Entry: TypeAlias = Tuple[str, type, Field[Any]]
 
 # default zen_convert settings for `builds` and `hydrated_dataclass`
-_BUILDS_CONVERT_SETTINGS = AllConvert(dataclass=True)
+_BUILDS_CONVERT_SETTINGS = AllConvert(dataclass=True, flat_target=True)
 
 # stores type -> value-conversion-fn
 # for types with specialized support from hydra-zen
@@ -804,6 +805,26 @@ def sanitized_field(
 
 
 # partial=False, pop-sig=True; no *args, **kwargs, nor builds_bases
+@overload
+def builds(
+    __hydra_target: Type[BuildsWithSig[Type[R], P]],
+    *,
+    zen_partial: Literal[False, None] = ...,
+    populate_full_signature: Literal[True],
+    zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all", "object"]] = ...,
+    hydra_defaults: Optional[DefaultsList] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[()] = ...,
+    zen_dataclass: Optional[DataclassOptions] = None,
+    frozen: bool = ...,
+    zen_convert: Optional[ZenConvert] = ...,
+) -> Type[BuildsWithSig[Type[R], P]]:
+    ...
+
+
 @overload
 def builds(
     __hydra_target: Callable[P, R],
@@ -824,6 +845,28 @@ def builds(
     ...
 
 
+# partial=False, pop-sig=bool
+@overload
+def builds(
+    __hydra_target: Type[AnyBuilds[Importable]],
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[False, None] = ...,
+    populate_full_signature: bool = ...,
+    zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all", "object"]] = ...,
+    hydra_defaults: Optional[DefaultsList] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[DataClass_], ...] = ...,
+    zen_dataclass: Optional[DataclassOptions] = None,
+    frozen: bool = ...,
+    zen_convert: Optional[ZenConvert] = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[Builds[Importable]]:
+    ...
+
+
 # partial=False, pop-sig=bool
 @overload
 def builds(
@@ -846,6 +889,28 @@ def builds(
     ...
 
 
+# partial=True, pop-sig=bool
+@overload
+def builds(
+    __hydra_target: Type[AnyBuilds[Importable]],
+    *pos_args: SupportedPrimitive,
+    zen_partial: Literal[True] = ...,
+    populate_full_signature: bool = ...,
+    zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all", "object"]] = ...,
+    hydra_defaults: Optional[DefaultsList] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[DataClass_], ...] = ...,
+    zen_dataclass: Optional[DataclassOptions] = None,
+    frozen: bool = ...,
+    zen_convert: Optional[ZenConvert] = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Type[PartialBuilds[Importable]]:
+    ...
+
+
 # partial=True, pop-sig=bool
 @overload
 def builds(
@@ -868,6 +933,28 @@ def builds(
     ...
 
 
+# partial=bool, pop-sig=False
+@overload
+def builds(
+    __hydra_target: Type[AnyBuilds[Importable]],
+    *pos_args: SupportedPrimitive,
+    zen_partial: Optional[bool] = ...,
+    populate_full_signature: Literal[False] = ...,
+    zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+    zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+    hydra_recursive: Optional[bool] = ...,
+    hydra_convert: Optional[Literal["none", "partial", "all", "object"]] = ...,
+    hydra_defaults: Optional[DefaultsList] = ...,
+    dataclass_name: Optional[str] = ...,
+    builds_bases: Tuple[Type[DataClass_], ...] = ...,
+    zen_dataclass: Optional[DataclassOptions] = None,
+    frozen: bool = ...,
+    zen_convert: Optional[ZenConvert] = ...,
+    **kwargs_for_target: SupportedPrimitive,
+) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]]]:
+    ...
+
+
 # partial=bool, pop-sig=False
 @overload
 def builds(
@@ -886,14 +973,14 @@ def builds(
     frozen: bool = ...,
     zen_convert: Optional[ZenConvert] = ...,
     **kwargs_for_target: SupportedPrimitive,
-) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]],]:
+) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]]]:
     ...
 
 
 # partial=bool, pop-sig=bool
 @overload
 def builds(
-    __hydra_target: Union[Callable[P, R], Importable],
+    __hydra_target: Union[Callable[P, R], Type[Builds[Importable]], Importable],
     *pos_args: SupportedPrimitive,
     zen_partial: Optional[bool],
     populate_full_signature: bool = ...,
@@ -917,7 +1004,9 @@ def builds(
 
 
 def builds(
-    *pos_args: Union[Importable, Callable[P, R], SupportedPrimitive],
+    *pos_args: Union[
+        Importable, Callable[P, R], Type[AnyBuilds[Importable]], SupportedPrimitive
+    ],
     zen_partial: Optional[bool] = None,
     zen_wrappers: ZenWrappers[Callable[..., Any]] = tuple(),
     zen_meta: Optional[Mapping[str, SupportedPrimitive]] = None,
@@ -1011,6 +1100,9 @@ def builds(
             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_bases : Tuple[Type[DataClass], ...]
         Specifies a tuple of parent classes that the resulting config inherits from.
 
@@ -1478,6 +1570,18 @@ def builds(
             f" {','.join(str(_key) for _key in zen_meta if not isinstance(_key, str))}"
         )
 
+    target_path: str
+    if (
+        zen_convert_settings["flat_target"]
+        and isinstance(target, type)
+        and is_dataclass(target)
+        and hasattr(target, TARGET_FIELD_NAME)
+    ):
+        # pass through _target_ field
+        target_path = safe_getattr(target, TARGET_FIELD_NAME)
+    else:
+        target_path = _utils.get_obj_path(target)
+
     if zen_wrappers is not None:
         if not isinstance(zen_wrappers, Sequence) or isinstance(zen_wrappers, str):
             zen_wrappers = (zen_wrappers,)
@@ -1547,8 +1651,6 @@ def builds(
 
     target_field: List[Union[Tuple[str, Type[Any]], Tuple[str, Type[Any], Any]]]
 
-    target_path: Final[str] = _utils.get_obj_path(target)
-
     # zen_partial behavior:
     #
     # If zen_partial is not None: zen_partial dictates if output is PartialBuilds
@@ -1827,6 +1929,11 @@ def builds(
                     default=f.default_factory(),
                 )
         signature_params.update(_update)
+        if (
+            zen_convert_settings["flat_target"]
+            and TARGET_FIELD_NAME in signature_params
+        ):
+            signature_params.pop(TARGET_FIELD_NAME)
         del _update
         del _fields
 
diff --git a/src/hydra_zen/structured_configs/_just.py b/src/hydra_zen/structured_configs/_just.py
index 418a5a71..ab89c3ef 100644
--- a/src/hydra_zen/structured_configs/_just.py
+++ b/src/hydra_zen/structured_configs/_just.py
@@ -24,7 +24,7 @@ TB = TypeVar("TB", bound=Union[_SupportedViaBuilds, FrozenSet[Any]])
 __all__ = ["just"]
 
 
-_JUST_CONVERT_SETTINGS = AllConvert(dataclass=True)
+_JUST_CONVERT_SETTINGS = AllConvert(dataclass=True, flat_target=False)
 
 
 @overload
diff --git a/src/hydra_zen/structured_configs/_make_config.py b/src/hydra_zen/structured_configs/_make_config.py
index c0f3e4e0..eee1b1b2 100644
--- a/src/hydra_zen/structured_configs/_make_config.py
+++ b/src/hydra_zen/structured_configs/_make_config.py
@@ -103,7 +103,7 @@ class ZenField:
             )
 
 
-_MAKE_CONFIG_SETTINGS = AllConvert(dataclass=False)
+_MAKE_CONFIG_SETTINGS = AllConvert(dataclass=False, flat_target=False)
 
 
 def make_config(
diff --git a/src/hydra_zen/typing/_builds_overloads.py b/src/hydra_zen/typing/_builds_overloads.py
index cf87c0e9..801fd262 100644
--- a/src/hydra_zen/typing/_builds_overloads.py
+++ b/src/hydra_zen/typing/_builds_overloads.py
@@ -19,6 +19,7 @@ from typing import (
 from typing_extensions import Literal, ParamSpec, Protocol
 
 from ._implementations import (
+    AnyBuilds,
     Builds,
     BuildsWithSig,
     DataClass_,
@@ -38,6 +39,27 @@ __all__ = ["FullBuilds", "PBuilds", "StdBuilds"]
 
 
 class StdBuilds(Protocol):
+    # partial=False, pop-sig=True; no *args, **kwargs, nor builds_bases
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[BuildsWithSig[Type[R], P]],
+        *,
+        zen_partial: Literal[False, None] = ...,
+        populate_full_signature: Literal[True],
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[()] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+    ) -> Type[BuildsWithSig[Type[R], P]]:
+        ...
+
     # partial=False, pop-sig=True; no *args, **kwargs, nor builds_bases
     @overload
     def __call__(
@@ -60,6 +82,27 @@ class StdBuilds(Protocol):
         ...
 
     # partial=False, pop-sig=bool
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Literal[False, None] = ...,
+        populate_full_signature: bool = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Type[Builds[Importable]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -82,6 +125,27 @@ class StdBuilds(Protocol):
         ...
 
     # partial=True, pop-sig=bool
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Literal[True] = ...,
+        populate_full_signature: bool = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Type[PartialBuilds[Importable]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -104,6 +168,27 @@ class StdBuilds(Protocol):
         ...
 
     # partial=bool, pop-sig=False
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Optional[bool] = ...,
+        populate_full_signature: Literal[False] = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]],]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -129,7 +214,7 @@ class StdBuilds(Protocol):
     @overload
     def __call__(
         self,
-        __hydra_target: Union[Callable[P, R], Importable],
+        __hydra_target: Union[Callable[P, R], Type[AnyBuilds[Importable]], Importable],
         *pos_args: SupportedPrimitive,
         zen_partial: Optional[bool],
         populate_full_signature: bool = ...,
@@ -153,7 +238,7 @@ class StdBuilds(Protocol):
 
     def __call__(
         self,
-        __hydra_target: Union[Callable[P, R], Importable],
+        __hydra_target: Union[Callable[P, R], Type[Builds[Importable]], Importable],
         *pos_args: SupportedPrimitive,
         zen_partial: Optional[bool] = None,
         populate_full_signature: bool = False,
@@ -180,6 +265,47 @@ class FullBuilds(Protocol):
     # full_builds -> populate_full_signature=True  (new default value)
 
     # partial=False, pop_sig=True; no *args, **kwargs, nor builds_bases`
+
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[BuildsWithSig[Type[R], P]],
+        *,
+        zen_partial: Literal[False, None] = ...,
+        populate_full_signature: Literal[True] = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[()] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+    ) -> Type[BuildsWithSig[Type[R], P]]:
+        ...
+
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *,
+        zen_partial: Literal[False, None] = ...,
+        populate_full_signature: Literal[True] = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[()] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+    ) -> Type[Builds[Importable]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -200,7 +326,27 @@ class FullBuilds(Protocol):
     ) -> Type[BuildsWithSig[Type[R], P]]:
         ...
 
-    # partial=False, pop_sig=bool; has *args, **kwargs, and/or `builds_bases`
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[Union[AnyBuilds[Importable], PartialBuilds[Importable]]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Literal[False, None] = ...,
+        populate_full_signature: bool = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Type[Builds[Importable]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -223,6 +369,27 @@ class FullBuilds(Protocol):
         ...
 
     # partial=True, pop-sig=bool
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Literal[True] = ...,
+        populate_full_signature: bool = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Type[PartialBuilds[Importable]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -245,6 +412,27 @@ class FullBuilds(Protocol):
         ...
 
     # partial=bool, pop-sig=False
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Optional[bool] = ...,
+        populate_full_signature: Literal[False],
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        frozen: bool = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        dataclass_name: Optional[str] = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -270,7 +458,7 @@ class FullBuilds(Protocol):
     @overload
     def __call__(
         self,
-        __hydra_target: Union[Callable[P, R], Importable],
+        __hydra_target: Union[Callable[P, R], Type[AnyBuilds[Importable]], Importable],
         *pos_args: SupportedPrimitive,
         zen_partial: Optional[bool],
         populate_full_signature: bool = ...,
@@ -294,7 +482,12 @@ class FullBuilds(Protocol):
 
     def __call__(
         self,
-        __hydra_target: Union[Callable[P, R], Importable],
+        __hydra_target: Union[
+            Callable[P, R],
+            Importable,
+            Type[Builds[Importable]],
+            Type[PartialBuilds[Importable]],
+        ],
         *pos_args: SupportedPrimitive,
         zen_partial: Optional[bool] = None,
         populate_full_signature: bool = True,
@@ -321,6 +514,27 @@ class PBuilds(Protocol):
     # partial_builds -> zen_partial=True (new default value)
 
     # partial=True, pop-sig=bool
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[AnyBuilds[Importable]],
+        *pos_args: SupportedPrimitive,
+        zen_partial: Literal[True] = ...,
+        populate_full_signature: bool = ...,
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[Type[DataClass_], ...] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+        **kwargs_for_target: SupportedPrimitive,
+    ) -> Type[PartialBuilds[Importable]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -343,6 +557,26 @@ class PBuilds(Protocol):
         ...
 
     # partial=False, pop-sig=True; no *args, **kwargs, nor builds_bases
+    @overload
+    def __call__(
+        self,
+        __hydra_target: Type[BuildsWithSig[Type[R], P]],
+        *,
+        zen_partial: Literal[False, None],
+        populate_full_signature: Literal[True],
+        zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
+        zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
+        hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
+        hydra_defaults: Optional[DefaultsList] = ...,
+        dataclass_name: Optional[str] = ...,
+        builds_bases: Tuple[()] = ...,
+        frozen: bool = ...,
+        zen_dataclass: Optional[DataclassOptions] = ...,
+        zen_convert: Optional[ZenConvert] = ...,
+    ) -> Type[BuildsWithSig[Type[R], P]]:
+        ...
+
     @overload
     def __call__(
         self,
@@ -367,7 +601,7 @@ class PBuilds(Protocol):
     @overload
     def __call__(
         self,
-        __hydra_target: Importable,
+        __hydra_target: Type[AnyBuilds[Importable]],
         *pos_args: SupportedPrimitive,
         zen_partial: Optional[bool] = ...,
         populate_full_signature: Literal[False] = ...,
@@ -385,17 +619,16 @@ class PBuilds(Protocol):
     ) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]],]:
         ...
 
-    # partial=bool, pop-sig=bool
     @overload
     def __call__(
         self,
-        __hydra_target: Union[Callable[P, R], Importable],
+        __hydra_target: Importable,
         *pos_args: SupportedPrimitive,
-        zen_partial: Optional[bool],
-        populate_full_signature: bool = ...,
-        hydra_recursive: Optional[bool] = ...,
+        zen_partial: Optional[bool] = ...,
+        populate_full_signature: Literal[False] = ...,
         zen_wrappers: ZenWrappers[Callable[..., Any]] = ...,
         zen_meta: Optional[Mapping[str, SupportedPrimitive]] = ...,
+        hydra_recursive: Optional[bool] = ...,
         hydra_convert: Optional[Literal["none", "partial", "all"]] = ...,
         hydra_defaults: Optional[DefaultsList] = ...,
         dataclass_name: Optional[str] = ...,
@@ -404,11 +637,7 @@ class PBuilds(Protocol):
         zen_dataclass: Optional[DataclassOptions] = ...,
         zen_convert: Optional[ZenConvert] = ...,
         **kwargs_for_target: SupportedPrimitive,
-    ) -> Union[
-        Type[Builds[Importable]],
-        Type[PartialBuilds[Importable]],
-        Type[BuildsWithSig[Type[R], P]],
-    ]:
+    ) -> Union[Type[Builds[Importable]], Type[PartialBuilds[Importable]],]:
         ...
 
     # partial=bool, pop-sig=bool
@@ -439,7 +668,7 @@ class PBuilds(Protocol):
 
     def __call__(
         self,
-        __hydra_target: Union[Callable[P, R], Importable],
+        __hydra_target: Union[Callable[P, R], Type[AnyBuilds[Importable]], Importable],
         *pos_args: SupportedPrimitive,
         zen_partial: Optional[bool] = True,
         populate_full_signature: bool = False,
diff --git a/src/hydra_zen/typing/_implementations.py b/src/hydra_zen/typing/_implementations.py
index 1a489f2c..44871f05 100644
--- a/src/hydra_zen/typing/_implementations.py
+++ b/src/hydra_zen/typing/_implementations.py
@@ -182,6 +182,7 @@ IsPartial: TypeAlias = Union[ZenPartialMixin[T], HydraPartialMixin[T]]
 
 PartialBuilds: TypeAlias = Union[ZenPartialBuilds[T], HydraPartialBuilds[T]]
 
+AnyBuilds: TypeAlias = Union[Builds[T], BuildsWithSig[T, Any]]
 
 Importable = TypeVar("Importable", bound=Callable[..., Any])
 
@@ -254,10 +255,11 @@ DefaultsList = List[
 # Lists all zen-convert settings and their types. Not part of public API
 class AllConvert(TypedDict, total=True):
     dataclass: bool
+    flat_target: bool
 
 
 # used for runtime type-checking
-convert_types: Final = {"dataclass": bool}
+convert_types: Final = {"dataclass": bool, "flat_target": bool}
 
 GroupName: TypeAlias = Optional[str]
 NodeName: TypeAlias = str
@@ -278,12 +280,17 @@ class ZenConvert(TypedDict, total=False):
     options that configure the hydra-zen config-creation functions (e.g., `builds`,
     `just`, and `make_config`).
 
-    Note that, at runtime, `ZenConvert` is simply a dictionary with type-annotations. There is no enforced runtime validation of its keys and values.
+    Note that, at runtime, `ZenConvert` is simply a dictionary with type-annotations.
+    There is no enforced runtime validation of its keys and values.
 
     Parameters
     ----------
+    flat_target: bool
+        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.
+
     dataclass : bool
-        If `True` any dataclass type/instance without a `_target_` field is
+        If `True` (default) 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.
 
@@ -347,6 +354,7 @@ class ZenConvert(TypedDict, total=False):
     True
     """
 
+    flat_target: bool
     dataclass: bool
 
 


Test patch:
diff --git a/tests/annotations/declarations.py b/tests/annotations/declarations.py
index c1cede08..d4f51130 100644
--- a/tests/annotations/declarations.py
+++ b/tests/annotations/declarations.py
@@ -1278,3 +1278,120 @@ def test_zen_dataclass(ff: FullBuilds, ss: StdBuilds, pp: PBuilds):
 
     make_config(zen_dataclass={"eq": True})
     make_config(zen_dataclass={"blah": 2})  # type: ignore
+
+
+def builds_target_pass_through():
+    def foo(x: int) -> str:
+        ...
+
+    c1 = builds(builds(foo, x=1), x=2)
+    reveal_type(instantiate(c1), expected_text="str")
+
+    c2 = builds(builds(foo, populate_full_signature=True))
+    reveal_type(instantiate(c2), expected_text="str")
+    reveal_type(c2, expected_text="Type[Builds[Type[str]]]")
+
+    c3 = builds(builds(foo, zen_partial=True))
+    reveal_type(instantiate(c3), expected_text="str")
+
+    pc1 = builds(builds(foo, x=1), x=2, zen_partial=True)
+    reveal_type(instantiate(pc1), expected_text="Partial[str]")
+
+    pc2 = builds(builds(foo, populate_full_signature=True), zen_partial=True)
+    reveal_type(instantiate(pc2), expected_text="Partial[str]")
+
+    pc3 = builds(builds(foo, zen_partial=True), zen_partial=True)
+    reveal_type(instantiate(pc3), expected_text="Partial[str]")
+
+    fc = builds(builds(foo, populate_full_signature=True), populate_full_signature=True)
+    reveal_type(instantiate(fc), expected_text="str")
+    reveal_type(fc, expected_text="Type[BuildsWithSig[Type[str], (x: int)]]")
+
+
+def sbuilds_target_pass_through():
+    def foo(x: int) -> str:
+        ...
+
+    sbuilds = make_custom_builds_fn()
+    c1 = sbuilds(sbuilds(foo, x=1), x=2)
+    reveal_type(instantiate(c1), expected_text="str")
+
+    c2 = sbuilds(sbuilds(foo, populate_full_signature=True))
+    reveal_type(instantiate(c2), expected_text="str")
+    reveal_type(c2, expected_text="Type[Builds[Type[str]]]")
+
+    c3 = sbuilds(sbuilds(foo, zen_partial=True))
+    reveal_type(instantiate(c3), expected_text="str")
+
+    pc1 = sbuilds(sbuilds(foo, x=1), x=2, zen_partial=True)
+    reveal_type(instantiate(pc1), expected_text="Partial[str]")
+
+    pc2 = sbuilds(sbuilds(foo, populate_full_signature=True), zen_partial=True)
+    reveal_type(instantiate(pc2), expected_text="Partial[str]")
+
+    pc3 = sbuilds(sbuilds(foo, zen_partial=True), zen_partial=True)
+    reveal_type(instantiate(pc3), expected_text="Partial[str]")
+
+    fc = sbuilds(
+        sbuilds(foo, populate_full_signature=True), populate_full_signature=True
+    )
+    reveal_type(instantiate(fc), expected_text="str")
+    reveal_type(fc, expected_text="Type[BuildsWithSig[Type[str], (x: int)]]")
+
+
+def fbuilds_target_pass_through():
+    def foo(x: int) -> str:
+        ...
+
+    fbuilds = make_custom_builds_fn(populate_full_signature=True)
+    c1 = fbuilds(fbuilds(foo, x=1), x=2)
+    reveal_type(instantiate(c1), expected_text="str")
+
+    c2 = fbuilds(fbuilds(foo, populate_full_signature=True))
+    reveal_type(instantiate(c2), expected_text="str")
+    reveal_type(c2, expected_text="Type[BuildsWithSig[Type[str], (x: int)]]")
+
+    c3 = fbuilds(fbuilds(foo, zen_partial=True))
+    reveal_type(instantiate(c3), expected_text="str")
+
+    pc1 = fbuilds(fbuilds(foo, x=1), x=2, zen_partial=True)
+    reveal_type(instantiate(pc1), expected_text="Partial[str]")
+
+    pc2 = fbuilds(fbuilds(foo, populate_full_signature=True), zen_partial=True)
+    reveal_type(instantiate(pc2), expected_text="Partial[str]")
+
+    pc3 = fbuilds(fbuilds(foo, zen_partial=True), zen_partial=True)
+    reveal_type(instantiate(pc3), expected_text="Partial[str]")
+
+    fc = fbuilds(fbuilds(foo, populate_full_signature=False))
+    reveal_type(instantiate(fc), expected_text="str")
+    reveal_type(fc, expected_text="Type[Builds[(x: int) -> str]]")
+
+
+def pbuilds_target_pass_through():
+    def foo(x: int) -> str:
+        ...
+
+    pbuilds = make_custom_builds_fn(zen_partial=True)
+    pc1 = pbuilds(pbuilds(foo, x=1), x=2)
+    reveal_type(instantiate(pc1), expected_text="Partial[str]")
+
+    pc2 = pbuilds(builds(foo, populate_full_signature=True))
+    reveal_type(instantiate(pc2), expected_text="Partial[str]")
+
+    pc3 = pbuilds(pbuilds(foo, zen_partial=False))
+    reveal_type(instantiate(pc3), expected_text="Partial[str]")
+
+    c1 = pbuilds(pbuilds(foo, x=1), x=2, zen_partial=False)
+    reveal_type(instantiate(c1), expected_text="str")
+
+    fc = pbuilds(
+        builds(foo, populate_full_signature=True),
+        zen_partial=False,
+        populate_full_signature=True,
+    )
+    reveal_type(fc, expected_text="Type[BuildsWithSig[Type[str], (x: int)]]")
+    reveal_type(instantiate(fc), expected_text="str")
+
+    pc4 = pbuilds(pbuilds(foo, zen_partial=True), zen_partial=True)
+    reveal_type(instantiate(pc4), expected_text="Partial[str]")
diff --git a/tests/test_flat_target.py b/tests/test_flat_target.py
new file mode 100644
index 00000000..93c91280
--- /dev/null
+++ b/tests/test_flat_target.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2023 Massachusetts Institute of Technology
+# SPDX-License-Identifier: MIT
+
+
+from dataclasses import dataclass
+
+import pytest
+
+from hydra_zen import builds, instantiate, make_custom_builds_fn
+from hydra_zen.structured_configs._utils import get_obj_path
+from hydra_zen.typing import ZenConvert
+
+fbuilds = make_custom_builds_fn(populate_full_signature=True)
+
+
+def test_no_flat_target():
+    out = builds(builds(int), zen_convert=ZenConvert(flat_target=False))
+    assert out._target_.startswith("types.Builds")
+    with pytest.raises(Exception):
+        instantiate(out)
+
+
+@pytest.mark.parametrize("options", [ZenConvert(flat_target=True), ZenConvert()])
+def test_flat_target(options: ZenConvert):
+    out = builds(builds(int), zen_convert=options)
+    assert instantiate(out) == int()
+
+
+def foo(x: int, y: int):
+    return (x, y)
+
+
+def test_flat_target_manual_config():
+    # In A, `_target_` is an init=True field, which
+    # we will pick up in pop-sig; but _target_ is
+    @dataclass
+    class A:
+        x: int
+        y: int
+        _target_: str = get_obj_path(foo)
+
+    c = builds(A, x=3, y=4, populate_full_signature=True)
+    assert c._target_ == A._target_
+    assert instantiate(c) == (3, 4)
+
+
+def test_store_hydrated():
+    from hydra_zen import ZenStore, hydrated_dataclass, instantiate
+
+    store = ZenStore()
+
+    @store(name="a")
+    @store(name="b", y=-1)
+    @store(name="c", x=-1, y=2)
+    @hydrated_dataclass(
+        foo,
+        zen_partial=True,  # <- note
+    )
+    class A:
+        x: int
+        y: int
+
+    assert instantiate(A)(x=3, y=4) == (3, 4)
+    assert instantiate(store[None, "a"])(x=10, y=22) == (10, 22)
+    assert instantiate(store[None, "b"])(x=2) == (2, -1)
+    assert instantiate(store[None, "c"])() == (-1, 2)
diff --git a/tests/test_store.py b/tests/test_store.py
index d0b255ca..ca9d0b04 100644
--- a/tests/test_store.py
+++ b/tests/test_store.py
@@ -19,6 +19,7 @@ from omegaconf import DictConfig, ListConfig
 from hydra_zen import (
     ZenStore,
     builds,
+    hydrated_dataclass,
     instantiate,
     just,
     make_config,
@@ -790,3 +791,19 @@ def test_node_warns():
     store = ZenStore(warn_node_kwarg=True)
     with pytest.warns(UserWarning):
         store(node=builds(int))
+
+
+def foo(x: int):
+    return x
+
+
+def test_store_hydrated_dataclass():
+    # regression test for: https://github.com/mit-ll-responsible-ai/hydra-zen/issues/453
+
+    @hydrated_dataclass(foo)
+    class SomethingHydrated:
+        x: int = 1
+
+    store = ZenStore()
+    store(SomethingHydrated, name="foo", x=2)
+    assert instantiate(store[None, "foo"]) == 2
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 27b18cd2..d7c6ebe7 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -449,7 +449,7 @@ def test_merge_settings_retains_user_settings(
 @pytest.mark.parametrize("bad_settings", [1, {"not a field": True}, {"dataclass": 1.0}])
 def test_merge_settings_validation(bad_settings):
     with pytest.raises((TypeError, ValueError)):
-        merge_settings(bad_settings, {"dataclass": True})
+        merge_settings(bad_settings, {"dataclass": True, "flat_target": True})
 
 
 def test_strict_dataclass_options_reflects_current_dataclass_ver():