openapi-generators / openapi-generators/openapi-python-client

from_dict raises when nullable ref is set to None

未关闭
#679 7 条评论 13 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

🐞bug
主要语言
Python
星标
2k
派生
293
平均合并
34 分钟
30 天内合并 PR
1

描述

Describe the bug
from_dict when we set a nullable field ref to None. This behavior:

  • differs from class constructor which accepts None ref fields
  • differs from others fields : from_dict accept nullable string, object, array, etc to be set to None

To Reproduce
Steps to reproduce the behavior:

  1. take the following swagger:
components:
  schemas:
    MyObject:
      properties:
        myId:
          title: MyId
          type: string
          nullable: true
        myArray:
          title: MyArray
          type: array
          nullable: true
          items:
            type: string
        myString:
          title: MyString
          type: string
          nullable: true
        myObject:
          title: myObject
          type: object
          nullable: true
        myRef:
          $ref: '#/components/schemas/MyChildObject'
          nullable: true
      required:
        - myId
      title: MyObject
      type: object
    MyChildObject:
      properties:
        aField:
          items:
            type: string
          nullable: true
          title: AField
          type: array
      title: MyChildObject
      type: object
info:
  title: myapp
  version: 1.9.4a5
openapi: 3.0.2
paths:
  /myroute:
    get:
      parameters:
      requestBody:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MyChildObject'
          description: Successful Response
  1. generate the client from this swagger
  2. run the following code snippet:
from myapp_client.models import MyObject, MyChildObject
MyObject(my_id="id", my_object=None, my_array=None, my_string=None, my_ref=None) # pass
MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None, "myRef": None}) # raise
MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None}) # pass
  1. See error
In [1]: from myapp_client.models import MyObject, MyChildObject

In [2]: MyObject(my_id="id", my_object=None, my_array=None, my_string=None, my_ref=None)
Out[2]: MyObject(my_id='id', my_array=None, my_string=None, my_object=None, my_ref=None, additional_properties={})

In [3]: MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None, "myRef": None})
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In [3], line 1
----> 1 MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None, "myRef": None})

File ~/CF/amp-client/myapp-client/myapp_client/models/my_object.py:89, in MyObject.from_dict(cls, src_dict)
     87     my_ref = UNSET
     88 else:
---> 89     my_ref = MyChildObject.from_dict(_my_ref)
     91 my_object = cls(
     92     my_id=my_id,
     93     my_array=my_array,
   (...)
     96     my_ref=my_ref,
     97 )
     99 my_object.additional_properties = d

File ~/CF/amp-client/myapp-client/myapp_client/models/my_child_object.py:38, in MyChildObject.from_dict(cls, src_dict)
     36 @classmethod
     37 def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
---> 38     d = src_dict.copy()
     39     a_field = cast(List[str], d.pop("aField", UNSET))
     41     my_child_object = cls(
     42         a_field=a_field,
     43     )

AttributeError: 'NoneType' object has no attribute 'copy'

In [4]: MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None})
Out[4]: MyObject(my_id='id', my_array=None, my_string=None, my_object=None, my_ref=<myapp_client.types.Unset object at 0x7f9f73a4f8e0>, additional_properties={})

In [5]: MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None})

Expected behavior
We expect MyObject.from_dict({"myId":"id", "myObject": None, "myArray": None, "myString": None, "myRef": None}) to run without errors.

Some debug
having a look in the debugger, it raises there::

        _my_ref = d.pop("myRef", UNSET)
        my_ref: Union[Unset, MyChildObject]
        if isinstance(_my_ref, Unset):
            my_ref = UNSET
        else:
->          my_ref = MyChildObject.from_dict(_my_ref)

The code generated should rather look like:

        _my_ref = d.pop("myRef", UNSET)
        my_ref: Union[Unset, None, MyChildObject]
        if _my_ref is None:
            my_ref = None
        elif isinstance(_my_ref, Unset):
            my_ref = UNSET
        else:
            my_ref = MyChildObject.from_dict(_my_ref)

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

首先检查生成的 myapp_client/models/my_object.py 和 my_child_object.py,尤其是报告中所示的 from_dict 路径,然后追踪生成它的生成器代码。比较 nullable 标量的处理方式与 nullable $ref 的处理方式。当 from_dict 接受 myRef 的 None 值而不调用 MyChildObject.from_dict(None),同时保留 UNSET 行为时,即表示完成。

由索引模型根据 Issue 内容生成。

评估

技术栈
openapi, python
领域
tooling
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
停滞
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。