Repository: omni-us/jsonargparse
Pull request #634 (2024-11-27 19:51:00)

Deprecate add_dataclass_arguments and related changes
## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/main/CONTRIBUTING.rst)?
- [x] Did you update **the documentation**? (readme and public docstrings)
- [x] Did you write **unit tests** such that there is 100% coverage on related code? (required for bug fixes and new features)
- [x] Did you verify that new and existing **tests pass locally**?
- [x] Did you make sure that all changes preserve **backward compatibility**?
- [x] Did you update **the CHANGELOG**? (not for typos, docs, test updates, or minor internal changes/refactors)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/omni-us/jsonargparse/pull/634?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.93%. Comparing base [(`0ccac3a`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/0ccac3ad07e0c9d555177b67d770bc3dad70b675?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us) to head [(`51192be`)](https://app.codecov.io/gh/omni-us/jsonargparse/commit/51192be611a0d07f4516cc4804c09db0271229e5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

:white_check_mark: All tests successful. No failed tests found.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##              main     #634      +/-   ##
===========================================
- Coverage   100.00%   99.93%   -0.07%     
===========================================
  Files           22       22              
  Lines         6490     6480      -10     
===========================================
- Hits          6490     6476      -14     
- Misses           0        4       +4     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/omni-us/jsonargparse/pull/634?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=omni-us).

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [3 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634)


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=634&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=634&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=634)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index d25e457..ea3e467 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -12,9 +12,24 @@ The semantic versioning only considers the public API as described in
 paths are considered internals and can change in minor and patch releases.
 
 
-v4.34.1 (2024-12-02)
+v4.35.0 (2024-12-??)
 --------------------
 
+Changed
+^^^^^^^
+- Argument groups created from dataclass-like that have zero configurable
+  arguments no longer adds a config loader (`#634
+  <https://github.com/omni-us/jsonargparse/pull/634>`__).
+
+Deprecated
+^^^^^^^^^^
+- ``add_dataclass_arguments`` is deprecated and will be removed in v5.0.0.
+  Instead use ``add_class_arguments`` (`#634
+  <https://github.com/omni-us/jsonargparse/pull/634>`__).
+
+
+v4.34.1 (2024-12-02)
+
 Fixed
 ^^^^^
 - List of dataclass with nested dataclass attribute fails to parse (`#625
diff --git a/jsonargparse/_actions.py b/jsonargparse/_actions.py
index 938d494..8190c9d 100644
--- a/jsonargparse/_actions.py
+++ b/jsonargparse/_actions.py
@@ -9,7 +9,7 @@ from contextlib import contextmanager
 from contextvars import ContextVar
 from typing import Any, Dict, List, Optional, Tuple, Type, Union
 
-from ._common import Action, get_class_instantiator, is_subclass, parser_context
+from ._common import Action, is_subclass, parser_context
 from ._loaders_dumpers import get_loader_exceptions, load_value
 from ._namespace import Namespace, NSKeyError, split_key, split_key_root
 from ._optionals import get_config_read_mode
@@ -333,10 +333,6 @@ class _ActionConfigLoad(Action):
     def check_type(self, value, parser):
         return self._load_config(value, parser)
 
-    def instantiate_classes(self, value):
-        instantiator_fn = get_class_instantiator()
-        return instantiator_fn(self.basetype, **value)
-
 
 class _ActionHelpClassPath(Action):
     sub_add_kwargs: Dict[str, Any] = {}
diff --git a/jsonargparse/_core.py b/jsonargparse/_core.py
index 548e5b6..8f35c56 100644
--- a/jsonargparse/_core.py
+++ b/jsonargparse/_core.py
@@ -124,7 +124,7 @@ class ActionsContainer(SignatureArguments, argparse._ActionsContainer):
         if "type" in kwargs:
             if is_dataclass_like(kwargs["type"]):
                 nested_key = args[0].lstrip("-")
-                self.add_dataclass_arguments(kwargs.pop("type"), nested_key, **kwargs)
+                self.add_class_arguments(kwargs.pop("type"), nested_key, **kwargs)
                 return _find_action(parser, nested_key)
             if ActionTypeHint.is_supported_typehint(kwargs["type"]):
                 args = ActionTypeHint.prepare_add_argument(
@@ -176,6 +176,33 @@ class ActionsContainer(SignatureArguments, argparse._ActionsContainer):
             parser.groups[name] = group  # type: ignore[union-attr]
         return group
 
+    def set_defaults(self, *args: Dict[str, Any], **kwargs: Any) -> None:
+        """Sets default values from dictionary or keyword arguments.
+
+        Args:
+            *args: Dictionary defining the default values to set.
+            **kwargs: Sets default values based on keyword arguments.
+
+        Raises:
+            KeyError: If key not defined in the parser.
+        """
+        for arg in args:
+            for dest, default in arg.items():
+                action = _find_action(self, dest)
+                if action is None:
+                    raise NSKeyError(f'No action for key "{dest}" to set its default.')
+                elif isinstance(action, ActionConfigFile):
+                    ActionConfigFile.set_default_error()
+                elif isinstance(action, _ActionConfigLoad):
+                    default = {f"{dest}.{k}": v for k, v in default.items()}
+                    self.set_defaults(default)
+                    continue
+                if isinstance(action, ActionTypeHint):
+                    default = action.normalize_default(default)
+                self._defaults[dest] = action.default = default
+        if kwargs:
+            self.set_defaults(kwargs)
+
 
 class _ArgumentGroup(ActionsContainer, argparse._ArgumentGroup):
     """Extension of argparse._ArgumentGroup to support additional functionalities."""
@@ -884,29 +911,6 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
 
     ## Methods related to defaults ##
 
-    def set_defaults(self, *args: Dict[str, Any], **kwargs: Any) -> None:
-        """Sets default values from dictionary or keyword arguments.
-
-        Args:
-            *args: Dictionary defining the default values to set.
-            **kwargs: Sets default values based on keyword arguments.
-
-        Raises:
-            KeyError: If key not defined in the parser.
-        """
-        for arg in args:
-            for dest, default in arg.items():
-                action = _find_action(self, dest)
-                if action is None:
-                    raise NSKeyError(f'No action for key "{dest}" to set its default.')
-                elif isinstance(action, ActionConfigFile):
-                    ActionConfigFile.set_default_error()
-                if isinstance(action, ActionTypeHint):
-                    default = action.normalize_default(default)
-                self._defaults[dest] = action.default = default
-        if kwargs:
-            self.set_defaults(kwargs)
-
     def _get_default_config_files(self) -> List[Tuple[Optional[str], Path]]:
         default_config_files = []
 
@@ -1179,7 +1183,7 @@ class ArgumentParser(ParserDeprecations, ActionsContainer, ArgumentLinking, argp
         cfg = strip_meta(cfg)
         for component in components:
             ActionLink.apply_instantiation_links(self, cfg, target=component.dest)
-            if isinstance(component, (ActionTypeHint, _ActionConfigLoad)):
+            if isinstance(component, ActionTypeHint):
                 try:
                     value, parent, key = cfg.get_value_and_parent(component.dest)
                 except (KeyError, AttributeError):
diff --git a/jsonargparse/_deprecated.py b/jsonargparse/_deprecated.py
index bb3a615..ab05a39 100644
--- a/jsonargparse/_deprecated.py
+++ b/jsonargparse/_deprecated.py
@@ -520,6 +520,17 @@ class ParserDeprecations:
         else:
             raise ValueError("error_handler can be either a Callable or None.")
 
+    @deprecated(
+        """
+        add_dataclass_arguments was deprecated in v4.35.0 and will be removed in
+        v5.0.0. Instead use add_class_arguments.
+    """
+    )
+    def add_dataclass_arguments(self, *args, **kwargs):
+        if "title" in kwargs:
+            kwargs["help"] = kwargs.pop("title")
+        return self.add_class_arguments(*args, **kwargs)
+
 
 ParserError = ArgumentError
 
diff --git a/jsonargparse/_signatures.py b/jsonargparse/_signatures.py
index efd57e2..ee27720 100644
--- a/jsonargparse/_signatures.py
+++ b/jsonargparse/_signatures.py
@@ -4,7 +4,6 @@ import dataclasses
 import inspect
 import re
 from argparse import SUPPRESS, ArgumentParser
-from contextlib import suppress
 from typing import Any, Callable, List, Optional, Set, Tuple, Type, Union
 
 from ._actions import _ActionConfigLoad
@@ -14,6 +13,7 @@ from ._common import (
     get_generic_origin,
     get_unaliased_type,
     is_dataclass_like,
+    is_final_class,
     is_subclass,
 )
 from ._namespace import Namespace
@@ -53,7 +53,7 @@ class SignatureArguments(LoggerProperty):
         nested_key: Optional[str] = None,
         as_group: bool = True,
         as_positional: bool = False,
-        default: Optional[Union[dict, Namespace, LazyInitBaseClass]] = None,
+        default: Optional[Union[dict, Namespace, LazyInitBaseClass, Type]] = None,
         skip: Optional[Set[Union[str, int]]] = None,
         instantiate: bool = True,
         fail_untyped: bool = True,
@@ -82,16 +82,27 @@ class SignatureArguments(LoggerProperty):
             ValueError: When not given a class.
             ValueError: When there are required parameters without at least one valid type.
         """
-        if not inspect.isclass(get_generic_origin(get_unaliased_type(theclass))):
+        unaliased_class_type = get_unaliased_type(theclass)
+        if not inspect.isclass(get_generic_origin(unaliased_class_type)):
             raise ValueError(f"Expected 'theclass' parameter to be a class type, got: {theclass}")
         if not (
             isinstance(default, (NoneType, dict, Namespace))
-            or (isinstance(default, LazyInitBaseClass) and isinstance(default, theclass))
+            or (isinstance(default, LazyInitBaseClass) and isinstance(default, unaliased_class_type))
+            or (
+                not is_final_class(default.__class__)
+                and is_dataclass_like(default.__class__)
+                and isinstance(default, unaliased_class_type)
+            )
         ):
             raise ValueError(
-                f"Expected 'default' parameter to be a dict, Namespace or lazy instance of the class, got: {default}"
+                f"Expected 'default' to be dict, Namespace, lazy instance or dataclass-like, got: {default}"
             )
-        linked_targets = get_private_kwargs(kwargs, linked_targets=None)
+        linked_targets, help, _ = get_private_kwargs(
+            kwargs,
+            linked_targets=None,
+            help=None,
+            required=None,  # Ignored because provided when adding signatures, remove with dataclass inheritance support
+        )
 
         added_args = self._add_signature_arguments(
             theclass,
@@ -104,6 +115,7 @@ class SignatureArguments(LoggerProperty):
             sub_configs=sub_configs,
             instantiate=instantiate,
             linked_targets=linked_targets,
+            help=help,
         )
 
         if default:
@@ -112,6 +124,8 @@ class SignatureArguments(LoggerProperty):
             defaults = default
             if isinstance(default, LazyInitBaseClass):
                 defaults = default.lazy_get_init_args().as_dict()
+            elif is_dataclass_like(default.__class__):
+                defaults = dataclass_to_dict(default)
             elif isinstance(default, Namespace):
                 defaults = default.as_dict()
             if defaults:
@@ -230,6 +244,7 @@ class SignatureArguments(LoggerProperty):
         sub_configs: bool = False,
         instantiate: bool = True,
         linked_targets: Optional[Set[str]] = None,
+        help: Optional[str] = None,
     ) -> List[str]:
         """Adds arguments from parameters of objects based on signatures and docstrings.
 
@@ -273,7 +288,10 @@ class SignatureArguments(LoggerProperty):
                 )
 
         ## Create group if requested ##
-        doc_group = get_doc_short_description(function_or_class, method_name, logger=self.logger)
+        if help is not None:
+            doc_group = help
+        else:
+            doc_group = get_doc_short_description(function_or_class, method_name, logger=self.logger)
         component = getattr(function_or_class, method_name) if method_name else function_or_class
         container = self._create_group_if_requested(
             component,
@@ -424,67 +442,6 @@ class SignatureArguments(LoggerProperty):
                 f" type. Parameter '{name}' from '{src}' does not specify a type."
             )
 
-    def add_dataclass_arguments(
-        self,
-        theclass: Type,
-        nested_key: str,
-        default: Optional[Union[Type, dict]] = None,
-        as_group: bool = True,
-        fail_untyped: bool = True,
-        **kwargs,
-    ) -> List[str]:
-        """Adds arguments from a dataclass based on its field types and docstrings.
-
-        Args:
-            theclass: Class from which to add arguments.
-            nested_key: Key for nested namespace.
-            default: Value for defaults. Must be instance of or kwargs for theclass.
-            as_group: Whether arguments should be added to a new argument group.
-            fail_untyped: Whether to raise exception if a required parameter does not have a type.
-
-        Returns:
-            The list of arguments added.
-
-        Raises:
-            ValueError: When not given a dataclass.
-            ValueError: When default is not instance of or kwargs for theclass.
-        """
-        if not is_dataclass_like(theclass):
-            raise ValueError(f'Expected "theclass" argument to be a dataclass-like, given {theclass}')
-
-        doc_group = get_doc_short_description(theclass, logger=self.logger)
-        for key in ["help", "title"]:
-            if key in kwargs and kwargs[key] is not None:
-                doc_group = kwargs.pop(key)
-        group = self._create_group_if_requested(theclass, nested_key, as_group, doc_group, config_load_type=theclass)
-
-        defaults = {}
-        if default is not None:
-            if isinstance(default, dict):
-                with suppress(TypeError):
-                    default = theclass(**default)
-            if not isinstance(default, get_unaliased_type(theclass)):
-                raise ValueError(
-                    f'Expected "default" argument to be an instance of "{theclass.__name__}" '
-                    f"or its kwargs dict, given {default}"
-                )
-            defaults = dataclass_to_dict(default)
-
-        added_args: List[str] = []
-        param_kwargs = {k: v for k, v in kwargs.items() if k == "sub_configs"}
-        for param in get_signature_parameters(theclass, None, logger=self.logger):
-            self._add_signature_parameter(
-                group,
-                nested_key,
-                param,
-                added_args,
-                fail_untyped=fail_untyped,
-                default=defaults.get(param.name, inspect_empty),
-                **param_kwargs,
-            )
-
-        return added_args
-
     def add_subclass_arguments(
         self,
         baseclass: Union[Type, Tuple[Type, ...]],
@@ -618,8 +575,6 @@ def dataclass_to_dict(value) -> dict:
         pydantic_model = is_pydantic_model(type(value))
         if pydantic_model:
             return value.dict() if pydantic_model == 1 else value.model_dump()
-    if isinstance(value, LazyInitBaseClass):
-        return value.lazy_get_init_data().as_dict()
     return dataclasses.asdict(value)
 
 
diff --git a/jsonargparse/_typehints.py b/jsonargparse/_typehints.py
index cd7ebc5..4ac35b3 100644
--- a/jsonargparse/_typehints.py
+++ b/jsonargparse/_typehints.py
@@ -1592,8 +1592,6 @@ class LazyInitBaseClass:
 
     def lazy_get_init_data(self):
         init_args = self.lazy_get_init_args()
-        if is_dataclass_like(self._lazy_class_type):
-            return init_args
         init = Namespace(class_path=get_import_path(self._lazy_class_type))
         if len(self._lazy_kwargs) > 0:
             init["init_args"] = init_args


Test patch:
diff --git a/jsonargparse_tests/test_dataclass_like.py b/jsonargparse_tests/test_dataclass_like.py
index ff736d0..bce6865 100644
--- a/jsonargparse_tests/test_dataclass_like.py
+++ b/jsonargparse_tests/test_dataclass_like.py
@@ -15,6 +15,7 @@ from jsonargparse import (
     compose_dataclasses,
     lazy_instance,
 )
+from jsonargparse._namespace import NSKeyError
 from jsonargparse._optionals import (
     attrs_support,
     docstring_parser_support,
@@ -58,16 +59,16 @@ class DataClassB:
     """
 
     b1: PositiveFloat = PositiveFloat(3.0)  # type: ignore[valid-type]
-    b2: DataClassA = DataClassA()
+    b2: DataClassA = DataClassA(a2="x")
 
 
 class MixedClass(int, DataClassA):
     """MixedClass description"""
 
 
-def test_add_dataclass_arguments(parser, subtests):
-    parser.add_dataclass_arguments(DataClassA, "a", default=DataClassA(), title="CustomA title")
-    parser.add_dataclass_arguments(DataClassB, "b", default=DataClassB())
+def test_add_class_arguments(parser, subtests):
+    parser.add_class_arguments(DataClassA, "a", default=DataClassA(), help="CustomA title")
+    parser.add_class_arguments(DataClassB, "b", default=DataClassB())
 
     with subtests.test("get_defaults"):
         cfg = parser.get_defaults()
@@ -96,11 +97,11 @@ def test_add_dataclass_arguments(parser, subtests):
 
     with subtests.test("add failures"):
         with pytest.raises(ValueError):
-            parser.add_dataclass_arguments(1, "c")
+            parser.add_class_arguments(1, "c")
+        with pytest.raises(NSKeyError):
+            parser.add_class_arguments(DataClassB, "c", default=DataClassB(b2=DataClassB()))
         with pytest.raises(ValueError):
-            parser.add_dataclass_arguments(DataClassB, "c", default=DataClassB(b2=DataClassB()))
-        with pytest.raises(ValueError):
-            parser.add_dataclass_arguments(MixedClass, "c")
+            parser.add_class_arguments(MixedClass, "c")
 
 
 @dataclasses.dataclass
@@ -115,7 +116,7 @@ class NestedDefaultsB:
 
 
 def test_add_dataclass_nested_defaults(parser):
-    parser.add_dataclass_arguments(NestedDefaultsB, "data")
+    parser.add_class_arguments(NestedDefaultsB, "data")
     cfg = parser.parse_args(["--data.a=[{}]"])
     assert cfg.data == Namespace(a=[Namespace(x=[], v=1)])
 
@@ -218,7 +219,7 @@ def test_list_append_defaults(parser):
 def test_add_argument_dataclass_type(parser):
     parser.add_argument("--b", type=DataClassB, default=DataClassB(b1=7.0))
     cfg = parser.get_defaults()
-    assert {"b1": 7.0, "b2": {"a1": 1, "a2": "2"}} == cfg.b.as_dict()
+    assert {"b1": 7.0, "b2": {"a1": 1, "a2": "x"}} == cfg.b.as_dict()
     init = parser.instantiate_classes(cfg)
     assert isinstance(init.b, DataClassB)
     assert isinstance(init.b.b2, DataClassA)
@@ -254,11 +255,13 @@ class DataInitFalse:
 
 
 def test_dataclass_field_init_false(parser):
-    added = parser.add_dataclass_arguments(DataInitFalse, "d")
+    added = parser.add_class_arguments(DataInitFalse, "data")
     assert added == []
     assert parser.get_defaults() == Namespace()
-    cfg = parser.parse_args(["--d", "{}"])
-    assert cfg.d == Namespace()
+    cfg = parser.parse_args([])
+    assert cfg == Namespace()
+    init = parser.instantiate_classes(cfg)
+    assert isinstance(init.data, DataInitFalse)
 
 
 @dataclasses.dataclass
@@ -513,7 +516,7 @@ class SpecificData:
 
 
 def test_nested_generic_dataclass(parser):
-    parser.add_dataclass_arguments(SpecificData, "x")
+    parser.add_class_arguments(SpecificData, "x")
     help_str = get_parser_help(parser).lower()
     assert "--x.y.g1 g1          (required, type: float)" in help_str
     assert "--x.y.g2 [item,...]  (required, type: tuple[float, float])" in help_str
@@ -853,11 +856,11 @@ class TestPydantic:
         parser.add_argument("--data", type=PydanticDataFieldInitFalse)
         help_str = get_parser_help(parser)
         assert "--data.p1" not in help_str
-        cfg = parser.parse_args(["--data", "{}"])
-        assert cfg.data == Namespace()
+        cfg = parser.parse_args([])
+        assert cfg == Namespace()
 
-        cfg = parser.instantiate_classes(cfg)
-        assert cfg.data.p1 == "-"
+        init = parser.instantiate_classes(cfg)
+        assert init.data.p1 == "-"
 
     def test_dataclass_stdlib_field(self, parser):
         parser.add_argument("--data", type=PydanticDataStdlibField)
@@ -969,9 +972,9 @@ class TestAttrs:
         # not included in config object or its value is None.')
 
         parser.add_argument("--data", type=AttrsFieldInitFalse)
-        cfg = parser.parse_args(["--data", "{}"])
+        cfg = parser.parse_args([])
         help_str = get_parser_help(parser)
         assert "--data.p1" not in help_str
-        assert cfg.data == Namespace()
-        cfg = parser.instantiate_classes(cfg)
-        assert cfg.data.p1 == {}
+        assert cfg == Namespace()
+        init = parser.instantiate_classes(cfg)
+        assert init.data.p1 == {}
diff --git a/jsonargparse_tests/test_deprecated.py b/jsonargparse_tests/test_deprecated.py
index 41e544a..da522cd 100644
--- a/jsonargparse_tests/test_deprecated.py
+++ b/jsonargparse_tests/test_deprecated.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import dataclasses
 import os
 import pathlib
 from calendar import Calendar
@@ -10,6 +11,7 @@ from io import StringIO
 from warnings import catch_warnings
 
 import pytest
+import yaml
 
 from jsonargparse import (
     CLI,
@@ -32,7 +34,7 @@ from jsonargparse._deprecated import (
     shown_deprecation_warnings,
     usage_and_exit_error_handler,
 )
-from jsonargparse._optionals import jsonnet_support, url_support
+from jsonargparse._optionals import docstring_parser_support, jsonnet_support, url_support
 from jsonargparse._util import argument_error
 from jsonargparse_tests.conftest import (
     get_parser_help,
@@ -40,6 +42,7 @@ from jsonargparse_tests.conftest import (
     skip_if_docstring_parser_unavailable,
     skip_if_requests_unavailable,
 )
+from jsonargparse_tests.test_dataclass_like import DataClassA
 from jsonargparse_tests.test_jsonnet import example_2_jsonnet
 
 
@@ -575,3 +578,28 @@ def test_action_jsonnet_ext_vars(parser):
     assert 9 == len(cfg.jsonnet["records"])
     assert "#8" == cfg.jsonnet["records"][-2]["ref"]
     assert 15.5 == cfg.jsonnet["records"][-2]["val"]
+
+
+def test_add_dataclass_arguments(parser, subtests):
+    with catch_warnings(record=True) as w:
+        parser.add_dataclass_arguments(DataClassA, "a", default=DataClassA(), title="CustomA title")
+    assert_deprecation_warn(
+        w,
+        message="add_dataclass_arguments was deprecated",
+        code='parser.add_dataclass_arguments(DataClassA, "a", default=DataClassA(), title="CustomA title")',
+    )
+
+    with subtests.test("get_defaults"):
+        cfg = parser.get_defaults()
+        assert dataclasses.asdict(DataClassA()) == cfg["a"].as_dict()
+        dump = yaml.safe_load(parser.dump(cfg))
+        assert dataclasses.asdict(DataClassA()) == dump["a"]
+
+    with subtests.test("instantiate_classes"):
+        init = parser.instantiate_classes(cfg)
+        assert isinstance(init["a"], DataClassA)
+
+    with subtests.test("docstrings in help"):
+        help_str = get_parser_help(parser)
+        if docstring_parser_support:
+            assert "CustomA title:" in help_str
diff --git a/jsonargparse_tests/test_postponed_annotations.py b/jsonargparse_tests/test_postponed_annotations.py
index afbdccb..d7cee42 100644
--- a/jsonargparse_tests/test_postponed_annotations.py
+++ b/jsonargparse_tests/test_postponed_annotations.py
@@ -338,7 +338,7 @@ def test_get_types_dataclass_pep585(parser):
     types = get_types(Data585)
     list_int = List[int] if sys.version_info < (3, 9) else list[int]
     assert types == {"a": list_int, "b": str}
-    parser.add_dataclass_arguments(Data585, "data")
+    parser.add_class_arguments(Data585, "data")
     cfg = parser.parse_args(["--data.a=[1, 2]"])
     assert cfg.data == Namespace(a=[1, 2], b="x")
 
@@ -350,6 +350,6 @@ class DataWithInit585(Data585):
 
 
 def test_add_dataclass_with_init_pep585(parser, tmp_cwd):
-    parser.add_dataclass_arguments(DataWithInit585, "data")
+    parser.add_class_arguments(DataWithInit585, "data")
     cfg = parser.parse_args(["--data.a=[1, 2]", "--data.b=."])
     assert cfg.data == Namespace(a=[1, 2], b=Path_drw("."))