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

Commit

Permalink
authors: add schema validation endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
PascalEgn committed Sep 24, 2024
1 parent fd58651 commit 9616533
Show file tree
Hide file tree
Showing 8 changed files with 1,260 additions and 195 deletions.
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")))
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

0 comments on commit 9616533

Please sign in to comment.