From ef227128120de7bcec3c68a00238469c7c4c5c8b Mon Sep 17 00:00:00 2001 From: Erwan Rouchet <rouchet@teklia.com> Date: Tue, 28 May 2024 13:49:44 +0000 Subject: [PATCH] Upgrade spectacular to the latest version --- arkindex/documents/api/elements.py | 7 +++---- arkindex/documents/api/export.py | 4 ++-- arkindex/ponos/api.py | 4 ++-- arkindex/process/api.py | 4 +++- arkindex/process/tests/test_processes.py | 11 +++++++---- arkindex/project/openapi/schema.py | 3 ++- arkindex/project/settings.py | 3 +++ arkindex/training/api.py | 4 +--- arkindex/training/serializers.py | 4 ++-- arkindex/users/api.py | 4 ++-- arkindex/users/serializers.py | 8 +++----- requirements.txt | 2 +- 12 files changed, 31 insertions(+), 27 deletions(-) diff --git a/arkindex/documents/api/elements.py b/arkindex/documents/api/elements.py index f08ae65497..2116376656 100644 --- a/arkindex/documents/api/elements.py +++ b/arkindex/documents/api/elements.py @@ -573,14 +573,12 @@ class ElementsListBase(CorpusACLMixin, DestroyModelMixin, ListAPIView): @cached_property def selected_corpus(self): - # Retrieve the corpus and check user access rights only once + # Retrieve the corpus only once corpus_id = self.kwargs.get("corpus") if corpus_id is None: return - corpus = get_object_or_404(Corpus, id=corpus_id) - self.check_corpus_access(corpus) - return corpus + return get_object_or_404(Corpus, id=corpus_id) @property def folder_filter(self): @@ -1041,6 +1039,7 @@ class CorpusElements(ElementsListBase): def get_queryset(self): # Should not be possible due to the URL assert self.selected_corpus, "Missing corpus ID" + self.check_corpus_access(self.selected_corpus) return self.selected_corpus.elements.all() def get_filters(self) -> Q: diff --git a/arkindex/documents/api/export.py b/arkindex/documents/api/export.py index c2f6a52f96..9cec9a8351 100644 --- a/arkindex/documents/api/export.py +++ b/arkindex/documents/api/export.py @@ -6,7 +6,7 @@ from django.shortcuts import get_object_or_404 from django.utils import timezone from django.utils.functional import cached_property from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import permissions, serializers, status +from rest_framework import permissions, status from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.generics import ListCreateAPIView, RetrieveDestroyAPIView from rest_framework.response import Response @@ -78,7 +78,7 @@ class CorpusExportAPIView(ListCreateAPIView): Guest access is required on private corpora. """ ), - responses={302: serializers.Serializer}, + responses={302: None}, ), delete=extend_schema( operation_id="DestroyExport", diff --git a/arkindex/ponos/api.py b/arkindex/ponos/api.py index 368a44055b..a0a43c5ed8 100644 --- a/arkindex/ponos/api.py +++ b/arkindex/ponos/api.py @@ -4,7 +4,7 @@ from textwrap import dedent from django.db import transaction from django.shortcuts import get_object_or_404, redirect from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import serializers, status +from rest_framework import status from rest_framework.authentication import SessionAuthentication, TokenAuthentication from rest_framework.exceptions import NotFound, ValidationError from rest_framework.generics import CreateAPIView, ListCreateAPIView, RetrieveUpdateAPIView, UpdateAPIView @@ -200,12 +200,12 @@ class TaskUpdate(UpdateAPIView): The task must be in a final state to be restarted. """ ), + request=None, responses={201: TaskSerializer}, ), ) class TaskRestart(ProcessACLMixin, CreateAPIView): permission_classes = (IsVerified,) - serializer_class = serializers.Serializer def get_task(self): task = get_object_or_404( diff --git a/arkindex/process/api.py b/arkindex/process/api.py index 154f1273ba..b291def105 100644 --- a/arkindex/process/api.py +++ b/arkindex/process/api.py @@ -1327,7 +1327,7 @@ class WorkerRunList(ProcessACLMixin, ListCreateAPIView): 201: WorkerRunSerializer, 400: OpenApiResponse( response=inline_serializer( - name="Error message with ID", + name="ErrorWithID", fields={ "detail": serializers.ListField(child=serializers.CharField(), required=False), "id": serializers.UUIDField(required=False, help_text="ID of an existing worker run") @@ -1557,6 +1557,7 @@ class WorkerRunDetails(ProcessACLMixin, RetrieveUpdateDestroyAPIView): False: ProcessElementLightSerializer, }, resource_type_field_name="with_image", + many=True, ), tags=["process"] )) @@ -2193,6 +2194,7 @@ class S3ImportCreate(CreateAPIView): post=extend_schema( operation_id="SelectProcessFailures", tags=["process"], + request=None, responses={204: None}, ), ) diff --git a/arkindex/process/tests/test_processes.py b/arkindex/process/tests/test_processes.py index 7140e8a3ef..d0466ec05a 100644 --- a/arkindex/process/tests/test_processes.py +++ b/arkindex/process/tests/test_processes.py @@ -3049,18 +3049,21 @@ class TestProcesses(FixtureAPITestCase): self.elts_process.activity_state = ActivityState.Ready self.elts_process.save() self.client.force_login(self.user) + for state in unfinished_states: with self.subTest(state=state): self.elts_process.tasks.update(state=state) self.assertEqual(self.elts_process.state, state) + response = self.client.post( reverse("api:process-select-failures", kwargs={"pk": str(self.elts_process.id)}) ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual( - response.json(), - {"__all__": ["The process must be finished to select elements with failures."]}, - ) + + self.assertDictEqual( + response.json(), + {"__all__": ["The process must be finished to select elements with failures."]}, + ) def test_select_failed_elts_no_failure(self): self.elts_process.run() diff --git a/arkindex/project/openapi/schema.py b/arkindex/project/openapi/schema.py index b773799137..4cac2e090c 100644 --- a/arkindex/project/openapi/schema.py +++ b/arkindex/project/openapi/schema.py @@ -32,5 +32,6 @@ class AutoSchema(BaseAutoSchema): Spectacular does not include any pagination parameters. """ operation = super().get_operation(*args, **kwargs) - operation["x-paginated"] = self._is_list_view() and self._get_paginator() is not None + if operation is not None: + operation["x-paginated"] = self._is_list_view() and self._get_paginator() is not None return operation diff --git a/arkindex/project/settings.py b/arkindex/project/settings.py index 30feb9bf98..fe463ed963 100644 --- a/arkindex/project/settings.py +++ b/arkindex/project/settings.py @@ -219,6 +219,9 @@ SIMPLE_JWT = { SPECTACULAR_SETTINGS = { "CAMELIZE_NAMES": True, + # Remove the automatically generated `description` that lists the members of all enums, + # which duplicates the enum choices already displayed by ReDoc and does not add any documentation + "ENUM_GENERATE_CHOICE_DESCRIPTION": False, "SCHEMA_PATH_PREFIX": "/api/v1/", "TITLE": "Arkindex API", "CONTACT": { diff --git a/arkindex/training/api.py b/arkindex/training/api.py index 77289fab01..20f10a203f 100644 --- a/arkindex/training/api.py +++ b/arkindex/training/api.py @@ -522,9 +522,7 @@ class ModelCompatibleWorkerManage(CreateAPIView, DestroyAPIView): "Retrieve a download url for a model_version. A secret token is required to have access to the model version." "This endpoint does **not** require to be logged in." ), - responses={ - 302: serializers.Serializer, - }, + responses={302: None}, parameters=[ OpenApiParameter( "token", diff --git a/arkindex/training/serializers.py b/arkindex/training/serializers.py index 2f658f8b73..48ec505718 100644 --- a/arkindex/training/serializers.py +++ b/arkindex/training/serializers.py @@ -171,7 +171,7 @@ class ModelVersionLightSerializer(serializers.ModelSerializer): model = ModelLightSerializer(default=_model_from_context) state = EnumField(ModelVersionState) - configuration = serializers.JSONField(style={"base_template": "textarea.html"}) + configuration = serializers.DictField(style={"base_template": "textarea.html"}) class Meta: model = ModelVersion @@ -190,7 +190,7 @@ class ModelVersionSerializer(serializers.ModelSerializer): allow_null=True, ) description = serializers.CharField(required=False, style={"base_template": "textarea.html"}) - configuration = serializers.JSONField(required=False, decoder=None, encoder=None, style={"base_template": "textarea.html"}) + configuration = serializers.DictField(required=False, style={"base_template": "textarea.html"}) tag = serializers.CharField(allow_null=True, max_length=50, required=False, default=None) state = EnumField(ModelVersionState, read_only=True) s3_url = serializers.SerializerMethodField(read_only=True, help_text="Only returned if the user has **contributor** access to the model, and the model is available.") diff --git a/arkindex/users/api.py b/arkindex/users/api.py index 5bc55e012c..e4bfc44db7 100644 --- a/arkindex/users/api.py +++ b/arkindex/users/api.py @@ -13,7 +13,7 @@ from django_rq.queues import get_queue from django_rq.settings import QUEUES from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view -from rest_framework import serializers, status +from rest_framework import status from rest_framework.exceptions import AuthenticationFailed, NotFound, PermissionDenied, ValidationError from rest_framework.generics import CreateAPIView, ListAPIView, RetrieveDestroyAPIView, RetrieveUpdateDestroyAPIView from rest_framework.response import Response @@ -180,7 +180,7 @@ class UserEmailVerification(APIView): required=True, ) ], - responses={302: serializers.Serializer} + responses={302: None}, ) def get(self, *args, **kwargs): if not all(arg in self.request.GET for arg in ("email", "token")): diff --git a/arkindex/users/serializers.py b/arkindex/users/serializers.py index 8bf49eeea6..d30ba1c369 100644 --- a/arkindex/users/serializers.py +++ b/arkindex/users/serializers.py @@ -161,9 +161,7 @@ class JobSerializer(serializers.Serializer): ended_at = serializers.DateTimeField(read_only=True, allow_null=True) def get_status(self, instance) -> str: - """ - Avoid causing more Redis queries to fetch a job's current status - Note that a job status is part of a JobStatus enum, - but the enum is just a plain object and not an Enum for Py2 compatibility. - """ + # Avoid causing more Redis queries to fetch a job's current status + # Note that a job status is part of a JobStatus enum, + # but the enum is just a plain object and not an Enum for Py2 compatibility. return instance.get_status(refresh=False) diff --git a/requirements.txt b/requirements.txt index dc936a8639..0c6424ce3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ django-rq==2.10.1 djangorestframework==3.13.1 djangorestframework-simplejwt==5.2.2 docker==7.0.0 -drf-spectacular==0.21.2 +drf-spectacular==0.27.2 python-magic==0.4.27 python-memcached==1.59 pytz==2023.3 -- GitLab