Multi-body endpoints with required requestBody emit `| Unset` without importing `Unset`

Aperta
#1,451 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Tranquilla
Stack tecnologico
python
Ambito
api, tooling

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.

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 bodies

In templates/endpoint_macros.py.jinja, the multi-body branch of the arguments macro 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_required is referenced before it is ever initialized. On the first loop iteration body_required is Jinja Undefined, and Undefined and body.prop.required evaluates to Undefined (falsy). So it stays falsy for the whole loop, and after the loop {% if not body_required %} is always True. Result: | Unset = UNSET is appended for every multi-body endpoint, regardless of whether the bodies are required. (It should have been seeded to True > before the loop so the and chain actually reflects "all bodies required".)

2. The Unset import is never added for required bodies

Each member is rendered with get_type_string(no_optional=True), and the import set is driven by get_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/UNSET are 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, never Unset:

# templates/endpoint_module.py.jinja
from ...types import Response, UNSET
Lingua principale
Python
Stelle
2k
Fork
293
Merge medio
34m
PR unite (30g)
1

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openapi-generators/openapi-python-client

Tutte le issue di openapi-generators/openapi-python-client

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.