axnsan12 / drf-yasg

Automated generation of real Swagger/OpenAPI 2.0 schemas from Django REST Framework code.
https://drf-yasg.readthedocs.io/en/stable/
Other
3.42k stars 439 forks source link

[Feature Request] Pydantic Support for Request body, Response & Query Parameters #734

Open ghost opened 3 years ago

ghost commented 3 years ago

Hi Guys,

Pydantic by default provide support for schema generation from the Data Model. If someone can create a feature branch for support pydantic objects as well for the above mentioned things - it will be awesome !!

currently it works for drf serializers only -

just a request - might prove helpful for a quite a lot of developers !!

Regards

antonagestam commented 3 years ago

@omfd I'm curious if anyone has experimented with making this work, e.g. using some transformer from pydantic's BaseModel.dict() to drf-yasg's Schema?

JoelLefkowitz commented 3 years ago

@omfd could you provide an example interface for how you think registering pydantic objects could look if this feature is built?

antonagestam commented 3 years ago

@JoelLefkowitz I think it would make sense to use the same facilities as are already in place. Allowing pydantic models in the responses and request_body arguments of swagger_auto_schema, e.g. @swagger_auto_schema(request_body=SomeRequest, responses={200: SomeResponse}). I think that would solve 95% of cases :)

ghost commented 3 years ago

@antonagestam exactly! If you have a look at the Fastapi sample I am mentioning below for reference !

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    return item

The above is a POST request with Body as of type Item and response is also of the same type Item

Below is the screenshot for the swagger generator Fastapi guys have done for themselves where default support is Pydantic Data Models

Screenshot_2021-10-16_14-42-36

If we add support for adding request_body and responses object derived from Pydantic BaseModels it will be pretty good.

Regards

ghost commented 3 years ago

@omfd I'm curious if anyone has experimented with making this work, e.g. using some transformer from pydantic's BaseModel.dict() to drf-yasg's Schema?

@antonagestam I tried to extend SwaggerAutoSchema to accomodate support for the pydantic objects.

But the prime issue is the base class has a strict check of object being of type serializer which makes it incompatible

Even if that base method can be released with support of Union[Serializer, BaseModel]

we can customize it and extend it accordingly as per the need but the above support needs to be there !!

Rest pydantic by default have pretty good method to make it work once that strict check and include pydantic object support

antonagestam commented 3 years ago

@omfd

But the prime issue is the base class has a strict check of object being of type serializer which makes it incompatible

Which check is that? Is it in BaseInspector?

ghost commented 3 years ago
    def get_request_body_parameters(self, consumes):
        """Return the request body parameters for this view. |br|
        This is either:

        -  a list with a single object Parameter with a :class:`.Schema` derived from the request serializer
        -  a list of primitive Parameters parsed as form data

        :param list[str] consumes: a list of accepted MIME types as returned by :meth:`.get_consumes`
        :return: a (potentially empty) list of :class:`.Parameter`\\ s either ``in: body`` or ``in: formData``
        :rtype: list[openapi.Parameter]
        """
        serializer = self.get_request_serializer()
        schema = None
        if serializer is None:
            return []

        if isinstance(serializer, openapi.Schema.OR_REF):
            schema = serializer

        if any(is_form_media_type(encoding) for encoding in consumes):
            if schema is not None:
                raise SwaggerGenerationError("form request body cannot be a Schema")
            return self.get_request_form_parameters(serializer)
        else:
            if schema is None:
                schema = self.get_request_body_schema(serializer)
            return [self.make_body_parameter(schema)] if schema is not None else []

If you follow the method self.get_request_serializer() you will land up at the below method where the check is happening!!!

def force_serializer_instance(serializer):
    """Force `serializer` into a ``Serializer`` instance. If it is not a ``Serializer`` class or instance, raises
    an assertion error.

    :param serializer: serializer class or instance
    :type serializer: serializers.BaseSerializer or type[serializers.BaseSerializer]
    :return: serializer instance
    :rtype: serializers.BaseSerializer
    """
    if inspect.isclass(serializer):
        assert issubclass(serializer, serializers.BaseSerializer), "Serializer required, not %s" % serializer.__name__
        return serializer()

    assert isinstance(serializer, serializers.BaseSerializer), \
        "Serializer class or instance required, not %s" % type(serializer).__name__
    return serializer
ghost commented 3 years ago

@omfd

But the prime issue is the base class has a strict check of object being of type serializer which makes it incompatible

Which check is that? Is it in BaseInspector?

Please look the above comment ---- the idea was to override the get_request_body_parameters method

@antonagestam

ghost commented 3 years ago

@JoelLefkowitz Hi Joel I have added couple of examples in the thread above as well mentioned the code where I am having trouble in extending the SwaggerAutoSchema to accomodate pydantic support for request body and response

Utsavan commented 2 years ago

Has anyone tried using already provided schema() method in pydantic with already provided openapi.Schema class in drf_yasg to generate the docs, something like this --

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

from drf_yasg.openapi import Schema
from drf_yasg.utils import swagger_auto_schema
@swagger_auto_schema(
        responses={
            status.HTTP_200_OK: Schema(**Item.schema())
        }
    )
utapyngo commented 2 years ago

Has anyone tried using already provided schema() method in pydantic with already provided openapi.Schema class in drf_yasg to generate the docs, something like this --

This does not seem to work with nested models. I am getting

drf_yasg.errors.SwaggerValidationError: spec validation failed: 
{'ssv': '("Unresolvable JSON pointer: \'definitions/SubModel\'")'}
jclerman commented 1 year ago

Consider using the approach here (https://github.com/pydantic/pydantic/issues/889#issuecomment-1064688675) to avoid submodels in the JSON schema generated by pydantic.