Skip to content

Commit

Permalink
Add support for djangorestframework-dataclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
oxan committed Jan 25, 2022
1 parent 61cf160 commit df1c74d
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Features
- `django-filter <https://github.com/carltongibson/django-filter>`_
- `drf-nested-routers <https://github.com/alanjds/drf-nested-routers>`_
- `djangorestframework-recursive <https://github.com/heywbj/django-rest-framework-recursive>`_
- `djangorestframework-dataclasses <https://github.com/oxan/djangorestframework-dataclasses>`_


For more information visit the `documentation <https://drf-spectacular.readthedocs.io>`_.
Expand Down
1 change: 1 addition & 0 deletions drf_spectacular/contrib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
'djangorestframework_camel_case',
'rest_auth',
'rest_polymorphic',
'rest_framework_dataclasses',
'rest_framework_jwt',
'rest_framework_simplejwt',
'django_filters',
Expand Down
24 changes: 24 additions & 0 deletions drf_spectacular/contrib/rest_framework_dataclasses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from drf_spectacular.extensions import OpenApiSerializerExtension
from drf_spectacular.plumbing import get_doc
from drf_spectacular.utils import Direction


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

def get_name(self):
"""Use the dataclass name in the schema, instead of the serializer prefix (which can be just Dataclass)."""
return self.target.dataclass_definition.dataclass_type.__name__

def strip_library_doc(self, schema):
"""Strip the DataclassSerializer library documentation from the schema."""
from rest_framework_dataclasses.serializers import DataclassSerializer
if 'description' in schema and schema['description'] == get_doc(DataclassSerializer):
del schema['description']
return schema

def map_serializer(self, auto_schema, direction: Direction):
""""Generate the schema for a DataclassSerializer."""
schema = auto_schema._map_serializer(self.target, direction, bypass_extensions=True)
return self.strip_library_doc(schema)
3 changes: 2 additions & 1 deletion requirements/optionals.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ django-filter>=2.3.0
psycopg2-binary>=2.7.3.2
drf-nested-routers>=0.93.3
djangorestframework-recursive>=0.1.2
drf-spectacular-sidecar
drf-spectacular-sidecar
djangorestframework-dataclasses>=1.0.0; python_version >= '3.7'
51 changes: 51 additions & 0 deletions tests/contrib/test_rest_framework_dataclasses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import sys
import typing

import pytest
from django.urls import path
from rest_framework.decorators import api_view

from drf_spectacular.utils import extend_schema
from tests import assert_schema, generate_schema


@pytest.mark.contrib('rest_framework_dataclasses')
@pytest.mark.skipif(sys.version_info < (3, 7), reason='dataclass required by package')
def test_rest_framework_dataclasses(no_warnings):
from dataclasses import dataclass

from rest_framework_dataclasses.serializers import DataclassSerializer

@dataclass
class Person:
name: str
length: int

@dataclass
class Group:
name: str
leader: Person
members: typing.List[Person]

class GroupSerializer(DataclassSerializer):
class Meta:
dataclass = Group

@extend_schema(responses=GroupSerializer)
@api_view(['GET'])
def named(request):
pass # pragma: no cover

@extend_schema(responses=DataclassSerializer(dataclass=Person))
@api_view(['GET'])
def anonymous(request):
pass # pragma: no cover

urlpatterns = [
path('named', named),
path('anonymous', anonymous),
]
assert_schema(
generate_schema(None, patterns=urlpatterns),
'tests/contrib/test_rest_framework_dataclasses.yml'
)
72 changes: 72 additions & 0 deletions tests/contrib/test_rest_framework_dataclasses.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
openapi: 3.0.3
info:
title: ''
version: 0.0.0
paths:
/anonymous:
get:
operationId: anonymous_retrieve
tags:
- anonymous
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Person'
description: ''
/named:
get:
operationId: named_retrieve
tags:
- named
security:
- cookieAuth: []
- basicAuth: []
- {}
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Group'
description: ''
components:
schemas:
Group:
type: object
properties:
name:
type: string
leader:
$ref: '#/components/schemas/Person'
members:
type: array
items:
$ref: '#/components/schemas/Person'
required:
- leader
- members
- name
Person:
type: object
properties:
name:
type: string
length:
type: integer
required:
- length
- name
securitySchemes:
basicAuth:
type: http
scheme: basic
cookieAuth:
type: apiKey
in: cookie
name: sessionid
5 changes: 4 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,7 @@ ignore_missing_imports = True
ignore_missing_imports = True

[mypy-rest_framework_recursive.*]
ignore_missing_imports = True
ignore_missing_imports = True

[mypy-rest_framework_dataclasses.*]
ignore_missing_imports = True

0 comments on commit df1c74d

Please sign in to comment.