openapi-generators / openapi-generators/openapi-python-client
Multi-body endpoints with required requestBody emit `| Unset` without importing `Unset`
还没有人认领这个 Issue。
- 主要语言
- Python
- 星标
- 2k
- 派生
- 293
- 平均合并
- 34 分钟
- 30 天内合并 PR
- 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 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
根据提供的 openapi.yaml 复现生成的客户端,然后检查 templates/endpoint_macros.py.jinja、parser/properties/protocol.py、parser/openapi.py 和 templates/endpoint_module.py.jinja。跟踪必需的多 body 参数和导入是如何收集的。完成的标准是,生成的必需 request body 没有不必要的 Unset 默认值、没有重复的类型,并且 ruff 不报告未定义的 Unset 名称。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- python
- 领域
- api, tooling
- Issue 类型
- 缺陷
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 活跃度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 72/100