diff --git a/rnacentral/apiv1/views.py b/rnacentral/apiv1/views.py index 5b3ef0ef1..56acff89a 100644 --- a/rnacentral/apiv1/views.py +++ b/rnacentral/apiv1/views.py @@ -48,6 +48,7 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 from django_filters import rest_framework as filters +from drf_spectacular.utils import extend_schema from portal.config.expert_databases import expert_dbs from portal.models import ( Accession, @@ -182,6 +183,7 @@ def get(self, request, species, chromosome, start, end, format=None): return Response(features) +@extend_schema(exclude=True) class APIRoot(APIView): """ This is the root of the RNAcentral API Version 1. diff --git a/rnacentral/portal/templates/portal/header.html b/rnacentral/portal/templates/portal/header.html index 390bc105d..5176c600b 100644 --- a/rnacentral/portal/templates/portal/header.html +++ b/rnacentral/portal/templates/portal/header.html @@ -91,6 +91,7 @@
diff --git a/rnacentral/requirements.txt b/rnacentral/requirements.txt index 0873ac174..9465477eb 100644 --- a/rnacentral/requirements.txt +++ b/rnacentral/requirements.txt @@ -37,3 +37,6 @@ whitenoise==5.2.0 # AWS SDK for Python boto3==1.17.54 + +# drf-spectacular for OpenAPI schema generation +drf_spectacular diff --git a/rnacentral/rnacentral/settings.py b/rnacentral/rnacentral/settings.py index 0e26f7a35..de4f66eb6 100644 --- a/rnacentral/rnacentral/settings.py +++ b/rnacentral/rnacentral/settings.py @@ -191,6 +191,7 @@ "rest_framework", "compressor", "markdown_deux", + "drf_spectacular", # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', ) @@ -251,6 +252,7 @@ "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly" ], + "DEFAULT_AUTHENTICATION_CLASSES": [], # API results pagination "DEFAULT_PAGINATION_CLASS": "rnacentral.utils.pagination.Pagination", "PAGE_SIZE": 10, @@ -271,6 +273,19 @@ "rest_framework_yaml.renderers.YAMLRenderer", "rest_framework.renderers.BrowsableAPIRenderer", ), + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +SPECTACULAR_SETTINGS = { + "TITLE": "RNAcentral API", + "DESCRIPTION": "RNAcentral API provides programmatic access to RNAcentral data", + "VERSION": "1.0.0", + "SERVE_INCLUDE_SCHEMA": False, + "POSTPROCESSING_HOOKS": [ + "rnacentral.utils.drf_spectacular.remove_path", + "rnacentral.utils.drf_spectacular.fix_path", + ], + "SCHEMA_PATH_PREFIX": r"/api/v[0-9]", } # django-debug-toolbar diff --git a/rnacentral/rnacentral/urls.py b/rnacentral/rnacentral/urls.py index c477ca4b9..162b60653 100644 --- a/rnacentral/rnacentral/urls.py +++ b/rnacentral/rnacentral/urls.py @@ -15,6 +15,7 @@ from django.conf.urls import include, url from django.views.generic import TemplateView +from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView urlpatterns = [ # RNAcentral portal @@ -27,6 +28,13 @@ # new sequence search url(r"^sequence-search/", include("sequence_search.urls")), # Django Debug Toolbar + # OpenAPI schema + url(r"^api/schema/$", SpectacularAPIView.as_view(), name="schema"), + url( + r"^api/schema/swagger-ui/$", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), ] # robots.txt extras diff --git a/rnacentral/rnacentral/utils/drf_spectacular.py b/rnacentral/rnacentral/utils/drf_spectacular.py new file mode 100644 index 000000000..576bbe2bc --- /dev/null +++ b/rnacentral/rnacentral/utils/drf_spectacular.py @@ -0,0 +1,31 @@ +def remove_path(result, generator, request, public): + """ + Post-processing hook to remove /api/current/ and /sequence-search/ from the schema + """ + paths = result.get("paths", {}) + excluded_prefixes = ("/api/current/", "/sequence-search/") + paths = { + key: value + for key, value in paths.items() + if not key.startswith(excluded_prefixes) + } + result["paths"] = paths + return result + + +def fix_path(result, generator, request, public): + """ + Post-processing hook to normalize paths in the OpenAPI schema: + - Fix RNA path patterns containing [_/] to only show /. + """ + paths = result.get("paths", {}) + updated_paths = {} + + for path, details in paths.items(): + if "[/_]" in path: + path = path.replace("[/_]", "/") + + updated_paths[path] = details + + result["paths"] = updated_paths + return result