Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

authors: add validation endpoint #115

Merged
merged 1 commit into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 119 additions & 5 deletions backoffice/backoffice/workflows/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
extend_schema,
extend_schema_view,
)
from inspire_schemas.errors import SchemaKeyNotFound, SchemaNotFound
from inspire_schemas.utils import get_validation_errors
from opensearch_dsl import TermsFacet
from rest_framework import status, viewsets
from rest_framework.decorators import action
Expand Down Expand Up @@ -107,6 +109,19 @@ def create(self, request, *args, **kwargs):
class AuthorWorkflowViewSet(viewsets.ViewSet):
serializer_class = WorkflowAuthorSerializer

def render_validation_error_response(self, validation_errors):
validation_errors_messages = [
{
"message": error.message,
"path": list(error.path),
}
for error in validation_errors
]
return Response(
{"message": validation_errors_messages},
status=status.HTTP_400_BAD_REQUEST,
)

@extend_schema(
summary="Create/Update an Author",
description="Creates/Updates an author, launches the required airflow dags.",
Expand All @@ -116,10 +131,15 @@ def create(self, request):
logger.info("Creating workflow with data: %s", request.data)
serializer = self.serializer_class(data=request.data)
if serializer.is_valid(raise_exception=True):
workflow = Workflow.objects.create(
data=serializer.validated_data["data"],
workflow_type=serializer.validated_data["workflow_type"],
)
logger.info("Validating data against given schema: %s", request.data)
validation_errors = list(get_validation_errors(request.data.get("data")))
drjova marked this conversation as resolved.
Show resolved Hide resolved
if validation_errors:
return self.render_validation_error_response(validation_errors)
logger.info("Data passed schema validation, creating workflow.")
workflow = Workflow.objects.create(
data=serializer.validated_data["data"],
workflow_type=serializer.validated_data["workflow_type"],
)
logger.info(
"Trigger Airflow DAG: %s for %s",
WORKFLOW_DAGS[workflow.workflow_type].initialize,
Expand Down Expand Up @@ -157,7 +177,7 @@ def partial_update(self, request, pk=None):

@extend_schema(
summary="Accept or Reject Author",
description="Acceps or rejects an author, run associated dags.",
description="Accepts or rejects an author, run associated dags.",
request=AuthorResolutionSerializer,
)
@action(detail=True, methods=["post"])
Expand Down Expand Up @@ -214,6 +234,100 @@ def restart(self, request, pk=None):
workflow.id, workflow.workflow_type, request.data.get("params")
)

@extend_schema(
summary="Validate record",
description="Validate record against given schema in JSON.",
examples=[
OpenApiExample(
name="Valid record",
description="Example of a valid author record submission.",
request_only=True,
value={
"name": {"value": "John, Snow"},
"_collections": ["Authors"],
"$schema": "https://inspirehep.net/schemas/records/authors.json",
},
),
OpenApiExample(
name="Valid record response",
description="The response when the record is valid.",
response_only=True,
status_codes=["200"],
value={"message": "Record is valid."},
),
OpenApiExample(
name="Invalid record",
description="Example of failing schema validation.",
request_only=True,
value={
"name": "",
"affiliations": "CERN",
"$schema": "https://inspirehep.net/schemas/records/authors.json",
"email": "invalid-email-format",
},
),
OpenApiExample(
name="Invalid record response",
description="The response when the record contains validation errors.",
response_only=True,
status_codes=["400"],
value={
"message": [
{
"message": (
"Additional properties are not allowed"
"('affiliations', 'email' were unexpected)"
),
"path": [],
},
{"message": "'' is not of type 'object'", "path": ["name"]},
{
"message": "'_collections' is a required property",
"path": [],
},
]
},
),
OpenApiExample(
name="Schema not found",
description="Example where the schema for validation cannot be found.",
request_only=True,
value={
"name": {"value": "John, Snow"},
"_collections": ["Authors"],
},
),
OpenApiExample(
name="Schema not found response",
description="The response when the schema is not available.",
response_only=True,
status_codes=["400"],
value={"message": 'Unable to find "$schema" key in...'},
),
],
)
@action(detail=False, methods=["post"])
def validate(self, request):
try:
record_data = request.data
validation_errors = list(get_validation_errors(record_data))
if validation_errors:
return self.render_validation_error_response(validation_errors)
except (SchemaNotFound, SchemaKeyNotFound) as e:
return Response(
{"message": str(e)},
status=status.HTTP_400_BAD_REQUEST,
)
except Exception as e:
return Response(
{"message": f"An unexpected error occurred: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return Response(
{"message": "Record is valid."},
status=status.HTTP_200_OK,
)


@extend_schema_view(
list=extend_schema(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
interactions:
- request:
body: '{"dag_run_id": "00000000-0000-0000-0000-000000000000", "conf": {"workflow_id":
"00000000-0000-0000-0000-000000000000", "data": {"test": "test"}}}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '145'
Content-Type:
- application/json
method: POST
uri: http://airflow-webserver:8080/api/v1/dags/author_create_initialization_dag/dagRuns
response:
body:
string: "{\n \"conf\": {\n \"data\": {\n \"test\": \"test\"\n },\n
\ \"workflow_id\": \"00000000-0000-0000-0000-000000000000\"\n },\n \"dag_id\":
\"author_create_initialization_dag\",\n \"dag_run_id\": \"00000000-0000-0000-0000-000000000000\",\n
\ \"data_interval_end\": \"2024-09-20T15:32:57.605497+00:00\",\n \"data_interval_start\":
\"2024-09-20T15:32:57.605497+00:00\",\n \"end_date\": null,\n \"execution_date\":
\"2024-09-20T15:32:57.605497+00:00\",\n \"external_trigger\": true,\n \"last_scheduling_decision\":
null,\n \"logical_date\": \"2024-09-20T15:32:57.605497+00:00\",\n \"note\":
null,\n \"run_type\": \"manual\",\n \"start_date\": null,\n \"state\":
\"queued\"\n}\n"
headers:
Cache-Control:
- no-store
Connection:
- close
Content-Length:
- '621'
Content-Type:
- application/json
Date:
- Fri, 20 Sep 2024 15:32:57 GMT
Server:
- gunicorn
X-Robots-Tag:
- noindex, nofollow
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '0'
method: DELETE
uri: http://airflow-webserver:8080/api/v1/dags/author_create_initialization_dag/dagRuns/00000000-0000-0000-0000-000000000000
response:
body:
string: ''
headers:
Cache-Control:
- no-store
Connection:
- close
Content-Type:
- application/json
Date:
- Fri, 20 Sep 2024 15:32:57 GMT
Server:
- gunicorn
X-Robots-Tag:
- noindex, nofollow
status:
code: 204
message: NO CONTENT
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
interactions:
- request:
body: '{"dag_run_id": "00000000-0000-0000-0000-000000000000", "conf": {"workflow_id":
"00000000-0000-0000-0000-000000000000", "data": {"test": "test"}}}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '145'
Content-Type:
- application/json
method: POST
uri: http://airflow-webserver:8080/api/v1/dags/author_create_initialization_dag/dagRuns
response:
body:
string: "{\n \"conf\": {\n \"data\": {\n \"test\": \"test\"\n },\n
\ \"workflow_id\": \"00000000-0000-0000-0000-000000000000\"\n },\n \"dag_id\":
\"author_create_initialization_dag\",\n \"dag_run_id\": \"00000000-0000-0000-0000-000000000000\",\n
\ \"data_interval_end\": \"2024-09-20T15:32:58.169208+00:00\",\n \"data_interval_start\":
\"2024-09-20T15:32:58.169208+00:00\",\n \"end_date\": null,\n \"execution_date\":
\"2024-09-20T15:32:58.169208+00:00\",\n \"external_trigger\": true,\n \"last_scheduling_decision\":
null,\n \"logical_date\": \"2024-09-20T15:32:58.169208+00:00\",\n \"note\":
null,\n \"run_type\": \"manual\",\n \"start_date\": null,\n \"state\":
\"queued\"\n}\n"
headers:
Cache-Control:
- no-store
Connection:
- close
Content-Length:
- '621'
Content-Type:
- application/json
Date:
- Fri, 20 Sep 2024 15:32:58 GMT
Server:
- gunicorn
X-Robots-Tag:
- noindex, nofollow
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '0'
method: DELETE
uri: http://airflow-webserver:8080/api/v1/dags/author_create_initialization_dag/dagRuns/00000000-0000-0000-0000-000000000000
response:
body:
string: ''
headers:
Cache-Control:
- no-store
Connection:
- close
Content-Type:
- application/json
Date:
- Fri, 20 Sep 2024 15:32:58 GMT
Server:
- gunicorn
X-Robots-Tag:
- noindex, nofollow
status:
code: 204
message: NO CONTENT
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
interactions:
- request:
body: '{"dag_run_id": "00000000-0000-0000-0000-000000000000", "conf": {"workflow_id":
"00000000-0000-0000-0000-000000000000", "data": {"test": "test"}}}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '145'
Content-Type:
- application/json
method: POST
uri: http://airflow-webserver:8080/api/v1/dags/author_create_initialization_dag/dagRuns
response:
body:
string: "{\n \"conf\": {\n \"data\": {\n \"test\": \"test\"\n },\n
\ \"workflow_id\": \"00000000-0000-0000-0000-000000000000\"\n },\n \"dag_id\":
\"author_create_initialization_dag\",\n \"dag_run_id\": \"00000000-0000-0000-0000-000000000000\",\n
\ \"data_interval_end\": \"2024-09-20T15:32:58.727919+00:00\",\n \"data_interval_start\":
\"2024-09-20T15:32:58.727919+00:00\",\n \"end_date\": null,\n \"execution_date\":
\"2024-09-20T15:32:58.727919+00:00\",\n \"external_trigger\": true,\n \"last_scheduling_decision\":
null,\n \"logical_date\": \"2024-09-20T15:32:58.727919+00:00\",\n \"note\":
null,\n \"run_type\": \"manual\",\n \"start_date\": null,\n \"state\":
\"queued\"\n}\n"
headers:
Cache-Control:
- no-store
Connection:
- close
Content-Length:
- '621'
Content-Type:
- application/json
Date:
- Fri, 20 Sep 2024 15:32:58 GMT
Server:
- gunicorn
X-Robots-Tag:
- noindex, nofollow
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '0'
method: DELETE
uri: http://airflow-webserver:8080/api/v1/dags/author_create_initialization_dag/dagRuns/00000000-0000-0000-0000-000000000000
response:
body:
string: ''
headers:
Cache-Control:
- no-store
Connection:
- close
Content-Type:
- application/json
Date:
- Fri, 20 Sep 2024 15:32:58 GMT
Server:
- gunicorn
X-Robots-Tag:
- noindex, nofollow
status:
code: 204
message: NO CONTENT
version: 1
Loading
Loading