OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.42k stars 6.48k forks source link

[PYTHON]: Bug or not bug ? Using allOf for inheritance generate bad imports #18966

Open VBA-N7 opened 3 months ago

VBA-N7 commented 3 months ago

Hi, I'm not sure what i will describe is a bug; I prefer to give you some clues and have a little chat before openning a bug.

Context: I fetch a swagger generated from one of my colleagues who is using Swashbuckle and the inheritance for the Dog schema use the following syntax:

openapi: 3.0.1
info:
  title: title
  version: 1.0.0
paths: {}
components:
  schemas:
    Dog:
      type: object
      allOf:
        - $ref: '#/components/schemas/Animal'
      properties:
        food_eaten:
          type: array
          items:
            oneOf:
              - $ref: '#/components/schemas/Treat'

    Animal:
      type: object

    Treat:
      type: object

When generate python flask server, the imports are kinda broken. docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli:v7.6.0 generate -i /local/test.yaml -g python-flask -o /local/dist

from datetime import date, datetime  # noqa: F401

from typing import List, Dict  # noqa: F401

from openapi_server.models.base_model import Model
from openapi_server.models.one_ofobject import OneOfobject
from openapi_server import util

from openapi_server.models.one_ofobject import OneOfobject  # noqa: E501

class Dog(Model):
    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

    Do not edit the class manually.
    """

    def __init__(self, food_eaten=None):  # noqa: E501
        """Dog - a model defined in OpenAPI

        :param food_eaten: The food_eaten of this Dog.  # noqa: E501
        :type food_eaten: List[OneOfobject]
        """
        self.openapi_types = {
            'food_eaten': List[OneOfobject]
        }

        self.attribute_map = {
            'food_eaten': 'food_eaten'
        }

        self._food_eaten = food_eaten

    @classmethod
    def from_dict(cls, dikt) -> 'Dog':
        """Returns the dict as a model

        :param dikt: A dict.
        :type: dict
        :return: The Dog of this Dog.  # noqa: E501
        :rtype: Dog
        """
        return util.deserialize_model(dikt, cls)

    @property
    def food_eaten(self) -> List[OneOfobject]:
        """Gets the food_eaten of this Dog.

        :return: The food_eaten of this Dog.
        :rtype: List[OneOfobject]
        """
        return self._food_eaten

    @food_eaten.setter
    def food_eaten(self, food_eaten: List[OneOfobject]):
        """Sets the food_eaten of this Dog.

        :param food_eaten: The food_eaten of this Dog.
        :type food_eaten: List[OneOfobject]
        """

        self._food_eaten = food_eaten

I saw on swagger.io that the syntax of allOf should be the following one:

openapi: 3.0.1
info:
  title: title
  version: 1.0.0
paths: {}
components:
  schemas:
    Dog:
      type: object
      allOf:
        - $ref: '#/components/schemas/Animal'
        - type: object
          properties:
            food_eaten:
              type: array
              items:
                oneOf:
                  - $ref: '#/components/schemas/Treat'

    Animal:
      type: object

    Treat:
      type: object

When i'm using this one, the imports are correct (working):

from datetime import date, datetime  # noqa: F401

from typing import List, Dict  # noqa: F401

from openapi_server.models.base_model import Model
from openapi_server.models.dog_all_of_food_eaten import DogAllOfFoodEaten
from openapi_server import util

from openapi_server.models.dog_all_of_food_eaten import DogAllOfFoodEaten  # noqa: E501

class Dog(Model):
    """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

    Do not edit the class manually.
    """

    def __init__(self, food_eaten=None):  # noqa: E501
        """Dog - a model defined in OpenAPI

        :param food_eaten: The food_eaten of this Dog.  # noqa: E501
        :type food_eaten: List[DogAllOfFoodEaten]
        """
        self.openapi_types = {
            'food_eaten': List[DogAllOfFoodEaten]
        }

        self.attribute_map = {
            'food_eaten': 'food_eaten'
        }

        self._food_eaten = food_eaten

    @classmethod
    def from_dict(cls, dikt) -> 'Dog':
        """Returns the dict as a model

        :param dikt: A dict.
        :type: dict
        :return: The Dog of this Dog.  # noqa: E501
        :rtype: Dog
        """
        return util.deserialize_model(dikt, cls)

    @property
    def food_eaten(self) -> List[DogAllOfFoodEaten]:
        """Gets the food_eaten of this Dog.

        :return: The food_eaten of this Dog.
        :rtype: List[DogAllOfFoodEaten]
        """
        return self._food_eaten

    @food_eaten.setter
    def food_eaten(self, food_eaten: List[DogAllOfFoodEaten]):
        """Sets the food_eaten of this Dog.

        :param food_eaten: The food_eaten of this Dog.
        :type food_eaten: List[DogAllOfFoodEaten]
        """

        self._food_eaten = food_eaten
  1. Why the first syntax break the imports ? Is the syntax wrong ?
  2. When im using the second syntax, i dont understand why it is generating a DogAllOfFoodEaten.

Actual:

        self.openapi_types = {
            'food_eaten': List[DogAllOfFoodEaten]
        }

Expected:

        self.openapi_types = {
            'food_eaten': List[Treat | DogBiscuit | SomethingElse]
        }

Thanks for all you have done so far. I really appreciate it. Have a good day.

wing328 commented 3 months ago

https://github.com/openapitools/openapi-generator/blob/master/docs/customization.md#openapi-normalizer

can you please enabled the normalizer rule REFACTOR_ALLOF_WITH_PROPERTIES_ONLY?

            oneOf:
             - $ref: '#/components/schemas/Treat'

this is an inline schema so it will be created as a model separately

VBA-N7 commented 3 months ago

Thanks @wing328 for your help, it works !

Could you please indicate me if it is a normal behavior ?

  1. When im using the second syntax, i dont understand why it is generating a DogAllOfFoodEaten.

Actual:

        self.openapi_types = {
            'food_eaten': List[DogAllOfFoodEaten]
        }

Expected:

        self.openapi_types = {
            'food_eaten': List[Treat | DogBiscuit | SomethingElse]
        }

Thanks !