Repository: omni-us/jsonargparse
Pull request #214 (2022-12-15 19:48:24)

Fix incorrectly considering pure dataclass instance default as a subclass type
## What does this PR do?

Fix regression caused by https://github.com/omni-us/jsonargparse/commit/8df46b1044bcad10f6d3137a784519b6abce7998.

## Before submitting

- [x] Did you read the [contributing guideline](https://github.com/omni-us/jsonargparse/blob/master/CONTRIBUTING.rst)?
- [n/a] 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)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=omni-us_jsonargparse&pullRequest=214)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=omni-us_jsonargparse&pullRequest=214&resolved=false&types=CODE_SMELL)

[![No Coverage information](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo-16px.png 'No Coverage information')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=214) No Coverage information  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=214&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=omni-us_jsonargparse&pullRequest=214&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 5c6015a..1134df6 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -23,6 +23,7 @@ Fixed
 ^^^^^
 - List type with empty list default causes failure `PyLaia#48
   <https://github.com/jpuigcerver/PyLaia/issues/48>`__.
+- Pure dataclass instance default being considered as a subclass type.
 
 
 v4.18.0 (2022-11-29)
diff --git a/jsonargparse/parameter_resolvers.py b/jsonargparse/parameter_resolvers.py
index e23484d..d64d726 100644
--- a/jsonargparse/parameter_resolvers.py
+++ b/jsonargparse/parameter_resolvers.py
@@ -294,6 +294,9 @@ def get_kwargs_pop_or_get_parameter(node, component, parent, doc_params, log_deb
 
 
 def is_param_subclass_instance_default(param: ParamData) -> bool:
+    from .signatures import is_pure_dataclass
+    if is_pure_dataclass(type(param.default)):
+        return False
     from .typehints import ActionTypeHint, get_subclass_types
     class_types = get_subclass_types(param.annotation)
     return (


Test patch:
diff --git a/jsonargparse_tests/test_signatures.py b/jsonargparse_tests/test_signatures.py
index 8a9696d..49b7f71 100755
--- a/jsonargparse_tests/test_signatures.py
+++ b/jsonargparse_tests/test_signatures.py
@@ -1309,54 +1309,48 @@ class SignaturesConfigTests(TempDirTestCase):
         self.assertEqual(cfg.fit.model.foo, 123)
 
 
-class DataclassesTests(unittest.TestCase):
+@dataclasses.dataclass(frozen=True)
+class MyDataClassA:
+    """MyDataClassA description
 
-    @classmethod
-    def setUpClass(cls):
+    Args:
+        a1: a1 help
+        a2: a2 help
+    """
+    a1: PositiveInt = PositiveInt(1)  # type: ignore
+    a2: str = '2'
 
-        @dataclasses.dataclass(frozen=True)
-        class MyDataClassA:
-            """MyDataClassA description
+@dataclasses.dataclass
+class MyDataClassB:
+    """MyDataClassB description
 
-            Args:
-                a1: a1 help
-                a2: a2 help
-            """
-            a1: PositiveInt = PositiveInt(1)
-            a2: str = '2'
+    Args:
+        b1: b1 help
+        b2: b2 help
+    """
+    b1: PositiveFloat = PositiveFloat(3.0)  # type: ignore
+    b2: MyDataClassA = MyDataClassA()
 
-        @dataclasses.dataclass
-        class MyDataClassB:
-            """MyDataClassB description
-
-            Args:
-                b1: b1 help
-                b2: b2 help
-            """
-            b1: PositiveFloat = PositiveFloat(3.0)
-            b2: MyDataClassA = MyDataClassA()
-
-        cls.MyDataClassA = MyDataClassA
-        cls.MyDataClassB = MyDataClassB
 
+class DataclassesTests(unittest.TestCase):
 
     @unittest.skipIf(not docstring_parser_support, 'docstring-parser package is required')
     def test_add_dataclass_arguments(self):
         parser = ArgumentParser(error_handler=None)
-        parser.add_dataclass_arguments(self.MyDataClassA, 'a', default=self.MyDataClassA(), title='CustomA title')
-        parser.add_dataclass_arguments(self.MyDataClassB, 'b', default=self.MyDataClassB())
+        parser.add_dataclass_arguments(MyDataClassA, 'a', default=MyDataClassA(), title='CustomA title')
+        parser.add_dataclass_arguments(MyDataClassB, 'b', default=MyDataClassB())
 
         cfg = parser.get_defaults()
-        self.assertEqual(dataclasses.asdict(self.MyDataClassA()), cfg['a'].as_dict())
-        self.assertEqual(dataclasses.asdict(self.MyDataClassB()), cfg['b'].as_dict())
+        self.assertEqual(dataclasses.asdict(MyDataClassA()), cfg['a'].as_dict())
+        self.assertEqual(dataclasses.asdict(MyDataClassB()), cfg['b'].as_dict())
         dump = yaml.safe_load(parser.dump(cfg))
-        self.assertEqual(dataclasses.asdict(self.MyDataClassA()), dump['a'])
-        self.assertEqual(dataclasses.asdict(self.MyDataClassB()), dump['b'])
+        self.assertEqual(dataclasses.asdict(MyDataClassA()), dump['a'])
+        self.assertEqual(dataclasses.asdict(MyDataClassB()), dump['b'])
 
         cfg = parser.instantiate_classes(cfg)
-        self.assertIsInstance(cfg['a'], self.MyDataClassA)
-        self.assertIsInstance(cfg['b'], self.MyDataClassB)
-        self.assertIsInstance(cfg['b'].b2, self.MyDataClassA)
+        self.assertIsInstance(cfg['a'], MyDataClassA)
+        self.assertIsInstance(cfg['b'], MyDataClassB)
+        self.assertIsInstance(cfg['b'].b2, MyDataClassA)
 
         self.assertEqual(5, parser.parse_args(['--b.b2.a1=5']).b.b2.a1)
         self.assertRaises(ParserError, lambda: parser.parse_args(['--b.b2.a1=x']))
@@ -1371,9 +1365,9 @@ class DataclassesTests(unittest.TestCase):
             parser.add_dataclass_arguments(1, 'c')
 
         with self.assertRaises(ValueError):
-            parser.add_dataclass_arguments(self.MyDataClassB, 'c', default=self.MyDataClassB(b2=self.MyDataClassB()))
+            parser.add_dataclass_arguments(MyDataClassB, 'c', default=MyDataClassB(b2=MyDataClassB()))
 
-        class MyClass(int, self.MyDataClassA):
+        class MyClass(int, MyDataClassA):
             """MyClass description"""
 
         with self.assertRaises(ValueError):
@@ -1385,8 +1379,8 @@ class DataclassesTests(unittest.TestCase):
         class MyClass:
             def __init__(
                 self,
-                a1: self.MyDataClassA = self.MyDataClassA(),  # type: ignore
-                a2: self.MyDataClassB = self.MyDataClassB(),  # type: ignore
+                a1: MyDataClassA = MyDataClassA(),
+                a2: MyDataClassB = MyDataClassB(),
             ):
                 self.a1 = a1
                 self.a2 = a2
@@ -1395,22 +1389,22 @@ class DataclassesTests(unittest.TestCase):
         parser.add_class_arguments(MyClass, 'g')
 
         cfg = parser.get_defaults()
-        self.assertEqual(dataclasses.asdict(self.MyDataClassA()), cfg['g']['a1'].as_dict())
-        self.assertEqual(dataclasses.asdict(self.MyDataClassB()), cfg['g']['a2'].as_dict())
+        self.assertEqual(dataclasses.asdict(MyDataClassA()), cfg['g']['a1'].as_dict())
+        self.assertEqual(dataclasses.asdict(MyDataClassB()), cfg['g']['a2'].as_dict())
         dump = yaml.safe_load(parser.dump(cfg))
-        self.assertEqual(dataclasses.asdict(self.MyDataClassA()), dump['g']['a1'])
-        self.assertEqual(dataclasses.asdict(self.MyDataClassB()), dump['g']['a2'])
+        self.assertEqual(dataclasses.asdict(MyDataClassA()), dump['g']['a1'])
+        self.assertEqual(dataclasses.asdict(MyDataClassB()), dump['g']['a2'])
 
         cfg_init = parser.instantiate_classes(cfg)
-        self.assertIsInstance(cfg_init.g.a1, self.MyDataClassA)
-        self.assertIsInstance(cfg_init.g.a2, self.MyDataClassB)
-        self.assertIsInstance(cfg_init.g.a2.b2, self.MyDataClassA)
+        self.assertIsInstance(cfg_init.g.a1, MyDataClassA)
+        self.assertIsInstance(cfg_init.g.a2, MyDataClassB)
+        self.assertIsInstance(cfg_init.g.a2.b2, MyDataClassA)
 
 
     def test_dataclass_typehint_in_subclass(self):
 
         class MyClass1:
-            def __init__(self, a1: self.MyDataClassB = self.MyDataClassB()):  # type: ignore
+            def __init__(self, a1: MyDataClassB = MyDataClassB()):
                 """MyClass1 description"""
                 self.a1 = a1
 
@@ -1431,21 +1425,21 @@ class DataclassesTests(unittest.TestCase):
             self.assertIsInstance(cfg.c1.init_args.a1.b2.a1, PositiveInt)
             cfg = parser.instantiate_classes(cfg)
             self.assertIsInstance(cfg['c1'], MyClass1)
-            self.assertIsInstance(cfg['c1'].a1, self.MyDataClassB)
-            self.assertIsInstance(cfg['c1'].a1.b2, self.MyDataClassA)
+            self.assertIsInstance(cfg['c1'].a1, MyDataClassB)
+            self.assertIsInstance(cfg['c1'].a1.b2, MyDataClassA)
             self.assertIsInstance(cfg['c1'].a1.b1, PositiveFloat)
 
 
     def test_dataclass_add_argument_type(self):
         parser = ArgumentParser()
-        parser.add_argument('--b', type=self.MyDataClassB, default=self.MyDataClassB(b1=7.0))
+        parser.add_argument('--b', type=MyDataClassB, default=MyDataClassB(b1=7.0))
 
         cfg = parser.get_defaults()
         self.assertEqual({'b1': 7.0, 'b2': {'a1': 1, 'a2': '2'}}, cfg['b'].as_dict())
 
         cfg = parser.instantiate_classes(cfg)
-        self.assertIsInstance(cfg['b'], self.MyDataClassB)
-        self.assertIsInstance(cfg['b'].b2, self.MyDataClassA)
+        self.assertIsInstance(cfg['b'], MyDataClassB)
+        self.assertIsInstance(cfg['b'].b2, MyDataClassA)
 
 
     def test_dataclass_add_argument_type_some_required(self):