Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect handling of Union types #837

Open
Amwam opened this issue May 4, 2023 · 2 comments
Open

Incorrect handling of Union types #837

Amwam opened this issue May 4, 2023 · 2 comments
Labels

Comments

@Amwam
Copy link

Amwam commented May 4, 2023

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.

@Amwam
Copy link
Author

Amwam commented May 4, 2023

I think i may have found a solution, by adding a custom attribute function

def convert_union_to_spec(self, field, **kwargs):
    ret = {}
    if isinstance(field, marshmallow_dataclass.union_field.Union):
        ret["oneOf"] = [self.field2property(f[1]) for f in field.union_fields]

    return ret


ma_plugin.converter.add_attribute_function(convert_union_to_spec)

Would still appreciate some guidance if this is the right approach. Thanks!

@lafrech
Copy link
Member

lafrech commented Jun 4, 2023

I'm not familiar with dataclass so I can't provide an educated advice.

This custom attribute function looks good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants