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

`oneOf` inside `allOf` is ignored during code generation

未關閉
#1,328 0 則留言 1 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

主要語言
Python
星號
2k
分支
293
平均合併
34 分鐘
30 天內合併 PR
1

描述

Describe the bug

When using allOf with a nested oneOf, the code generator only processes the first schema in the allOf list and ignores the oneOf portion. Fields defined in the oneOf schemas are not included as typed attributes in the generated class, but instead end up in additional_properties as untyped data.

OpenAPI Spec File

Click to expand full OpenAPI spec
openapi: 3.0.3
info:
  title: AllOf + OneOf Test
  version: 1.0.0
paths:
  /animals/{animal_id}:
    get:
      operationId: get_animal
      parameters:
        - name: animal_id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Animal'

components:
  schemas:
    Animal:
      type: object
      required:
        - id
        - attributes
      properties:
        id:
          type: string
        attributes:
          allOf:
            - $ref: '#/components/schemas/CommonAttributes'
            - oneOf:
                - $ref: '#/components/schemas/DogAttributes'
                - $ref: '#/components/schemas/CatAttributes'

    CommonAttributes:
      type: object
      properties:
        name:
          type: string
          description: Animal's name
        age:
          type: integer
          description: Animal's age in years

    DogAttributes:
      type: object
      properties:
        breed:
          type: string
          description: Dog breed
        is_good_boy:
          type: boolean
          description: Is this a good boy?

    CatAttributes:
      type: object
      properties:
        color:
          type: string
          description: Cat fur color
        lives_remaining:
          type: integer
          description: Number of lives remaining (out of 9)

Key part of the spec (the problematic schema):

attributes:
  allOf:
    - $ref: '#/components/schemas/CommonAttributes'  # name, age
    - oneOf:
        - $ref: '#/components/schemas/DogAttributes'   # breed, is_good_boy
        - $ref: '#/components/schemas/CatAttributes'   # color, lives_remaining

Generated Code

The generated AnimalAttributes class only includes fields from CommonAttributes:

@_attrs_define
class AnimalAttributes:
    """
    Attributes:
        name (Union[Unset, str]): Animal's name
        age (Union[Unset, int]): Animal's age in years
    """

    name: Union[Unset, str] = UNSET
    age: Union[Unset, int] = UNSET
    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
Click to expand full generated file
from typing import Any, TypeVar, Union

from attrs import define as _attrs_define
from attrs import field as _attrs_field

from ..types import UNSET, Unset

T = TypeVar("T", bound="AnimalAttributes")


@_attrs_define
class AnimalAttributes:
    """
    Attributes:
        name (Union[Unset, str]): Animal's name
        age (Union[Unset, int]): Animal's age in years
    """

    name: Union[Unset, str] = UNSET
    age: Union[Unset, int] = UNSET
    additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)

    def to_dict(self) -> dict[str, Any]:
        name = self.name

        age = self.age

        field_dict: dict[str, Any] = {}
        field_dict.update(self.additional_properties)
        field_dict.update({})
        if name is not UNSET:
            field_dict["name"] = name
        if age is not UNSET:
            field_dict["age"] = age

        return field_dict

    @classmethod
    def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
        d = src_dict.copy()
        name = d.pop("name", UNSET)

        age = d.pop("age", UNSET)

        animal_attributes = cls(
            name=name,
            age=age,
        )

        animal_attributes.additional_properties = d
        return animal_attributes

    @property
    def additional_keys(self) -> list[str]:
        return list(self.additional_properties.keys())

    def __getitem__(self, key: str) -> Any:
        return self.additional_properties[key]

    def __setitem__(self, key: str, value: Any) -> None:
        self.additional_properties[key] = value

    def __delitem__(self, key: str) -> None:
        del self.additional_properties[key]

    def __contains__(self, key: str) -> bool:
        return key in self.additional_properties

Fields from DogAttributes and CatAttributes are not present as typed attributes. When parsing JSON responses containing these fields (e.g., breed, is_good_boy), they end up in additional_properties as untyped data.

Expected Behavior

Since oneOf means "exactly one of", the generator should create separate classes for each variant and use a Union type:

@_attrs_define
class AnimalAttributesWithDog:
    """CommonAttributes + DogAttributes"""
    name: Union[Unset, str] = UNSET
    age: Union[Unset, int] = UNSET
    breed: Union[Unset, str] = UNSET
    is_good_boy: Union[Unset, bool] = UNSET

@_attrs_define
class AnimalAttributesWithCat:
    """CommonAttributes + CatAttributes"""
    name: Union[Unset, str] = UNSET
    age: Union[Unset, int] = UNSET
    color: Union[Unset, str] = UNSET
    lives_remaining: Union[Unset, int] = UNSET

# The actual type used in Animal
AnimalAttributes = Union[AnimalAttributesWithDog, AnimalAttributesWithCat]

Desktop (please complete the following information):

  • OS: Linux (Ubuntu 22.04)
  • Python Version: 3.13.5
  • openapi-python-client version: 0.25.3

Additional context

Current workaround requires accessing fields via additional_properties, which loses type safety:

animal.additional_properties.get('breed')  # type is Any, not str

Note that DogAttributes and CatAttributes are generated as separate model files, but they are not merged into the AnimalAttributes class as expected from the allOf + oneOf combination.

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

使用提供的 OpenAPI 規格重現問題,並檢查產生器對巢狀 allOf 和 oneOf 結構描述的處理方式;報告中未指定原始檔案或測試。完成的標準是,產生的 AnimalAttributes 表示保留共用欄位,並以具型別的屬性公開每個 oneOf 變體,而不是將它們放入 additional_properties。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
devtools
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
停滯
描述清晰度
基本清楚
新手友好度
35/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。