felix-martel / pydanclick

Add click options from a Pydantic model
https://felix-martel.github.io/pydanclick/
MIT License
32 stars 8 forks source link

Random exception with `Optional` parameter #19

Closed azmeuk closed 2 months ago

azmeuk commented 2 months ago

With pydantic 2.9.0 and python 3.12, I randomly get an exception with the following snippet:

import click
from pydanclick import from_pydantic
from pydantic import BaseModel
from typing import Optional

class Bar(BaseModel):
    baz: str = "baz"

class Foo(BaseModel):
    bar: Optional[Bar] = None

@click.command()
@from_pydantic(Foo)
def cli(foo: Foo):
    ...

if __name__ == "__main__":
    cli()
python foobar.py
Traceback (most recent call last):
  File "/home/eloi/dev/yaal/scim2-cli/foobar.py", line 22, in <module>
    cli()
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/click/core.py", line 1157, in __call__
    return self.main(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/click/core.py", line 1078, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/click/core.py", line 1434, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/click/core.py", line 783, in invoke
    return __callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/pydanclick/main.py", line 66, in wrapped
    kwargs[variable_name] = validator(kwargs)
                            ^^^^^^^^^^^^^^^^^
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/pydanclick/model/validation.py", line 45, in model_validate_kwargs
    raw_model = _unflatten_dict(flat_model)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/eloi/.cache/pypoetry/virtualenvs/scim2-cli-KsA89t9a-py3.12/lib/python3.12/site-packages/pydanclick/model/validation.py", line 67, in _unflatten_dict
    current_dict[parts[-1]] = v
    ~~~~~~~~~~~~^^^^^^^^^^^
TypeError: 'NoneType' object does not support item assignment

When using the | None syntax instead of Optional, the snippet never crashes, though I am not sure why:

class Foo(BaseModel):
    bar: Bar | None = None

I am not encountering the exception if the type of bar is str for instance:

class Foo(BaseModel):
    bar: Optional[str] = None
azmeuk commented 2 months ago

flat_model has different values depending on the situations: https://github.com/felix-martel/pydanclick/blob/2468b62ef0ac9ccd93dedec0eb3d612b537b8f4f/pydanclick/model/validation.py#L44

On cases the exception is raised, it has the value of {'bar': None, 'bar.baz': 'baz'}, on cases the exception is not raised it has the value of {'bar.baz': 'baz', 'bar': None}.

It seems that the order has an impact here.

felix-martel commented 2 months ago

Thanks for the bug report, I'll look into it!

felix-martel commented 2 months ago

Can reproduce. It was caused by the way we treated default value and was solved by #17 Fix is available in version 0.3

azmeuk commented 2 months ago

I confirm this is solved with 0.3. Thank you!