openapi-generators / openapi-generators/openapi-python-client
Multi-body endpoints with required requestBody emit `| Unset` without importing `Unset`
Nessuno ha ancora preso questa issue.
- Lingua principale
- Python
- Stelle
- 2k
- Fork
- 293
- Merge medio
- 34m
- PR unite (30g)
- 1
Descrizione
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
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Direzione di ricerca
Riproduci il client generato dal file openapi.yaml fornito, quindi esamina templates/endpoint_macros.py.jinja, parser/properties/protocol.py, parser/openapi.py e templates/endpoint_module.py.jinja. Traccia come vengono raccolti gli argomenti body obbligatori per più body e gli import. Il lavoro è completato quando il request body obbligatorio generato non ha un valore predefinito Unset non necessario né un tipo duplicato e ruff non segnala alcun nome Unset non definito.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- python
- Ambito
- api, tooling
- Tipo di issue
- Bug
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Stato di attività
- Tranquilla
- Chiarezza
- Specificata chiaramente
- Idoneità per principianti
- 72/100