From 758b6ac9a409abf775ac9b66d49114b292dbcb32 Mon Sep 17 00:00:00 2001
From: Daniel
Date: Wed, 13 Nov 2024 09:28:49 +0100
Subject: [PATCH 01/11] Improve API documentation - Implement drf_spectacular -
Add /schema endpoint to build OpenAPI standard - Include UIs to navigate and
test the API
---
rnacentral/apiv1/views.py | 66 ++++++++++++++++++++++++++++---
rnacentral/requirements.txt | 3 ++
rnacentral/rnacentral/settings.py | 9 +++++
rnacentral/rnacentral/urls.py | 6 +++
4 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/rnacentral/apiv1/views.py b/rnacentral/apiv1/views.py
index 5b3ef0ef1..7ca062abf 100644
--- a/rnacentral/apiv1/views.py
+++ b/rnacentral/apiv1/views.py
@@ -181,6 +181,29 @@ def get(self, request, species, chromosome, start, end, format=None):
return Response(features)
+from django.urls import NoReverseMatch
+
+def build_feature_url(request, species, chromosome, start, end):
+ """
+
+ Builds the URL for the feature endpoint based on the provided parameters.
+ This function is a workaround for the reverse() function, which cannot find a compatible regex
+ due to the presence of colons in the pattern.
+
+ Parameters:
+ - request: The Django request object.
+ - species: The species for which the feature URL is being generated.
+ - chromosome: The chromosome number or name for the feature.
+ - start: The start position of the feature.
+ - end: The end position of the feature.
+
+ Returns:
+ - A string representing the absolute URL of the feature endpoint.
+ """
+ base = '/api/v1/feature/region'
+ path = f"{base}/{species}/{chromosome}:{start}-{end}"
+ return request.build_absolute_uri(path)
+
class APIRoot(APIView):
"""
@@ -192,13 +215,46 @@ class APIRoot(APIView):
# the above docstring appears on the API website
permission_classes = (AllowAny,)
- def get(self, request, format=format):
- return Response(
- {
- "rna": reverse("rna-sequences", request=request),
- }
+ def get(self, request, format=None):
+ endpoints = {
+ 'rna': 'rna-sequences',
+ 'accession': ('accession-detail', {'pk': 'URS000075D2D3'}),
+ 'accession_citations': ('accession-citations', {'pk': 'URS000075D2D3'}),
+ #'feature': ('human-genome-coordinates', {'species': 'homo_sapiens',
+ # 'chromosome': 'Y', 'start': 1, 'end': 1000000}),
+ # The above does not work because reverse() cannot find a compatible regex
+ # See build_feature_url()
+
+ 'expert-db-stats': 'expert-db-stats',
+ 'genomes': 'genomes-api',
+ 'genome-browser': ('genome-browser-api', {'species': 'human'}),
+ 'litsumm': 'litsumm',
+ 'md5': ('md5-sequence', {'md5': '6bba097c8c39ed9a0fdf02273ee1c79a'}),
+ }
+
+ result = {}
+
+ result['feature'] = build_feature_url(
+ request,
+ species='human',
+ chromosome='Y',
+ start='1',
+ end='1000000'
)
+ for key, value in endpoints.items():
+ # Debuggin implementation to make sure reverse() works
+ try:
+ if isinstance(value, tuple):
+ result[key] = reverse(value[0], request=request, format=format, kwargs=value[1])
+ else:
+ result[key] = reverse(value, request=request, format=format)
+ except NoReverseMatch as e:
+ # Skip this endpoint if it can't be reversed
+ pass
+
+ return Response(result)
+
class RnaFilter(filters.FilterSet):
"""Declare what fields can be filtered using django-filters"""
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..6ec476c32 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',
)
@@ -271,6 +272,14 @@
"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,
}
# django-debug-toolbar
diff --git a/rnacentral/rnacentral/urls.py b/rnacentral/rnacentral/urls.py
index c477ca4b9..1effc0d5f 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, SpectacularRedocView
urlpatterns = [
# RNAcentral portal
@@ -27,6 +28,11 @@
# 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'),
+ # Optional UI:
+ url(r'^api/schema/swagger-ui/$', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
+ url(r'^api/schema/redoc/$', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]
# robots.txt extras
From 7ba0cb5bffd9d8c5b332becef764d2f41cf8d8b6 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 20 Nov 2024 23:22:04 +0000
Subject: [PATCH 02/11] Add post-processing hook
---
rnacentral/rnacentral/settings.py | 4 +++
.../rnacentral/utils/drf_spectacular.py | 31 +++++++++++++++++++
2 files changed, 35 insertions(+)
create mode 100644 rnacentral/rnacentral/utils/drf_spectacular.py
diff --git a/rnacentral/rnacentral/settings.py b/rnacentral/rnacentral/settings.py
index 6ec476c32..d450fc1e7 100644
--- a/rnacentral/rnacentral/settings.py
+++ b/rnacentral/rnacentral/settings.py
@@ -280,6 +280,10 @@
"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",
+ ],
}
# django-debug-toolbar
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
From fdcf8b955137e2ba5ea3954e8200d30a7d99b322 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 20 Nov 2024 23:23:56 +0000
Subject: [PATCH 03/11] Do not change APIRoot
---
rnacentral/apiv1/views.py | 68 ++++-----------------------------------
1 file changed, 7 insertions(+), 61 deletions(-)
diff --git a/rnacentral/apiv1/views.py b/rnacentral/apiv1/views.py
index 7ca062abf..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,
@@ -181,30 +182,8 @@ def get(self, request, species, chromosome, start, end, format=None):
return Response(features)
-from django.urls import NoReverseMatch
-
-def build_feature_url(request, species, chromosome, start, end):
- """
-
- Builds the URL for the feature endpoint based on the provided parameters.
- This function is a workaround for the reverse() function, which cannot find a compatible regex
- due to the presence of colons in the pattern.
-
- Parameters:
- - request: The Django request object.
- - species: The species for which the feature URL is being generated.
- - chromosome: The chromosome number or name for the feature.
- - start: The start position of the feature.
- - end: The end position of the feature.
-
- Returns:
- - A string representing the absolute URL of the feature endpoint.
- """
- base = '/api/v1/feature/region'
- path = f"{base}/{species}/{chromosome}:{start}-{end}"
- return request.build_absolute_uri(path)
-
+@extend_schema(exclude=True)
class APIRoot(APIView):
"""
This is the root of the RNAcentral API Version 1.
@@ -215,46 +194,13 @@ class APIRoot(APIView):
# the above docstring appears on the API website
permission_classes = (AllowAny,)
- def get(self, request, format=None):
- endpoints = {
- 'rna': 'rna-sequences',
- 'accession': ('accession-detail', {'pk': 'URS000075D2D3'}),
- 'accession_citations': ('accession-citations', {'pk': 'URS000075D2D3'}),
- #'feature': ('human-genome-coordinates', {'species': 'homo_sapiens',
- # 'chromosome': 'Y', 'start': 1, 'end': 1000000}),
- # The above does not work because reverse() cannot find a compatible regex
- # See build_feature_url()
-
- 'expert-db-stats': 'expert-db-stats',
- 'genomes': 'genomes-api',
- 'genome-browser': ('genome-browser-api', {'species': 'human'}),
- 'litsumm': 'litsumm',
- 'md5': ('md5-sequence', {'md5': '6bba097c8c39ed9a0fdf02273ee1c79a'}),
- }
-
- result = {}
-
- result['feature'] = build_feature_url(
- request,
- species='human',
- chromosome='Y',
- start='1',
- end='1000000'
+ def get(self, request, format=format):
+ return Response(
+ {
+ "rna": reverse("rna-sequences", request=request),
+ }
)
- for key, value in endpoints.items():
- # Debuggin implementation to make sure reverse() works
- try:
- if isinstance(value, tuple):
- result[key] = reverse(value[0], request=request, format=format, kwargs=value[1])
- else:
- result[key] = reverse(value, request=request, format=format)
- except NoReverseMatch as e:
- # Skip this endpoint if it can't be reversed
- pass
-
- return Response(result)
-
class RnaFilter(filters.FilterSet):
"""Declare what fields can be filtered using django-filters"""
From f07f2fa5705d3bf024e07894279943125a9b2220 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 20 Nov 2024 23:24:37 +0000
Subject: [PATCH 04/11] Update urls
---
rnacentral/rnacentral/urls.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/rnacentral/rnacentral/urls.py b/rnacentral/rnacentral/urls.py
index 1effc0d5f..162b60653 100644
--- a/rnacentral/rnacentral/urls.py
+++ b/rnacentral/rnacentral/urls.py
@@ -15,7 +15,7 @@
from django.conf.urls import include, url
from django.views.generic import TemplateView
-from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView
+from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
# RNAcentral portal
@@ -29,10 +29,12 @@
url(r"^sequence-search/", include("sequence_search.urls")),
# Django Debug Toolbar
# OpenAPI schema
- url(r'^api/schema/$', SpectacularAPIView.as_view(), name='schema'),
- # Optional UI:
- url(r'^api/schema/swagger-ui/$', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
- url(r'^api/schema/redoc/$', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
+ 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
From 03c9c503d69f32a8697d9db25b3e5ace8a4c6c83 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 20 Nov 2024 23:31:47 +0000
Subject: [PATCH 05/11] Add link to swagger-ui
---
rnacentral/portal/templates/portal/header.html | 1 +
1 file changed, 1 insertion(+)
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 @@
From 33ff95a55478d87338d0c6c551d877fc4758316a Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Thu, 21 Nov 2024 20:11:32 +0000
Subject: [PATCH 06/11] Hide Authorize button
---
rnacentral/rnacentral/settings.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rnacentral/rnacentral/settings.py b/rnacentral/rnacentral/settings.py
index d450fc1e7..7e190a4c8 100644
--- a/rnacentral/rnacentral/settings.py
+++ b/rnacentral/rnacentral/settings.py
@@ -252,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,
From 21a40e4dcacb82a2d823661cf8673ea92a58ebdc Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 27 Nov 2024 11:58:39 +0000
Subject: [PATCH 07/11] Add SCHEMA_PATH_PREFIX
---
rnacentral/rnacentral/settings.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/rnacentral/rnacentral/settings.py b/rnacentral/rnacentral/settings.py
index 7e190a4c8..de4f66eb6 100644
--- a/rnacentral/rnacentral/settings.py
+++ b/rnacentral/rnacentral/settings.py
@@ -285,6 +285,7 @@
"rnacentral.utils.drf_spectacular.remove_path",
"rnacentral.utils.drf_spectacular.fix_path",
],
+ "SCHEMA_PATH_PREFIX": r"/api/v[0-9]",
}
# django-debug-toolbar
From bb6e670ea277fc790756435f365e4e27e8c6f9f5 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 27 Nov 2024 16:45:11 +0000
Subject: [PATCH 08/11] Bug fix
---
rnacentral/apiv1/views.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/rnacentral/apiv1/views.py b/rnacentral/apiv1/views.py
index 56acff89a..5ffe6ba98 100644
--- a/rnacentral/apiv1/views.py
+++ b/rnacentral/apiv1/views.py
@@ -632,7 +632,12 @@ class CitationsView(generics.ListAPIView):
def get_queryset(self):
pk = self.kwargs["pk"]
- return Accession.objects.select_related().get(pk=pk).refs.all()
+ try:
+ citations = Accession.objects.select_related().get(pk=pk).refs.all()
+ except Accession.DoesNotExist:
+ citations = Accession.objects.none()
+
+ return citations
class RnaPublicationsView(generics.ListAPIView):
From f2c755b9eac1c2c2ab8c92bf87569ee584244ad8 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 27 Nov 2024 17:15:23 +0000
Subject: [PATCH 09/11] Do not show some endpoints
---
rnacentral/apiv1/views.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/rnacentral/apiv1/views.py b/rnacentral/apiv1/views.py
index 5ffe6ba98..c1407a111 100644
--- a/rnacentral/apiv1/views.py
+++ b/rnacentral/apiv1/views.py
@@ -110,6 +110,7 @@ def get_database(region):
return providing_databases
+@extend_schema(exclude=True)
class GenomeAnnotations(APIView):
"""
Ensembl-like genome coordinates endpoint.
@@ -459,6 +460,7 @@ def get_queryset(self):
return Rna.objects.get(upi=upi).get_xrefs(taxid=taxid)
+@extend_schema(exclude=True)
class SecondaryStructureSpeciesSpecificList(generics.ListAPIView):
"""
List of secondary structures for a particular RNA sequence in a specific species.
@@ -698,6 +700,7 @@ def _normalize_expert_db_label(expert_db_label):
# return Database.objects.get(expert_db_name).references
+@extend_schema(exclude=True)
class ExpertDatabasesStatsViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
"""
API endpoint with statistics of databases, comprising RNAcentral.
From d41e9b780382436acc6c62764fdd3317d29cc17a Mon Sep 17 00:00:00 2001
From: Blake Sweeney
Date: Mon, 2 Dec 2024 11:16:43 +0000
Subject: [PATCH 10/11] Update SAB membership
Forgot to update Manja and Yann before. Also Yiliang is just an observer
for now.
---
rnacentral/portal/templates/portal/docs/sab.md | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/rnacentral/portal/templates/portal/docs/sab.md b/rnacentral/portal/templates/portal/docs/sab.md
index 88de24ec8..0c1f1ccba 100644
--- a/rnacentral/portal/templates/portal/docs/sab.md
+++ b/rnacentral/portal/templates/portal/docs/sab.md
@@ -6,7 +6,6 @@
[RNAcentral](/) and [Rfam](https://rfam.org) share a Scientific Advisory Board (SAB)
which includes six RNA biologists covering a wide range of expertise from wet lab to computational research.
-- [Yann Ponty](http://www.lix.polytechnique.fr/~ponty/) CNRS École Politechnique, France
- [Lovorka Stojic](https://www.bartscancer.london/staff/dr-lovorka-stojic/) Barts Centre CRUK, UK
- [Alain Laederach](https://ribosnitch.bio.unc.edu/) UNC Chapel Hill, USA
- [Madeline Sherlock](https://profiles.ucdenver.edu/display/20414874) New York Structural Biology Center, USA
@@ -26,4 +25,6 @@ The SAB oversees the progress of the project during annual meetings.
- [Mihaela Zavolan](https://www.biozentrum.unibas.ch/research/researchgroups/overview/unit/zavolan/research-group-mihaela-zavolan/) (University of Basel) *SAB member between 2016-2020*
- [Eric Westhof](http://www-ibmc.u-strasbg.fr/upr9002/westhof/index.html) University of Strasbourg *SAB member between 2015-2021*
- [Michelle Meyer](https://bioinformatics.bc.edu/meyerlab/) Boston College *SAB member between 2016-2021*
-- [Manja Marz](http://www.rna.uni-jena.de/members/manja-marz/) Friedrich Schiller University Jena, Germany
+- [Manja Marz](http://www.rna.uni-jena.de/members/manja-marz/) Friedrich Schiller University Jena, Germany *SAB member between 2017-2022*
+- [Yiliang Ding](https://www.jic.ac.uk/people/yilliang-ding/) John Innes Centre, UK *SAB member between 2021-2023*
+- [Yann Ponty](http://www.lix.polytechnique.fr/~ponty/) CNRS École Politechnique, France *SAB member between 2019-2024*
From 6b0960795b8694c007b1423dd34ef71e0f1265b9 Mon Sep 17 00:00:00 2001
From: carlosribas
Date: Wed, 4 Dec 2024 16:52:54 +0000
Subject: [PATCH 11/11] Change description used in the example
---
rnacentral/portal/templates/portal/r2dt.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rnacentral/portal/templates/portal/r2dt.html b/rnacentral/portal/templates/portal/r2dt.html
index f7f7ee250..07b3234eb 100644
--- a/rnacentral/portal/templates/portal/r2dt.html
+++ b/rnacentral/portal/templates/portal/r2dt.html
@@ -26,7 +26,7 @@ R2DT