Skip to content

Incorrect handling of Union types #837

@Amwam

Description

@Amwam

It seems that the API spec is not properly generated when using Union typehints.
The below snippet is on python 3.10 and 3.11

from typing import Literal

from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from marshmallow_dataclass import dataclass


class BaseItemMixin:
    name: str


@dataclass
class BaseItem(BaseItemMixin):
    pass


@dataclass
class Food(BaseItem):
    label: str
    name: Literal["food"] = "food"


@dataclass
class Drink(BaseItem):
    size: int
    name: Literal["drink"] = "drink"


@dataclass
class MainSchema:
    id: int
    name: str
    items: list[Food | Drink]


spec = APISpec(
    title="API",
    version="1.0.0",
    openapi_version="3.0.2",
    plugins=[(MarshmallowPlugin())],
)


spec.path(
    "/endpoint",
    operations={
        "post": {
            "parameters": [
                {
                    "name": "Args",
                    "schema": MainSchema.Schema(),
                    "in": "body",
                }
            ],
            "responses": {
                "200": {
                    "content": {"application/json": {}},
                    "description": "OK",
                },
            },
        }
    },
)

if __name__ == "__main__":
    print(spec.to_yaml())

The outputted yaml contains the schema for Main as:

components:
  schemas:
    Main:
      type: object
      properties:
        name:
          type: string
        id:
          type: integer
        items:
          type: array
          items: {}
      required:
      - id
      - items
      - name

Items is correctly marked as an array, but the items is not correct. I'd expect oneOf being used here (although i'm not 100%).
The same happens using item: Food | Drink.

I believe the issue is within this library, and not marshmallow_dataclass, as i'm able to deserialise serialise with the schemas without issue

    data = MainSchema(
        id=1,
        name="test",
        items=[
            Food(name="food", label="test"),
            Drink(name="drink", size=1),
        ],
    )
    data_as_dict = MainSchema.Schema().dumps(data)
    loaded_data = MainSchema.Schema().loads(data_as_dict)
    print("original", data)
	print("deserialised", loaded_data)
    print("serialised", data_as_dict)
   
# original MainSchema(id=1, name='test', items=[Food(label='test', name='food'), Drink(size=1, name='drink')])
# deserialised MainSchema(id=1, name='test', items=[Food(label='test', name='food'), Drink(size=1, name='drink')])
# serialised {"id": 1, "items": [{"label": "test", "name": "food"}, {"size": 1, "name": "drink"}], "name": "test"}

Note: I've also tried without the inheritance involved, and the result is the same.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions