oxan / djangorestframework-dataclasses

Dataclasses serializer for Django REST framework
BSD 3-Clause "New" or "Revised" License
429 stars 28 forks source link

With drf-yasg, Dataclass name appears as "Dataclass" string instead of class name #41

Closed akakakakakaa closed 3 years ago

akakakakakaa commented 3 years ago

Environment

python==3.8
Django==3.2.4
djangorestframework==3.12.4
djangorestframework-dataclasses==0.9
dataclasses-json==0.5.4
drf-yasg==1.20.0

Problem

DataclassSerializer shows Dataclass name appears as "Dataclass" string instead of class name.

According to issue #14, it seems that even nested fields have proper names in the openapi format.

#serializer for response
@dataclass
@dataclass_json
class JobInfo:
    name: str
    labels: Dict[str, str]
    status: str
    accelerator: str
    limits: float
    requests: float
    volumes: List[str]
    image: str
    command: str
    startTime: str
    completionTime: str

@dataclass
@dataclass_json
class JobInfos:
    jobNumber: int
    jobs: List[JobInfo]

class JobInfoGetResponseSerializer(DataclassSerializer):
    class Meta:
        dataclass = JobInfos

image

oxan commented 3 years ago

Judging from a quick look at their code, I don't think this is something that can or should be fixed on our side. drf-yasg will need to be adapted for this.

In general for all drf-yasg issues, I think the most productive solution would be for someone to contribute support to drf-yasg -- there's only so much I can do to imitate the behaviour drf-yasg expects.

oxan commented 3 years ago

You can probably workaround this by explicitly declaring the serializer, btw:

class JobInfoSerializer(DataclassSerializer):
    class Meta:
        dataclass = JobInfo

class JobInfoGetResponseSerializer(DataclassSerializer):
    jobs = JobInfoSerializer(many=True)
    class Meta:
        dataclass = JobInfos
dineshtrivedi commented 3 years ago

Hi @oxan

I am sorry if it is not the right place to add this question/information, but it seemed related so I thought of adding it here.

I have a similar issue as described by @akakakakakaa .

I have multiple endpoints using the DataclassSerializer which then generates the wrong information on swagger due to the #definitions generated in the swagger file. The consequence is that every place that DataclassSerializer was used to create a Serializer of a nested dataclass refers to the wrong documentation

image

Code:

@dataclass
class PolicyEntity:
    id: int 
    ...

@dataclass
class Pagination:
    page_count: int
    page_number: int
    total: int

@dataclass
class ListPolicies:
    policies: List[PolicyEntity]
    pagination: Pagination

class ListPoliciesOutput(DataclassSerializer):
    class Meta:
        dataclass = ListPolicies

I made this change to make this change and it kind of fixed my issue:

class MyDataclassSerializer(DataclassSerializer):
    def build_dataclass_field(self, field_name: str, type_info: TypeInfo) -> SerializerFieldDefinition:
        """
        Create fields for dataclasses.
        """
        try:
            field_class = field_utils.lookup_type_in_mapping(self.serializer_field_mapping, type_info.base_type)
        except KeyError:
            # Decide between full name with module or only class name
            # serializer_field_name = type_info.base_type.__name__
            serializer_field_name = f"{type_info.base_type.__module__}.{type_info.base_type.__name__}"
            field_class = type(serializer_field_name, (DataclassSerializer,), {})
            self.serializer_field_mapping[serializer_field_name] = field_class

        field_kwargs = {'dataclass': type_info.base_type,
                        'many': type_info.is_many}

        return field_class, field_kwargs

class ListPoliciesOutput(MyDataclassSerializer):
    class Meta:
        dataclass = ListPolicies

image

I thought of sharing the solution I found here. Maybe @oxan decides that it makes sense for the project or if @akakakakakaa wants to use the solution. The solution above is not fully tested yet. I have made it very recently.

oxan commented 3 years ago

As I said before, this is not something that can be fixed from our side. Either somebody needs to contribute support to drf-yasg, or you need to declare the serializers for your nested classes explicitly.

intgr commented 3 years ago

My project moved from drf-yasg to drf-spectacular some time ago. With drf-spectacular, you can define an extension to get the dataclass name:

class OpenApiDataclassSerializerExtensions(OpenApiSerializerExtension):
    target_class = "rest_framework_dataclasses.serializers.DataclassSerializer"
    match_subclasses = True
    priority = 1

    def get_name(self) -> str:
        return self.target.dataclass_definition.dataclass_type.__name__
dineshtrivedi commented 3 years ago

@oxan I saw your response, however, I have accidentally posted an incomplete comment. I have updated it.

I just wanted to share what I have done so far just in case you want to change the project to a similar solution or in case @akakakakakaa wants to use the solution.

@intgr Thank you for your comment. I have been meaning to try drf-spectacular, I just haven't had time. What was the reason why you changed from drf-yasg to drf-spectacular?

dineshtrivedi commented 3 years ago

Thank for your response @oxan

intgr commented 3 years ago

The immediate reason for switching to drf-spectacular was because we needed some OpenAPI 3 features. drf-yasg only supports OpenAPI 2. I don't think there were any downsides to migrating.

dineshtrivedi commented 3 years ago

Thank you @intgr