openapi-generators/openapi-python-client

code generated for `date-time` fromat can not parse null response data

rihkddd opened this issue · 2 comments

Describe the bug
code generated for date-time fromat in models file like this for now:

_update_time = d.pop("updateTime", UNSET)
update_time: Union[Unset, datetime.datetime]
if isinstance(_update_time, Unset):
    update_time = UNSET
else:
    update_time = isoparse(_update_time)

if response data of updateTime field is null, the _update_time variable will be set to None, so isoparse function will rasie TypeError except.

self = <dateutil.parser.isoparser.isoparser object at 0x7f94bf7ae950>, dt_str = None

    def _parse_isodate_common(self, dt_str):
>       len_str = len(dt_str)
E       TypeError: object of type 'NoneType' has no len()

../../../../.local/lib/python3.10/site-packages/dateutil/parser/isoparser.py:213: TypeError

OpenAPI Spec File

{
    "updateTime": {
        "type": "string",
        "description": "updateTime",
        "format": "date-time"
    }
}

Desktop (please complete the following information):

  • OS: windows wsl ubuntu 22.0
  • Python Version: 3.10.0
  • openapi-python-client version 0.17.2

Additional context
my suggestion is, adding branch check value is None before call isoparse function, generate code like this:

  _update_time = d.pop("updateTime", UNSET)
  update_time: Union[Unset, datetime.datetime]
  if isinstance(_update_time, Unset):
      update_time = UNSET
  elif _update_time is None:
      update_time = None
  else:
      update_time = isoparse(_update_time)

I also have this problem, but the issue lies in the spec.
The spec needs to define that response is nullable, otherwise your spec declares that you will always get a valid date-time string.

To do this, you can adjust your spec for openapi 3.0.x as such:

{
    "updateTime": {
        "type": "string",
        "description": "updateTime",
        "format": "date-time",
        "nullable": true
    }
}

which will generate code similar to this:

def _parse_update_time(data: object) -> Union[None, Unset, datetime.datetime]:
    if data is None:
        return data
    if isinstance(data, Unset):
        return data
    try:
        if not isinstance(data, str):
            raise TypeError()
        update_time_type_0 = isoparse(data)

        return update_time_type_0
    except:  # noqa: E722
        pass
    return cast(Union[None, Unset, datetime.datetime], data)

update_time = _parse_update_time(d.pop("updateTime", UNSET))

OpenAPI 3.1 removed the nullable field in favor of just supplying more types via list:

{
    "updateTime": {
        "type": "string",
        "description": "updateTime",
        "format": ["date-time", "null"]
    }
}

For this I haven't tested generation and can't really speak about it.

My situation is sadly that I'm using external provider's OpenAPI specification, which is not likely to be updated any time soon.

@dbanty If it is an option to generally support implicit null values in date-time and maybe others, where parsing will break unexpectedly, I'm happy to take a look at implementing this.

@texhnolyze Thanks for the spec you pointing out, also your situation is very common that OpenAPI specification user cannot expect provider keep it fully compliant specifications.
Fortunately, I found a proper way to solve this issue, using custom templates by modify this line


to, hope this solution also can help you.

if {{ source }} and not isinstance({{ source }}, Unset):