Skip to content

Multiple bugs with Optional nested dataclasses #358

@gottalottarock

Description

@gottalottarock

Describe the bug

  1. post_init runs with None

If a nested dataclass inside an optional field defines a post_init depending on a required field, it gets called even when the optional container is None.

  1. Ignored nested args

When passing only a nested argument (e.g. --datamodule.data_none.feature.path), the parser silently ignores it and keeps the optional container as None. Unless another field of that container (like --datamodule.data_none.name) is also provided.

To Reproduce

from simple_parsing import ArgumentParser, ArgumentGenerationMode, NestedMode, field, subgroups
from dataclasses import dataclass
from pathlib import Path

@dataclass
class Feature:
    path: Path
    name: str = field(init=False)

    def __post_init__(self):
        self.name = self.path.name

@dataclass
class Data:
    feature: Feature
    name: str = 'data'

@dataclass
class Datamodule():
    name: str = 'datamodule'
    data_none: Data | None = None

@dataclass
class Config():
    datamodule: Datamodule
    
def main(config: Config):
    print(config)


if __name__ == "__main__":
    parser = ArgumentParser(
        argument_generation_mode=ArgumentGenerationMode.NESTED,
        nested_mode=NestedMode.WITHOUT_ROOT,
    )
    parser.add_arguments(Config, dest="config")
    args = parser.parse_args()
    main(args.config)

1. Unexpected exception
Expected behavior

$ python issue.py --datamodule.name data
Config(datamodule=Datamodule(name='data', data_none=None))

Actual behavior
post_init of Feature runs with path=None and crashes:

$ python issue.py --datamodule.name data
Traceback (most recent call last):
  ...
AttributeError: 'NoneType' object has no attribute 'name'
Full traceback:
$ python simple-parsing-error.py --datamodule.name "data"                                                                                                                      
Traceback (most recent call last):                                                                                                                                             
File "/home/data/skuznetsov/conform-rank/simple-parsing-error.py", line 47, in <module>
  args = parser.parse_args()                                                                                                                                                 
         ^^^^^^^^^^^^^^^^^^^                                                                                                                                                 
File "/home/skuznetsov/miniconda3/lib/python3.12/argparse.py", line 1904, in parse_args
  args, argv = self.parse_known_args(args, namespace)                                                                                                                        
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                        
File "/home/data/skuznetsov/conform-rank/.venv/lib/python3.12/site-packages/simple_parsing/parsing.py", line 361, in parse_known_args
  parsed_args = self._postprocessing(parsed_args)                                                                                                                            
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                            
File "/home/data/skuznetsov/conform-rank/.venv/lib/python3.12/site-packages/simple_parsing/parsing.py", line 593, in _postprocessing
  parsed_args = self._instantiate_dataclasses(                                                                                                                               
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                               
File "/home/data/skuznetsov/conform-rank/.venv/lib/python3.12/site-packages/simple_parsing/parsing.py", line 862, in _instantiate_dataclasses
  value_for_dataclass_field = _create_dataclass_instance(                                                                                                                    
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                    
File "/home/data/skuznetsov/conform-rank/.venv/lib/python3.12/site-packages/simple_parsing/parsing.py", line 1160, in _create_dataclass_instance
  return constructor(**constructor_args)                                                                                                                                     
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                                                     
File "<string>", line 4, in __init__                                                                                                                                         
File "/home/data/skuznetsov/conform-rank/simple-parsing-error.py", line 11, in __post_init__
  self.name = self.path.name                                                                                                                                                 
              ^^^^^^^^^^^^^^                                                         
AttributeError: 'NoneType' object has no attribute 'name'

2. Nested args inside an Optional field are ignored unless another field is set
Expected behavior:

$ python issue.py --datamodule.name data --datamodule.data_none.feature.path /home
Config(datamodule=Datamodule(
  name='data',
  data_none=Data(feature=Feature(path=PosixPath('/home'), name='home'), name=data)
))

Actual behavior:
The nested argument is ignored, and data_none stays None:

$ python issue.py --datamodule.name data --datamodule.data_none.feature.path /home
Config(datamodule=Datamodule(name='data', data_none=None))

Only when another field is provided does it work:

$ python issue.py --datamodule.name data \
                  --datamodule.data_none.name name \
                  --datamodule.data_none.feature.path /home
Config(datamodule=Datamodule(
  name='data',
  data_none=Data(feature=Feature(path=PosixPath('/home'), name='home'), name='name')
))

Desktop (please complete the following information):

  • Version 0.1.7
  • Python version: 3.12.11

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions