Repository: lebrice/SimpleParsing

Issue:
Nested parameter name with dataclass
Hi, great library, thanks ! 
I have an issue with nested parser from dataclass. 

When trying to create a parser from a dataclass
```

@dataclass
class JBuildRelease:
    id: int 
    url: str  
    docker_image: str  

    parser = simple_parsing.ArgumentParser()
    parser.add_argument('--run_id', type=str)
    parser.add_arguments(JBuildRelease, dest='jbuild', prefix='jbuild')
    args = parser.parse_args()
```

I get the following:

> usage: release.py [-h] [--run_id str] --id int --url str --docker_image str
> release.py: error: the following arguments are required: --id, --url, --docker_image
> 

I wish the arguments would be prefixed like so: --jbuild.id 1 --jbuild.url url --jbuild.docker_image image
Is this flow supported ? 

Discussion:

Patch:
diff --git a/simple_parsing/conflicts.py b/simple_parsing/conflicts.py
index 3d7af91..8efd80c 100644
--- a/simple_parsing/conflicts.py
+++ b/simple_parsing/conflicts.py
@@ -119,6 +119,8 @@ class ConflictResolver:
             else:
                 field_wrappers.extend(w.fields)
 
+        # TODO: #49: Also consider the conflicts with regular argparse arguments.
+
         conflicts: Dict[str, List[FieldWrapper]] = defaultdict(list)
         for field_wrapper in field_wrappers:
             for option_string in field_wrapper.option_strings:
diff --git a/simple_parsing/wrappers/dataclass_wrapper.py b/simple_parsing/wrappers/dataclass_wrapper.py
index 5cc9785..62b3826 100644
--- a/simple_parsing/wrappers/dataclass_wrapper.py
+++ b/simple_parsing/wrappers/dataclass_wrapper.py
@@ -24,7 +24,8 @@ class DataclassWrapper(Wrapper[Dataclass]):
         # super().__init__(dataclass, name)
         self.dataclass = dataclass
         self._name = name
-        self.default = default
+        self.default = default 
+        self.prefix = prefix
 
         self.fields: List[FieldWrapper] = []
         self._destinations: List[str] = []
@@ -75,7 +76,7 @@ class DataclassWrapper(Wrapper[Dataclass]):
 
             else:
                 # a normal attribute
-                field_wrapper = FieldWrapper(field, parent=self)
+                field_wrapper = FieldWrapper(field, parent=self, prefix=self.prefix)
                 logger.debug(f"wrapped field at {field_wrapper.dest} has a default value of {field_wrapper.default}")
                 self.fields.append(field_wrapper)
         


Test patch:
diff --git a/test/conftest.py b/test/conftest.py
index d821c60..3fe8689 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -48,6 +48,15 @@ def simple_attribute(request):
     logging.debug(f"Attribute type: {some_type}, passed value: '{passed_value}', expected: '{expected_value}'")
     return request.param
 
+@pytest.fixture
+def assert_equals_stdout(capsys):
+    def strip(string): return "".join(string.split())
+
+    def should_equal(expected: str, file_path: str=None):
+        out = capsys.readouterr().out
+        assert strip(out) == strip(expected), file_path
+    return should_equal
+
 
 @pytest.fixture(scope="module")
 def parser():
diff --git a/test/test_issue_46.py b/test/test_issue_46.py
new file mode 100644
index 0000000..5538fff
--- /dev/null
+++ b/test/test_issue_46.py
@@ -0,0 +1,99 @@
+from dataclasses import dataclass
+import simple_parsing
+import textwrap
+import pytest
+
+
+@dataclass
+class JBuildRelease:
+    id: int 
+    url: str  
+    docker_image: str  
+
+
+def test_issue_46(assert_equals_stdout):
+    parser = simple_parsing.ArgumentParser()
+    parser.add_argument('--run_id', type=str)
+    parser.add_arguments(JBuildRelease, dest="jbuild", prefix="jbuild")
+    
+    parser.print_help()
+    
+    assert_equals_stdout(textwrap.dedent("""\
+        usage: pytest [-h] [--run_id str] --jbuildid int --jbuildurl str
+                      --jbuilddocker_image str
+        
+        optional arguments:
+          -h, --help            show this help message and exit
+          --run_id str
+        
+        JBuildRelease ['jbuild']:
+          JBuildRelease(id:int, url:str, docker_image:str)
+        
+          --jbuildid int
+          --jbuildurl str
+          --jbuilddocker_image str
+        """
+    ))
+    from .testutils import raises_missing_required_arg
+    with raises_missing_required_arg():
+        args = parser.parse_args("--id 123 --jbuild.id 456 --jbuild.url bob --jbuild.docker_image foo".split())
+
+
+def test_issue_46_solution2(assert_equals_stdout):
+    # This (now) works:
+    parser = simple_parsing.ArgumentParser(add_dest_to_option_strings=True)
+    parser.add_argument('--run_id', type=str)
+    parser.add_arguments(JBuildRelease, dest="jbuild", prefix="jbuild.")
+    
+    parser.print_help()
+    assert_equals_stdout(textwrap.dedent("""\
+        usage: pytest [-h] [--run_id str] --jbuild.id int --jbuild.url str
+                      --jbuild.docker_image str
+
+        optional arguments:
+          -h, --help            show this help message and exit
+          --run_id str
+
+        JBuildRelease ['jbuild']:
+          JBuildRelease(id:int, url:str, docker_image:str)
+
+          --jbuild.id int
+          --jbuild.url str
+          --jbuild.docker_image str
+        """
+    ))
+
+
+@pytest.mark.xfail(reason="TODO: Issue #49")
+def test_conflict_with_regular_argparse_arg():
+    # This _should_ work, but it doesn't, adding a new issue for this:
+    # the problem: SimpleParsing doesn't yet detect 
+    # conflicts between arguments added the usual way with `add_argument` and those
+    # added through `add_arguments`.
+    parser = simple_parsing.ArgumentParser()
+    parser.add_argument('--id', type=str)
+    parser.add_arguments(JBuildRelease, dest="jbuild")
+    args = parser.parse_args("--id 123 --jbuild.id 456 --jbuild.url bob --jbuild.docker_image foo".split())
+    assert args.id == 123
+    assert args.jbuild.id == 456
+
+
+@pytest.mark.xfail(reason="TODO: Issue #49")
+def test_workaround():
+    from simple_parsing import mutable_field, ConflictResolution
+    # This also doesn't work, since the prefix is only added to the 'offending'
+    # argument, rather than to all the args in that group. 
+    @dataclass
+    class Main:
+        id: int
+        jbuild: JBuildRelease
+    
+    parser = simple_parsing.ArgumentParser()
+    parser.add_arguments(Main, "main")
+    args = parser.parse_args("--id 123 --jbuild.id 456 --jbuild.url bob --jbuild.docker_image foo".split())
+    args = args.main
+    assert args.id == 123
+    assert args.jbuild.id == 456
+    
+    
+    
\ No newline at end of file