Skip to content

Multiple bugs with Optional nested dataclasses #358

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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