Skip to content

Commit

Permalink
drf-yasg -> drf-spectacular
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger committed Feb 4, 2022
1 parent 4d9ca80 commit a1c83ff
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 142 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Or, check out the **[documentation](https://archmonger.github.io/Conreq/)** for
- [Automatic HTTP Compression](https://github.com/friedelwolff/django-compression-middleware)
- [Automatic Cache Busting](https://docs.djangoproject.com/en/3.2/ref/contrib/staticfiles/#django.contrib.staticfiles.storage.ManifestStaticFilesStorage)
- [Automatic Code Performance Graphs](https://github.com/jazzband/django-silk)
- [Automatic API Docs Generation](https://github.com/axnsan12/drf-yasg)
- [Automatic API Docs Generation](https://github.com/tfranzel/drf-spectacular)
- [Automatic SQLite Database Optimization](https://www.sqlite.org/lang_vacuum.html)
- [Automatic Database Backups](https://github.com/django-dbbackup/django-dbbackup)
- [Automatic Health Checks](https://github.com/KristianOellegaard/django-health-check)
Expand Down
75 changes: 0 additions & 75 deletions conreq/_core/api/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
from django.contrib.auth import authenticate, login
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

Expand All @@ -12,78 +9,6 @@
class LocalAuthentication(APIView):
"""Sign in to an account."""

@swagger_auto_schema(
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"username": openapi.Schema(
type=openapi.TYPE_STRING,
),
"password": openapi.Schema(
type=openapi.TYPE_STRING,
),
},
),
responses={
status.HTTP_200_OK: openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"id": openapi.Schema(
type=openapi.TYPE_INTEGER,
),
"last_login": openapi.Schema(
type=openapi.TYPE_STRING,
),
"is_superuser": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
),
"username": openapi.Schema(
type=openapi.TYPE_STRING,
),
"first_name": openapi.Schema(
type=openapi.TYPE_STRING,
),
"last_name": openapi.Schema(
type=openapi.TYPE_STRING,
),
"email": openapi.Schema(
type=openapi.TYPE_STRING,
),
"is_staff": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
),
"is_active": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
),
"date_joined": openapi.Schema(
type=openapi.TYPE_STRING,
),
"groups": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_INTEGER),
),
"user_permissions": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(type=openapi.TYPE_INTEGER),
),
"profile": openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"language": openapi.Schema(
type=openapi.TYPE_STRING,
),
"externally_authenticated": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
),
},
),
"auth_token": openapi.Schema(
type=openapi.TYPE_STRING,
),
},
),
},
)
def post(self, request):
"""Authenticate a session using a `username` and `password`.
Requires CSRF tokens on all further insecure requests (POST, PUT, DELETE, PATCH)."""
Expand Down
31 changes: 4 additions & 27 deletions conreq/_core/debug/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,11 @@ def admin_panel():

def api_docs():
# pylint: disable=import-outside-toplevel
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions

# Django Rest Framework documentation (Swagger and Redoc)
SchemaView = get_schema_view(
openapi.Info(
title="Conreq API Endpoints",
default_version="v1",
description="""
This page displays all endpoints available within this Conreq instance.
Endpoints require an API key either in **HTTP Header (Authorization: Api-Key)** or in the **URL Parameter (apikey)**.
Token Authentication is performed using **HTTP Header (Authorization: Token)**. Session Authentication can alternatively be performed.
""",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="GPL-3.0 License"),
),
public=True,
permission_classes=[permissions.AllowAny],
)
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

register.http.url(
r"^swagger(?P<format>\.json|\.yaml)$", name="swagger_json", use_regex=True
)(SchemaView.without_ui(cache_timeout=0))
register.http.url("swagger", name="swagger_ui")(
SchemaView.with_ui("swagger", cache_timeout=0)
register.http.url("api/schema/", name="schema")(SpectacularAPIView)
register.http.url("api/schema/swagger-ui/", name="swagger_ui")(
SpectacularSwaggerView
)


Expand Down
32 changes: 30 additions & 2 deletions conreq/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,39 @@
# API Settings
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"conreq._core.api.permissions.HasAPIKey",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
SPECTACULAR_SETTINGS = { # Use built-in Swagger UI
"TITLE": "Conreq API Endpoints",
"DESCRIPTION": "Outline for all endpoints available within this Conreq instance.",
"VERSION": CONREQ_VERSION,
"CONTACT": {
"name": "Conreq Team",
"email": "[email protected]",
},
"LICENSE": {"name": "GPL-3.0 License"},
"SWAGGER_UI_DIST": "SIDECAR",
"SWAGGER_UI_FAVICON_HREF": "SIDECAR",
"REDOC_DIST": "SIDECAR",
"SCHEMA_PATH_PREFIX": "/api",
"SCHEMA_PATH_PREFIX_TRIM": True,
"SERVERS": [
{
"url": "{protocol}://{host}:{port}/api",
"variables": {
"host": {"default": "127.0.0.1"},
"port": {"default": 7575},
"protocol": {"enum": ["http", "https"], "default": "http"},
},
}
],
}


Expand Down Expand Up @@ -402,7 +429,8 @@
# Performance analysis tools
INSTALLED_APPS.append("silk")
# API docs generator
INSTALLED_APPS.append("drf_yasg")
INSTALLED_APPS.append("drf_spectacular")
INSTALLED_APPS.append("drf_spectacular_sidecar")

# Automatically delete dangling files
INSTALLED_APPS.append("django_cleanup.apps.CleanupConfig")
Expand Down
2 changes: 1 addition & 1 deletion conreq/templates/app/app_name/api/docs.py-tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ API documentation for {{ verbose_name }}.
See more information in the Conreq API docs.
"""

from drf_yasg import openapi
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, OpenApiResponse
from rest_framework import status

from conreq.utils.api import APIDocs
2 changes: 1 addition & 1 deletion conreq/templates/app/app_name/api/endpoints.py-tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ from rest_framework.response import Response
from rest_framework.views import APIView

from conreq.app import register
from conreq.utils.api import APIDocs, documented_api
from conreq.utils.api import APIDocs, extend_docs

from . import docs
121 changes: 87 additions & 34 deletions conreq/utils/api.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,96 @@
from dataclasses import dataclass, field
from typing import Union
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Union

from drf_yasg import inspectors, openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers
from drf_spectacular.utils import (
OpenApiExample,
OpenApiParameter,
_SerializerType,
extend_schema,
)
from rest_framework.decorators import api_view
from rest_framework.fields import empty
from rest_framework.response import Response


@dataclass
class APIDocs:
"""Information that can be passed into an `swagger_auto_schema`.
See drf-yasg docs for more information."""
"""Information that can be passed into an `documented_api`.
See drf-spectacular docs for more information."""

# TODO: Add docstrings to all of these fields.
method: str = None
methods: list[str] = None
# auto_schema = unset
request_body: Union[
openapi.Schema, openapi.SchemaRef, serializers.Serializer, None
] = None
query_serializer: serializers.Serializer = None
manual_parameters: list[openapi.Parameter] = None
operation_id: str = None
operation_description: str = None
operation_summary: str = None
security: list[dict] = None
deprecated: bool = None
responses: dict[
Union[int, str],
Union[
openapi.Schema, openapi.SchemaRef, openapi.Response, serializers.Serializer
],
] = None
field_inspectors: list[inspectors.FieldInspector] = None
filter_inspectors: list[inspectors.FilterInspector] = None
paginator_inspectors: list[inspectors.PaginatorInspector] = None
tags: list[str] = None
extra_overrides: dict = field(default_factory=dict)
operation_id: Optional[str] = None
"""replaces the auto-generated operation_id. make sure there
are no naming collisions."""

parameters: Optional[List[Union[OpenApiParameter, _SerializerType]]] = None
"""list of additional or replacement parameters added to the
auto-discovered fields."""

responses: Any = empty
"""replaces the discovered Serializer. Takes a variety of
inputs that can be used individually or combined
- ``Serializer`` class
- ``Serializer`` instance (e.g. ``Serializer(many=True)`` for listings)
- basic types or instances of ``OpenApiTypes``
- :class:`.OpenApiResponse` for bundling any of the other choices together with
either a dedicated response description and/or examples.
- :class:`.PolymorphicProxySerializer` for signaling that
the operation may yield data from different serializers depending
on the circumstances.
- ``dict`` with status codes as keys and one of the above as values.
Additionally in this case, it is also possible to provide a raw schema dict
as value.
- ``dict`` with tuples (status_code, media_type) as keys and one of the above
as values. Additionally in this case, it is also possible to provide a raw
schema dict as value."""

request: Any = empty
"""replaces the discovered ``Serializer``. Takes a variety of inputs
- ``Serializer`` class/instance
- basic types or instances of ``OpenApiTypes``
- :class:`.PolymorphicProxySerializer` for signaling that the operation
accepts a set of different types of objects.
- ``dict`` with media_type as keys and one of the above as values. Additionally in
this case, it is also possible to provide a raw schema dict as value."""

auth: Optional[List[str]] = None
"""replace discovered auth with explicit list of auth methods"""

description: Optional[str] = None
"""replaces discovered doc strings"""

summary: Optional[str] = None
"""an optional short summary of the description"""

deprecated: Optional[bool] = None
"""mark operation as deprecated"""

tags: Optional[List[str]] = None
"""override default list of tags"""

filters: Optional[bool] = None
"""ignore list detection and forcefully enable/disable filter discovery"""

exclude: bool = False
"""set True to exclude operation from schema"""

operation: Optional[Dict] = None
"""manually override what auto-discovery would generate. you must
provide a OpenAPI3-compliant dictionary that gets directly translated to YAML."""

methods: Optional[List[str]] = None
"""scope extend_schema to specific methods. matches all by default."""

versions: Optional[List[str]] = None
"""scope extend_schema to specific API version. matches all by default."""

examples: Optional[List[OpenApiExample]] = None
"""attach request/response examples to the operation"""

extensions: Optional[Dict[str, Any]] = None
"""specification extensions, e.g. ``x-badges``, ``x-code-samples``, etc."""


@api_view(["GET"])
Expand All @@ -46,12 +99,12 @@ def stub(request, *args, **kwargs):
return Response({"error": "This endpoint is not yet developed."})


def documented_api(docs: APIDocs):
def extend_docs(docs: APIDocs):
"""A decorator that applies a OpenAPI schema to an API method.
For more information, see the drf-yasg `swagger-auto-schema` docs."""
For more information, see the drf-spectacular `extend_schema` docs."""

def decorator(view_method):
return swagger_auto_schema(**docs.__dict__)(view_method)
return extend_schema(**docs.__dict__)(view_method)

return decorator
2 changes: 1 addition & 1 deletion requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ django-versionfield==1.0.2
djangorestframework==3.13.1
djangorestframework-api-key==2.1.0
docutils==0.18.1
drf-yasg==1.20.0
huey==2.4.3
hypercorn[h3]==0.13.2
markdown==3.3.6
Expand All @@ -34,3 +33,4 @@ sortedcontainers==2.4.0
Twisted[tls,http2]==21.7.0
tzlocal==4.1
whitenoise[brotli]==5.3.0
drf-spectacular[sidecar]==0.21.2

0 comments on commit a1c83ff

Please sign in to comment.