Repository: lebrice/SimpleParsing

Issue:
Utility function to shorten the parsing code isn't shown in README
I thing that the code to parse simple, most basic use case is a little bit too complicated:
```python
from dataclasses import dataclass
from simple_parsing import ArgumentParser


@dataclass
class HParams:
    """Set of options for the training of a Model."""

    num_layers: int = 4
    num_units: int = 64
    optimizer: str = "ADAM"
    learning_rate: float = 0.001


parser = ArgumentParser()
parser.add_arguments(HParams, dest="hparams")
args = parser.parse_args()
print(args.hparams)
```

Instead i would like something like this:
```python
from dataclasses import dataclass
from simple_parsing import parse_dataclass


@dataclass
class HParams:
    """Set of options for the training of a Model."""

    num_layers: int = 4
    num_units: int = 64
    optimizer: str = "ADAM"
    learning_rate: float = 0.001

args: HParams = simple_parsing.parse_dataclass(HParams)
```

The only thing I (and probably the most of the users) need is the simple utility function, that takes the class name, and returns the parsed object. This is what almost every user really wants. Other, more advanced features are important too, but they shouldn't obscure the most frequent use cases.

If you decide to add an utility function I would advice to add the simplified example to the front page, so that every new user would see how simple it is. You could than put more complicated examples. My personal experience, when I first saw the library was that the library is great, exactly something I was looking for, but for some reason it is unnecessary complicated.


Discussion:

