openapi-generators / openapi-generators/openapi-python-client
Multi-body endpoints with required requestBody emit `| Unset` without importing `Unset`
まだ誰も着手していません。
- 主要言語
- Python
- スター
- 2k
- フォーク
- 293
- 平均マージ
- 34分
- マージ済み PR(30日)
- 1
説明
Describe the bug
When an endpoint has a required requestBody with multiple content types, the generator appends | Unset = UNSET to the type annotation - even though the body is required.
Furthermore, Unset is not added to the imports, so the generated file references an undefined name - this causes ruff to report multiple instances of F821 Undefined name 'Unset'.
To Reproduce
Create openapi.yaml with the following spec:
openapi: 3.0.3
info:
title: Unset Bug Demo
version: 1.0.0
paths:
/items/{id}:
put:
operationId: updateItem
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true # <-- must be true
content:
application/json: # <-- must have multiple content types
schema:
$ref: '#/components/schemas/Item'
multipart/form-data: # <-- must have multiple content types
schema:
$ref: '#/components/schemas/Item'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
components:
schemas:
Item:
type: object
properties:
name:
type: string
Generate the client and lint:
# Tested on CachyOS with uv 0.11.21 and the following:
uvx --python 3.14 openapi-python-client==0.29.0 generate --path openapi.yaml --output-path out
uvx ruff==0.15.17 check out
Generated code snippet
In out/unset_bug_demo_client/api/default/update_item.py:
from ...types import UNSET, Response # <-- Unset is not imported
def _get_kwargs(
id: str,
*,
body: Item | Item | Unset = UNSET, # <-- F821: Undefined name 'Unset' (also note how Item is duplicated)
) -> dict[str, Any]:
...
Potential root cause (unverified)
The following was generated by an LLM and has not been verified!
I'm not really familiar with the codebase, but I might take a deeper look in the coming weeks if I find time and turn this into a proper fix/PR...
1. The template always appends
| Unset— even for required bodiesIn
templates/endpoint_macros.py.jinja, the multi-body branch of theargumentsmacro uses an uninitialized variable >body_required:{% elif endpoint.bodies | length > 1 %} body: {%- for body in endpoint.bodies -%}{% set body_required = body_required and body.prop.required %} {{ body.prop.get_type_string(no_optional=True) }} {% if not loop.last %} | {% endif %} {%- endfor -%}{% if not body_required %} | Unset = UNSET{% endif %} , {% endif %}
body_requiredis referenced before it is ever initialized. On the first loop iterationbody_requiredis JinjaUndefined, andUndefined and body.prop.requiredevaluates toUndefined(falsy). So it stays falsy for the whole loop, and after the loop{% if not body_required %}is alwaysTrue. Result:| Unset = UNSETis appended for every multi-body endpoint, regardless of whether the bodies are required. (It should have been seeded toTrue> before the loop so theandchain actually reflects "all bodies required".)2. The
Unsetimport is never added for required bodiesEach member is rendered with
get_type_string(no_optional=True), and the import set is driven byget_imports():# parser/properties/protocol.py def get_imports(self, *, prefix: str) -> set[str]: ... imports = set() if not self.required: imports.add(f"from {prefix}types import UNSET, Unset") return imports
Unset/UNSETare only imported when the property is not required. Endpoint imports are collected from exactly this > method:# parser/openapi.py result.bodies.append(body) result.relative_imports.update(body.prop.get_imports(prefix=models_relative_prefix))And the module's static imports include only
UNSET, neverUnset:# templates/endpoint_module.py.jinja from ...types import Response, UNSET
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
提供された openapi.yaml から生成されたクライアントを再現し、その後 templates/endpoint_macros.py.jinja、parser/properties/protocol.py、parser/openapi.py、templates/endpoint_module.py.jinja を調べます。必須の複数 body 引数と import がどのように収集されるかを追跡します。生成された必須 request body に不要な Unset のデフォルト値や重複した型がなく、ruff が未定義の Unset 名を報告しなければ完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- python
- 領域
- api, tooling
- issue の種類
- バグ
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 活発さ
- 静か
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 72/100