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