openapi-generators / openapi-generators/openapi-python-client
Multi-body endpoints with required requestBody emit `| Unset` without importing `Unset`
Ninguém assumiu esta issue ainda.
- Linguagem predominante
- Python
- Estrelas
- 2k
- Forks
- 293
- Merge médio
- 34min
- PRs com merge (30d)
- 1
Descrição
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
Guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Direção de pesquisa
Reproduza o cliente gerado a partir do openapi.yaml fornecido e, em seguida, inspecione templates/endpoint_macros.py.jinja, parser/properties/protocol.py, parser/openapi.py e templates/endpoint_module.py.jinja. Rastreie como os argumentos de body obrigatórios para múltiplos bodies e os imports são coletados. Está concluído quando o request body obrigatório gerado não tiver um valor padrão Unset desnecessário nem um tipo duplicado, e o ruff não relatar nenhum nome Unset não definido.
Escrita pelo modelo de indexação a partir do texto da issue.
Avaliação
- Stack de tecnologia
- python
- Domínio
- api, tooling
- Tipo de issue
- Bug
- Dificuldade
- 3/5
- Tempo estimado
- 1-2 dias
- Status de atividade
- Pouca atividade
- Clareza
- Claramente especificada
- Facilidade para iniciantes
- 72/100