Patch:
diff --git a/README.md b/README.md
index e84dc67..e8bdcae 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,15 @@ Options ['options']:
                         Help string for a float argument (default: 0.0001)
 ```
 
+### (*new*) Simplified API:
+
+For a simple use-case, where you only want to parse a single dataclass, you can use the `simple_parsing.parse` or `simple_parsing.parse_known_args` functions:
+
+```python
+options: Options = simple_parsing.parse(Options)
+options, leftover_args = simple_parsing.parse_known_args(Options)
+```
+
 
 ## installation
 
diff --git a/examples/config_files/one_config.py b/examples/config_files/one_config.py
index dffeba9..ec30a1d 100644
--- a/examples/config_files/one_config.py
+++ b/examples/config_files/one_config.py
@@ -13,7 +13,7 @@ class TrainConfig:
 
 
 def main(args=None) -> None:
-    cfg = simple_parsing.parse(config_class=TrainConfig, args=args)
+    cfg = simple_parsing.parse(config_class=TrainConfig, args=args, add_config_path_arg=True)
     print(f"Training {cfg.exp_name} with {cfg.workers} workers...")
 
 
diff --git a/examples/demo_simple.py b/examples/demo_simple.py
new file mode 100644
index 0000000..cbd02ef
--- /dev/null
+++ b/examples/demo_simple.py
@@ -0,0 +1,16 @@
+# examples/demo_simple.py
+from dataclasses import dataclass
+
+import simple_parsing
+
+
+@dataclass
+class Options:
+    """Help string for this group of command-line arguments"""
+
+    log_dir: str  # Help string for a required str argument
+    learning_rate: float = 1e-4  # Help string for a float argument
+
+
+options = simple_parsing.parse(Options)
+print(options)
diff --git a/simple_parsing/__init__.py b/simple_parsing/__init__.py
index 7af4dd3..8af002e 100644
--- a/simple_parsing/__init__.py
+++ b/simple_parsing/__init__.py
@@ -22,6 +22,7 @@ from .parsing import (
     NestedMode,
     ParsingError,
     parse,
+    parse_known_args,
 )
 from .utils import InconsistentArgumentError
 
@@ -44,6 +45,7 @@ __all__ = [
     "DashVariant",
     "ParsingError",
     "parse",
+    "parse_known_args",
     "ArgumentGenerationMode",
     "NestedMode",
     "InconsistentArgumentError",
diff --git a/simple_parsing/parsing.py b/simple_parsing/parsing.py
index 9d4187d..e054255 100644
--- a/simple_parsing/parsing.py
+++ b/simple_parsing/parsing.py
@@ -100,9 +100,9 @@ class ArgumentParser(argparse.ArgumentParser):
         argument_generation_mode=ArgumentGenerationMode.FLAT,
         nested_mode: NestedMode = NestedMode.DEFAULT,
         formatter_class: type[HelpFormatter] = SimpleHelpFormatter,
-        add_dest_to_option_strings: bool | None = None,
         add_config_path_arg: bool | None = None,
         config_path: Path | str | Sequence[Path | str] | None = None,
+        add_dest_to_option_strings: bool | None = None,
         **kwargs,
     ):
         kwargs["formatter_class"] = formatter_class
@@ -884,21 +884,82 @@ def parse(
     config_class: type[Dataclass],
     config_path: Path | str | None = None,
     args: str | Sequence[str] | None = None,
+    default: Dataclass | None = None,
+    dest: str = "config",
+    *,
+    nested_mode: NestedMode = NestedMode.WITHOUT_ROOT,
+    conflict_resolution: ConflictResolution = ConflictResolution.AUTO,
+    add_option_string_dash_variants: DashVariant = DashVariant.AUTO,
+    argument_generation_mode=ArgumentGenerationMode.FLAT,
+    formatter_class: type[HelpFormatter] = SimpleHelpFormatter,
+    add_config_path_arg: bool | None = None,
 ) -> Dataclass:
     """Parse the given dataclass from the command-line.
 
+    See the `ArgumentParser` constructor for more details on the arguments (they are the same here
+    except for `nested_mode`, which has a different default value).
+
+    If `config_path` is passed, loads the values from that file and uses them as defaults.
+    """
+    parser = ArgumentParser(
+        nested_mode=nested_mode,
+        add_help=True,
+        # add_config_path_arg=None,
+        config_path=config_path,
+        conflict_resolution=conflict_resolution,
+        add_option_string_dash_variants=add_option_string_dash_variants,
+        argument_generation_mode=argument_generation_mode,
+        formatter_class=formatter_class,
+        add_config_path_arg=add_config_path_arg,
+    )
+
+    parser.add_arguments(config_class, dest=dest, default=default)
+
+    if isinstance(args, str):
+        args = shlex.split(args)
+    parsed_args = parser.parse_args(args)
+
+    config: Dataclass = getattr(parsed_args, dest)
+    return config
+
+
+def parse_known_args(
+    config_class: type[Dataclass],
+    config_path: Path | str | None = None,
+    args: str | Sequence[str] | None = None,
+    default: Dataclass | None = None,
+    dest: str = "config",
+    attempt_to_reorder: bool = False,
+    *,
+    nested_mode: NestedMode = NestedMode.WITHOUT_ROOT,
+    conflict_resolution: ConflictResolution = ConflictResolution.AUTO,
+    add_option_string_dash_variants: DashVariant = DashVariant.AUTO,
+    argument_generation_mode=ArgumentGenerationMode.FLAT,
+    formatter_class: type[HelpFormatter] = SimpleHelpFormatter,
+    add_config_path_arg: bool | None = None,
+) -> tuple[Dataclass, list[str]]:
+    """Parse the given dataclass from the command-line, returning the leftover arguments.
+
+    See the `ArgumentParser` constructor for more details on the arguments (they are the same here
+    except for `nested_mode`, which has a different default value).
+
     If `config_path` is passed, loads the values from that file and uses them as defaults.
     """
 
     if isinstance(args, str):
         args = shlex.split(args)
     parser = ArgumentParser(
-        nested_mode=NestedMode.WITHOUT_ROOT,
+        nested_mode=nested_mode,
         add_help=True,
-        add_config_path_arg=True,
+        # add_config_path_arg=None,
         config_path=config_path,
+        conflict_resolution=conflict_resolution,
+        add_option_string_dash_variants=add_option_string_dash_variants,
+        argument_generation_mode=argument_generation_mode,
+        formatter_class=formatter_class,
+        add_config_path_arg=add_config_path_arg,
     )
-    parser.add_arguments(config_class, dest="config")
-    parsed_args = parser.parse_args(args)
-    config: Dataclass = parsed_args.config
-    return config
+    parser.add_arguments(config_class, dest=dest, default=default)
+    parsed_args, unknown_args = parser.parse_known_args(args, attempt_to_reorder=attempt_to_reorder)
+    config: Dataclass = getattr(parsed_args, dest)
+    return config, unknown_args


Test patch:
diff --git a/test/conftest.py b/test/conftest.py
index 4ee8f36..8e4bba2 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,18 +1,18 @@
+from __future__ import annotations
+
 import logging
+import os
 import sys
-from dataclasses import dataclass
 from logging import getLogger as get_logger
-from typing import Any, ClassVar, List, NamedTuple, Optional, Tuple, Type
+from typing import Any, NamedTuple
 
 import pytest
-
-from simple_parsing import choice
-from simple_parsing.helpers import Serializable
+from typing_extensions import Literal
 
 pytest.register_assert_rewrite("test.testutils")
 
 # List of simple attributes to use in test:
-simple_arguments: List[Tuple[Type, Any, Any]] = [
+simple_arguments: list[tuple[type, Any, Any]] = [
     # type, passed value, expected (parsed) value
     (int, "123", 123),
     (int, 123, 123),
@@ -40,7 +40,7 @@ simple_arguments: List[Tuple[Type, Any, Any]] = [
 
 
 class SimpleAttributeTuple(NamedTuple):
-    field_type: Type
+    field_type: type
     passed_value: str
     expected_value: Any
 
@@ -57,12 +57,19 @@ def simple_attribute(request):
     )
 
 
+@pytest.fixture(autouse=True, params=["simple", "verbose"])
+def simple_and_advanced_api(request, monkeypatch):
+    api: Literal["simple", "verbose"] = request.param
+    monkeypatch.setitem(os.environ, "SIMPLE_PARSING_API", api)
+    yield
+
+
 @pytest.fixture
 def assert_equals_stdout(capsys):
     def strip(string):
         return "".join(string.split())
 
-    def should_equal(expected: str, file_path: Optional[str] = None):
+    def should_equal(expected: str, file_path: str | None = None):
         if "optional arguments" in expected and sys.version_info >= (3, 10):
             expected = expected.replace("optional arguments", "options")
         out = capsys.readouterr().out
@@ -139,108 +146,3 @@ def logs_warning(caplog):
     messages = [x.message for x in caplog.get_records("call") if x.levelno == logging.WARNING]
     if not messages:
         pytest.fail(f"No warning messages were logged: {messages}")
-
-
-@pytest.fixture
-def TaskHyperParameters():
-    """Test fixture that gives a good example use-case from a real datascience
-    project.
-    """
-    from .testutils import TestSetup
-
-    @dataclass
-    class TaskHyperParameters(TestSetup, Serializable):
-        """
-        HyperParameters for a task-specific model
-        """
-
-        name: str  # name of the task
-        num_layers: int = 1  # number of dense layers
-        num_units: int = 8  # units per layer
-        activation: str = choice("tanh", "relu", "linear", default="tanh")  # activation function
-        use_batchnorm: bool = (
-            False  # whether or not to use batch normalization after each dense layer
-        )
-        use_dropout: bool = True  # whether or not to use dropout after each dense layer
-        dropout_rate: float = 0.1  # the dropout rate
-        use_image_features: bool = True  # whether or not image features should be used as input
-        use_likes: bool = True  # whether or not 'likes' features should be used as input
-        l1_reg: float = 0.005  # L1 regularization coefficient
-        l2_reg: float = 0.005  # L2 regularization coefficient
-
-        # Whether or not a task-specific Embedding layer should be used on the 'likes' features.
-        # When set to 'True', it is expected that there no shared embedding is used.
-        embed_likes: bool = False
-
-    return TaskHyperParameters
-
-
-@pytest.fixture
-def HyperParameters(TaskHyperParameters):
-    from .testutils import TestSetup
-
-    @dataclass
-    class HyperParameters(TestSetup, Serializable):
-        """Hyperparameters of a multi-headed model."""
-
-        batch_size: int = 128  # the batch size
-        learning_rate: float = 0.001  # Learning Rate
-        optimizer: str = choice(
-            "SGD", "ADAM", default="SGD"
-        )  # Which optimizer to use during training.
-
-        # number of individual 'pages' that were kept during preprocessing of the 'likes'.
-        # This corresponds to the number of entries in the multi-hot like vector.
-        num_like_pages: int = 10_000
-
-        gender_loss_weight: float = 1.0  # relative weight of the gender loss
-        age_loss_weight: float = 1.0  # relative weight of the age_group loss
-
-        num_text_features: ClassVar[int] = 91
-        num_image_features: ClassVar[int] = 65
-
-        max_number_of_likes: int = 2000
-        embedding_dim: int = 8
-
-        shared_likes_embedding: bool = True
-
-        # Whether or not to better filtering of liked pages
-        use_custom_likes: bool = True
-
-        # Gender model settings
-        gender: TaskHyperParameters = TaskHyperParameters(
-            "gender",
-            num_layers=1,
-            num_units=32,
-            use_batchnorm=False,
-            use_dropout=True,
-            dropout_rate=0.1,
-            use_image_features=True,
-            use_likes=True,
-        )
-
-        # Age Group Model settings
-        age_group: TaskHyperParameters = TaskHyperParameters(
-            "age_group",
-            num_layers=2,
-            num_units=64,
-            use_batchnorm=False,
-            use_dropout=True,
-            dropout_rate=0.1,
-            use_image_features=True,
-            use_likes=True,
-        )
-
-        # Personality Model(s) settings:
-        personality: TaskHyperParameters = TaskHyperParameters(
-            "personality",
-            num_layers=1,
-            num_units=8,
-            use_batchnorm=False,
-            use_dropout=True,
-            dropout_rate=0.1,
-            use_image_features=False,
-            use_likes=False,
-        )
-
-    return HyperParameters
diff --git a/test/nesting/example_use_cases.py b/test/nesting/example_use_cases.py
index 6a01d95..d7136d0 100644
--- a/test/nesting/example_use_cases.py
+++ b/test/nesting/example_use_cases.py
@@ -1,6 +1,8 @@
 from dataclasses import dataclass, field
 from typing import ClassVar, List
 
+from simple_parsing.helpers.serialization.serializable import Serializable
+
 from ..test_utils import TestSetup
 
 __all__ = [
@@ -97,7 +99,7 @@ class TaskHyperParameters(TestSetup):
 
 
 @dataclass
-class HyperParameters(TestSetup):
+class HyperParameters(TestSetup, Serializable):
     """Hyperparameters of our model."""
 
     # the batch size
diff --git a/test/nesting/test_nesting_auto.py b/test/nesting/test_nesting_auto.py
index 89188f1..c263b53 100644
--- a/test/nesting/test_nesting_auto.py
+++ b/test/nesting/test_nesting_auto.py
@@ -1,8 +1,9 @@
 from simple_parsing import ConflictResolution
 
+from .example_use_cases import HyperParameters
 
-def test_real_use_case(silent, HyperParameters):
-    HyperParameters
+
+def test_real_use_case(silent):
     hparams = HyperParameters.setup(
         "--age_group.num_layers 5 " "--age_group.num_units 65 ",
         conflict_resolution_mode=ConflictResolution.AUTO,
diff --git a/test/nesting/test_nesting_explicit.py b/test/nesting/test_nesting_explicit.py
index f100bb5..af4d61c 100644
--- a/test/nesting/test_nesting_explicit.py
+++ b/test/nesting/test_nesting_explicit.py
@@ -1,7 +1,9 @@
 from simple_parsing import ConflictResolution
 
+from .example_use_cases import HyperParameters, TaskHyperParameters
 
-def test_real_use_case(silent, HyperParameters, TaskHyperParameters):
+
+def test_real_use_case(silent):
     hparams = HyperParameters.setup(
         "--hyper_parameters.age_group.num_layers 5",
         conflict_resolution_mode=ConflictResolution.EXPLICIT,
diff --git a/test/nesting/test_nesting_merge.py b/test/nesting/test_nesting_merge.py
index 3b2e6a1..57c3eec 100644
--- a/test/nesting/test_nesting_merge.py
+++ b/test/nesting/test_nesting_merge.py
@@ -1,7 +1,9 @@
 from simple_parsing import ConflictResolution
 
+from .example_use_cases import HyperParameters
 
-def test_hparam_use_case(silent, HyperParameters, TaskHyperParameters):
+
+def test_hparam_use_case(silent):
     hparams = HyperParameters.setup(
         "--num_layers 5 6 7", conflict_resolution_mode=ConflictResolution.ALWAYS_MERGE
     )
diff --git a/test/nesting/test_weird_use_cases.py b/test/nesting/test_weird_use_cases.py
index 6b6ebe8..cf3cbf9 100644
--- a/test/nesting/test_weird_use_cases.py
+++ b/test/nesting/test_weird_use_cases.py
@@ -4,6 +4,8 @@ from typing import Callable, Type
 
 from simple_parsing import ConflictResolution
 
+from .example_use_cases import HyperParameters
+
 
 def simple_tree_structure(some_type: Type[T], default_value_function: Callable[[str], T]):
     @dataclass
@@ -200,7 +202,7 @@ def test_weird_with_duplicates_and_at_different_levels():
     assert aabbccdd.ccdd.dd.d2.d == "ccdd_dd_d_2"
 
 
-def test_defaults(HyperParameters):
+def test_defaults():
     default = HyperParameters()
     parsed = HyperParameters.setup("")
     for attr, value in vars(default).items():
diff --git a/test/test_custom_args.py b/test/test_custom_args.py
index e3803fc..4b36b20 100644
--- a/test/test_custom_args.py
+++ b/test/test_custom_args.py
@@ -16,6 +16,7 @@ from .testutils import (
     raises_expected_n_args,
     raises_missing_required_arg,
     raises_unrecognized_args,
+    using_simple_api,
 )
 
 
@@ -182,27 +183,28 @@ def test_only_dashes():
         my_var: int
         a: AClass
 
-    assert_help_output_equals(
-        SomeClass.get_help_text(add_option_string_dash_variants=DashVariant.DASH),
-        textwrap.dedent(
-            """\
-            usage: pytest [-h] --my-var int --a-var int
+    if not using_simple_api():
+        assert_help_output_equals(
+            SomeClass.get_help_text(add_option_string_dash_variants=DashVariant.DASH),
+            textwrap.dedent(
+                """\
+                usage: pytest [-h] --my-var int --a-var int
 
-            optional arguments:
-              -h, --help    show this help message and exit
+                optional arguments:
+                -h, --help    show this help message and exit
 
-            test_only_dashes.<locals>.SomeClass ['some_class']:
-              lol
+                test_only_dashes.<locals>.SomeClass ['some_class']:
+                lol
 
-              --my-var int
+                --my-var int
 
-            test_only_dashes.<locals>.AClass ['some_class.a']:
-              foo
+                test_only_dashes.<locals>.AClass ['some_class.a']:
+                foo
 
-              --a-var int
-            """  # noqa: W293
-        ),
-    )
+                --a-var int
+                """  # noqa: W293
+            ),
+        )
     sc = SomeClass.setup("--my-var 2 --a-var 3", add_option_string_dash_variants=DashVariant.DASH)
     assert sc.my_var == 2
     assert sc.a.a_var == 3
diff --git a/test/test_utils.py b/test/test_utils.py
index 73eadf1..b0f9d5e 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -1,4 +1,5 @@
 import enum
+import os
 from dataclasses import dataclass
 from typing import Dict, List, Set, Tuple, Type
 
@@ -100,9 +101,10 @@ def test_is_enum(t: Type):
     assert utils.is_enum(t)
 
 
-def test_json_serializable(HyperParameters, tmpdir):
+def test_json_serializable(tmpdir):
+    from .nesting.example_use_cases import HyperParameters
+
     hparams = HyperParameters()
-    import os
 
     filename = "hparams.json"
     hparams.save_json(os.path.join(tmpdir, filename))
diff --git a/test/testutils.py b/test/testutils.py
index ce557c8..66b4854 100644
--- a/test/testutils.py
+++ b/test/testutils.py
@@ -1,3 +1,4 @@
+import os
 import shlex
 import string
 import sys
@@ -124,6 +125,10 @@ class TestParser(simple_parsing.ArgumentParser, Generic[T]):
         return value
 
 
+def using_simple_api() -> bool:
+    return os.environ.get("SIMPLE_PARSING_API", "simple") == "simple"
+
+
 class TestSetup:
     @classmethod
     def setup(
@@ -148,40 +153,68 @@ class TestSetup:
         Returns:
             {cls}} -- the class's type.
         """
-        parser = simple_parsing.ArgumentParser(
-            conflict_resolution=conflict_resolution_mode,
-            add_option_string_dash_variants=add_option_string_dash_variants,
-            argument_generation_mode=argument_generation_mode,
-            nested_mode=nested_mode,
-        )
-        if dest is None:
-            dest = camel_case(cls.__name__)
 
-        parser.add_arguments(cls, dest=dest, default=default)
-
-        if arguments is None:
-            if parse_known_args:
-                args = parser.parse_known_args(attempt_to_reorder=attempt_to_reorder)
-            else:
-                args = parser.parse_args()
-        else:
-            splits = shlex.split(arguments)
+        # TODO: Add a switch here, that uses either the 'simpler' API: `simple_parsing.parse` or
+        # this more verbose API: `simple_parsing.ArgumentParser().parse_args()`
+        dest = dest or camel_case(cls.__name__)
+
+        if using_simple_api():
+            common_kwargs = dict(
+                config_class=cls,
+                config_path=None,
+                args=arguments,
+                default=default,
+                add_config_path_arg=None,
+                nested_mode=nested_mode,
+                dest=dest,
+                add_option_string_dash_variants=add_option_string_dash_variants,
+                conflict_resolution=conflict_resolution_mode,
+                argument_generation_mode=argument_generation_mode,
+            )
             if parse_known_args:
-                args, unknown_args = parser.parse_known_args(
-                    splits, attempt_to_reorder=attempt_to_reorder
+                instance, unknown_args = simple_parsing.parse_known_args(
+                    **common_kwargs,
+                    attempt_to_reorder=attempt_to_reorder,
                 )
             else:
-                args = parser.parse_args(splits)
-        assert hasattr(args, dest), f"attribute '{dest}' not found in args {args}"
-        instance: Dataclass = getattr(args, dest)  # type: ignore
-        delattr(args, dest)
+                instance = simple_parsing.parse(**common_kwargs)
+        else:
+            parser = simple_parsing.ArgumentParser(
+                conflict_resolution=conflict_resolution_mode,
+                add_option_string_dash_variants=add_option_string_dash_variants,
+                argument_generation_mode=argument_generation_mode,
+                nested_mode=nested_mode,
+            )
+            if dest is None:
+                dest = camel_case(cls.__name__)
 
-        # If there are subgroups, we can allow an extra "subgroups" attribute, otherwise we don't
-        # expect any other arguments.
-        args_dict = vars(args).copy()
-        args_dict.pop("subgroups", None)
+            parser.add_arguments(cls, dest=dest, default=default)
 
-        assert not args_dict, f"Namespace has leftover garbage values (besides subgroups): {args}"
+            if arguments is None:
+                if parse_known_args:
+                    args = parser.parse_known_args(attempt_to_reorder=attempt_to_reorder)
+                else:
+                    args = parser.parse_args()
+            else:
+                splits = shlex.split(arguments)
+                if parse_known_args:
+                    args, unknown_args = parser.parse_known_args(
+                        splits, attempt_to_reorder=attempt_to_reorder
+                    )
+                else:
+                    args = parser.parse_args(splits)
+            assert hasattr(args, dest), f"attribute '{dest}' not found in args {args}"
+            instance: Dataclass = getattr(args, dest)  # type: ignore
+            delattr(args, dest)
+
+            # If there are subgroups, we can allow an extra "subgroups" attribute, otherwise we don't
+            # expect any other arguments.
+            args_dict = vars(args).copy()
+            args_dict.pop("subgroups", None)
+
+            assert (
+                not args_dict
+            ), f"Namespace has leftover garbage values (besides subgroups): {args}"
 
         instance = cast(Dataclass, instance)
         return instance
diff --git a/test/utils/__init__.py b/test/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/utils/test_serialization.py b/test/utils/test_serialization.py
index 6b9b1e8..be3acff 100644
--- a/test/utils/test_serialization.py
+++ b/test/utils/test_serialization.py
@@ -39,8 +39,12 @@ from simple_parsing.helpers.serialization.serializable import SerializableMixin
 #         children: Mapping[str, Optional[ChildWithFriends]] = mutable_field(OrderedDict)
 
 
-@pytest.fixture(scope="module", params=[True, False])
-def frozen(request):
+@pytest.fixture(scope="function", params=[True, False])
+def frozen(request, monkeypatch):
+    """Switches from using frozen / non-frozen dataclasses during tests."""
+
+    # NOTE: Need to unregister all the subclasses of SerializableMixin and FrozenSerializable, so
+    # the dataclasses from one test aren't used in another.
     subclasses_before = SerializableMixin.subclasses.copy()
     from simple_parsing.helpers.serialization.decoding import (
         _decoding_fns,
@@ -52,19 +56,23 @@ def frozen(request):
 
     yield frozen
 
+    # NOTE: Not assigning a new list here, just to be sure we're changing the object on the class
+    # where it is originally set.
     SerializableMixin.subclasses.clear()
     SerializableMixin.subclasses.extend(subclasses_before)
-    # TODO: Need to unregister the decoding functions!
+
+    # Unregister the decoding functions.
     _decoding_fns.clear()
     _decoding_fns.update(decoding_fns_before)
 
+    # Clear the LRU cache of `get_decoding_fn`.
     get_decoding_fn.cache_clear()
 
     # SerializableMixin.subclasses = subclasses_before
     # note: clear the `subclasses` of the base classes?
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def Child(frozen: bool):
     @dataclass(frozen=frozen)
     class Child(FrozenSerializable if frozen else Serializable):
@@ -74,7 +82,7 @@ def Child(frozen: bool):
     return Child
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def Parent(frozen: bool, Child):
     @dataclass(frozen=frozen)
     class Parent(FrozenSerializable if frozen else Serializable):
@@ -84,7 +92,7 @@ def Parent(frozen: bool, Child):
     return Parent
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def ParentWithOptionalChildren(Parent, Child):
     @dataclass(frozen=issubclass(Parent, FrozenSerializable))
     class ParentWithOptionalChildren(Parent):
@@ -94,7 +102,7 @@ def ParentWithOptionalChildren(Parent, Child):
     return ParentWithOptionalChildren
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def ChildWithFriends(Child):
     @dataclass(frozen=issubclass(Child, FrozenSerializable))
     class ChildWithFriends(Child):
@@ -103,7 +111,7 @@ def ChildWithFriends(Child):
     return ChildWithFriends
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def ParentWithOptionalChildrenWithFriends(ParentWithOptionalChildren, ChildWithFriends):
     @dataclass(frozen=issubclass(ParentWithOptionalChildren, FrozenSerializable))
     class ParentWithOptionalChildrenWithFriends(ParentWithOptionalChildren):
@@ -192,7 +200,7 @@ def test_lists(silent, ChildWithFriends, Child, ParentWithOptionalChildrenWithFr
     assert parsed_nancy == nancy
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def Base(frozen: bool):
     @dataclass(frozen=frozen)
     class Base(FrozenSerializable if frozen else Serializable, decode_into_subclasses=True):
@@ -201,7 +209,7 @@ def Base(frozen: bool):
     return Base
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def A(Base):
     @dataclass(frozen=issubclass(Base, FrozenSerializable))
     class A(Base):
@@ -211,7 +219,7 @@ def A(Base):
     return A
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def B(Base):
     @dataclass(frozen=issubclass(Base, FrozenSerializable))
     class B(Base):
@@ -221,7 +229,7 @@ def B(Base):
     return B
 
 
-@pytest.fixture(scope="module")
+@pytest.fixture()
 def Container(frozen: bool, Base):
     @dataclass(frozen=frozen)
     class Container(FrozenSerializable if frozen else Serializable):
diff --git a/test/utils/test_yaml.py b/test/utils/test_yaml.py
index 2ce3c9f..3694d1b 100644
--- a/test/utils/test_yaml.py
+++ b/test/utils/test_yaml.py
@@ -7,6 +7,8 @@ from typing import List
 from simple_parsing import list_field
 from simple_parsing.helpers.serialization import YamlSerializable
 
+from ..nesting.example_use_cases import HyperParameters
+
 
 @dataclass
 class Point(YamlSerializable):
@@ -63,7 +65,7 @@ def test_dumps_loads():
     )
 
 
-def test_save_yaml(HyperParameters, tmpdir: Path):
+def test_save_yaml(tmpdir: Path):
     hparams = HyperParameters.setup("")
     tmp_path = Path(tmpdir / "temp.yml")
     hparams.save_yaml(tmp_path)
@@ -72,7 +74,7 @@ def test_save_yaml(HyperParameters, tmpdir: Path):
     assert hparams == _hparams
 
 
-def test_save_json(HyperParameters, tmpdir: Path):
+def test_save_json(tmpdir: Path):
     hparams = HyperParameters.setup("")
     tmp_path = Path(tmpdir / "temp.json")
     hparams.save_yaml(tmp_path)
@@ -80,7 +82,7 @@ def test_save_json(HyperParameters, tmpdir: Path):
     assert hparams == _hparams
 
 
-def test_save_yml(HyperParameters, tmpdir: Path):
+def test_save_yml(tmpdir: Path):
     hparams = HyperParameters.setup("")
     tmp_path = Path(tmpdir / "temp.yml")
     hparams.save(tmp_path)