From 3d7c58a2b48293bcb318b422b3e657fcc73d6d4c Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Fri, 26 Apr 2024 11:51:38 +0200 Subject: [PATCH 1/6] Revert validate and add another endpoint to retrieve errors from testapi --- CHANGELOG.md | 4 +- preClinVar/main.py | 41 +++++++++--------- tests/test_main.py | 101 +++++++++++++++++++++++++-------------------- 3 files changed, 81 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f95f62..66259e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [unreleased] -### Changed -- Expand the output of validate endpoint to return the link for downloading json file containing evantual submission errors +### Added +- An endpoint `/apitest-status` that returns a response containing a link to the eventual json file with the submissions data errors ## [2.5.2] ### Fixed diff --git a/preClinVar/main.py b/preClinVar/main.py index 80fb147..c37259f 100644 --- a/preClinVar/main.py +++ b/preClinVar/main.py @@ -33,16 +33,32 @@ async def root(): return {"message": f"preClinVar v{VERSION} is up and running!"} +@app.post("/apitest-status") +async def apitest_status(api_key: str = Form(), submission_id: str = Form()) -> JSONResponse: + """Returns the status (validation) of a test submission to the apitest endpoint.""" + + # Create a submission header + header = build_header(api_key) + + apitest_actions_url = f"{VALIDATE_SUBMISSION_URL}/{submission_id}/actions/" + apitest_actions_resp = requests.get(apitest_actions_url, headers=header) + + return JSONResponse( + status_code=apitest_actions_resp.status_code, + content=apitest_actions_resp.json(), + ) + + @app.post("/validate") -async def validate(api_key: str = Form(), json_file: UploadFile = File(...)) -> JSONResponse: - """A proxy to the apitest submission ClinVar API endpoint. Returns the ID of the submission and the data summary report with eventual errors.""" +async def validate(api_key: str = Form(), json_file: UploadFile = File(...)): + """A proxy to the apitest ClinVar API endpoint""" # Create a submission header header = build_header(api_key) # Get json file content as dict: submission_obj = json.load(json_file.file) - # And use it as data in a POST request to API + # And use it in POST request to API data = { "actions": [ { @@ -52,23 +68,10 @@ async def validate(api_key: str = Form(), json_file: UploadFile = File(...)) -> } ] } - apitest_resp = requests.post(VALIDATE_SUBMISSION_URL, data=json.dumps(data), headers=header) - apitest_json: dict = apitest_resp.json() - - # If submission was created, return eventual link for downloading json file with data-specific errors - if apitest_resp.status_code == 201: - - apitest_actions_url = f"{VALIDATE_SUBMISSION_URL}/{apitest_json.get('id')}/actions/" - apitest_actions_resp = requests.get(apitest_actions_url, headers=header) - - return JSONResponse( - status_code=apitest_actions_resp.status_code, - content=apitest_actions_resp.json(), - ) - + resp = requests.post(VALIDATE_SUBMISSION_URL, data=json.dumps(data), headers=header) return JSONResponse( - status_code=apitest_resp.status_code, - content=apitest_json, + status_code=resp.status_code, + content=resp.json(), ) diff --git a/tests/test_main.py b/tests/test_main.py index 8165afa..d8ba78f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -312,56 +312,69 @@ def test_validate_wrong_api_key(): @responses.activate -def test_validate_error(): - """Test the validated API proxy endpoint (with a mocked ClinVar API response)""" +def test_validate(): + """Test the endpoint validate, a proxy to ClinVar apitest, with a mocked ClinVar API response.""" - # GIVEN a mocked POST response from CLinVar apitest endpoint + # GIVEN a json submission file + json_file = {"json_file": open(subm_json_path, "rb")} + + # AND a mocked ClinVar API responses.add( responses.POST, VALIDATE_SUBMISSION_URL, json={"id": DEMO_SUBMISSION_ID}, - status=201, # The ClinVar API returns code 201 when request is successful (created) - ) - - # GIVEN a mocked error response from apitest actions endpoint - actions: list[dict] = [ - { - "id": "SUB14404390-1", - "targetDb": "clinvar-test", - "status": "error", - "updated": "2024-04-26T06:41:04.533900Z", - "responses": [ - { - "status": "error", - "message": { - "severity": "error", - "errorCode": "2", - "text": 'Your ClinVar submission processing status is "Error". Please find the details in the file referenced by actions[0].responses[0].files[0].url.', - }, - "files": [ - { - "url": "https://submit.ncbi.nlm.nih.gov/api/2.0/files/vxgc6vtt/sub14404390-summary-report.json/?format=attachment" - } - ], - "objects": [], - } - ], - } - ] - - responses.add( - responses.GET, - f"{VALIDATE_SUBMISSION_URL}/{DEMO_SUBMISSION_ID}/actions/", - json={"actions": actions}, - status=200, # The ClinVar API returns code 201 when request is successful (created) + status=201, # The ClinVar API returs code 201 when request is successful (created) ) - # GIVEN a json submission file - json_file = {"json_file": open(subm_json_path, "rb")} - response = client.post("/validate", data={"api_key": DEMO_API_KEY}, files=json_file) - # THEN the ClinVar API proxy should return the expected data - assert response.status_code == 200 - assert response.json()["actions"][0]["id"] - assert response.json()["actions"][0]["responses"][0]["files"] + # THEN the ClinVar API proxy should return "success" + assert response.status_code == 201 # Created + assert response.json()["id"] == DEMO_SUBMISSION_ID + + @responses.activate + def test_apitest_status(): + """Test the endpoint that sends GET requests to the apitest actions ClinVar endpoint.""" + + # GIVEN a mocked error response from apitest actions endpoint + actions: list[dict] = [ + { + "id": "SUB14404390-1", + "targetDb": "clinvar-test", + "status": "error", + "updated": "2024-04-26T06:41:04.533900Z", + "responses": [ + { + "status": "error", + "message": { + "severity": "error", + "errorCode": "2", + "text": 'Your ClinVar submission processing status is "Error". Please find the details in the file referenced by actions[0].responses[0].files[0].url.', + }, + "files": [ + { + "url": "https://submit.ncbi.nlm.nih.gov/api/2.0/files/vxgc6vtt/sub14404390-summary-report.json/?format=attachment" + } + ], + "objects": [], + } + ], + } + ] + + responses.add( + responses.GET, + f"{VALIDATE_SUBMISSION_URL}/{DEMO_SUBMISSION_ID}/actions/", + json={"actions": actions}, + status=200, # The ClinVar API returns code 201 when request is successful (created) + ) + + # GIVEN a call to the apitest_status endpoint + response = client.post( + "/validate", data={"api_key": DEMO_API_KEY, "submission_id": DEMO_SUBMISSION_ID} + ) + + # THEN the response should contain the provided actions + assert response.status_code == 200 + assert response.json()["actions"][0]["id"] + assert response.json()["actions"][0]["responses"][0]["files"] From ab870f7faf6d26c5c7f5bf8fd6b0bbb940d6e877 Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Fri, 26 Apr 2024 11:55:17 +0200 Subject: [PATCH 2/6] Fix test --- tests/test_main.py | 91 +++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index d8ba78f..0e4abe6 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -332,49 +332,50 @@ def test_validate(): assert response.status_code == 201 # Created assert response.json()["id"] == DEMO_SUBMISSION_ID - @responses.activate - def test_apitest_status(): - """Test the endpoint that sends GET requests to the apitest actions ClinVar endpoint.""" - - # GIVEN a mocked error response from apitest actions endpoint - actions: list[dict] = [ - { - "id": "SUB14404390-1", - "targetDb": "clinvar-test", - "status": "error", - "updated": "2024-04-26T06:41:04.533900Z", - "responses": [ - { - "status": "error", - "message": { - "severity": "error", - "errorCode": "2", - "text": 'Your ClinVar submission processing status is "Error". Please find the details in the file referenced by actions[0].responses[0].files[0].url.', - }, - "files": [ - { - "url": "https://submit.ncbi.nlm.nih.gov/api/2.0/files/vxgc6vtt/sub14404390-summary-report.json/?format=attachment" - } - ], - "objects": [], - } - ], - } - ] - responses.add( - responses.GET, - f"{VALIDATE_SUBMISSION_URL}/{DEMO_SUBMISSION_ID}/actions/", - json={"actions": actions}, - status=200, # The ClinVar API returns code 201 when request is successful (created) - ) - - # GIVEN a call to the apitest_status endpoint - response = client.post( - "/validate", data={"api_key": DEMO_API_KEY, "submission_id": DEMO_SUBMISSION_ID} - ) - - # THEN the response should contain the provided actions - assert response.status_code == 200 - assert response.json()["actions"][0]["id"] - assert response.json()["actions"][0]["responses"][0]["files"] +@responses.activate +def test_apitest_status(): + """Test the endpoint that sends GET requests to the apitest actions ClinVar endpoint.""" + + # GIVEN a mocked error response from apitest actions endpoint + actions: list[dict] = [ + { + "id": "SUB14404390-1", + "targetDb": "clinvar-test", + "status": "error", + "updated": "2024-04-26T06:41:04.533900Z", + "responses": [ + { + "status": "error", + "message": { + "severity": "error", + "errorCode": "2", + "text": 'Your ClinVar submission processing status is "Error". Please find the details in the file referenced by actions[0].responses[0].files[0].url.', + }, + "files": [ + { + "url": "https://submit.ncbi.nlm.nih.gov/api/2.0/files/vxgc6vtt/sub14404390-summary-report.json/?format=attachment" + } + ], + "objects": [], + } + ], + } + ] + + responses.add( + responses.GET, + f"{VALIDATE_SUBMISSION_URL}/{DEMO_SUBMISSION_ID}/actions/", + json={"actions": actions}, + status=200, # The ClinVar API returns code 201 when request is successful (created) + ) + + # GIVEN a call to the apitest_status endpoint + response = client.post( + "/apitest-status", data={"api_key": DEMO_API_KEY, "submission_id": DEMO_SUBMISSION_ID} + ) + + # THEN the response should contain the provided actions + assert response.status_code == 200 + assert response.json()["actions"][0]["id"] + assert response.json()["actions"][0]["responses"][0]["files"] From 371a89bab95ecb1c05ec57548d72917a7fb79332 Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Fri, 26 Apr 2024 13:02:40 +0200 Subject: [PATCH 3/6] Rename /validate to /apitest --- CHANGELOG.md | 2 ++ README.md | 4 ++-- preClinVar/main.py | 4 ++-- tests/test_main.py | 12 ++++++------ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66259e5..392c223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## [unreleased] ### Added - An endpoint `/apitest-status` that returns a response containing a link to the eventual json file with the submissions data errors +### Changed +- Renamed `/validate` endpoint to `/apitest` ## [2.5.2] ### Fixed diff --git a/README.md b/README.md index ddf7ed5..22de14f 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Transforms csv submission files (Variant.csv and CaseData.csv) into a json submi Proxy endpoint to the ClinVar submissions API (dry-run): https://submit.ncbi.nlm.nih.gov/api/v1/submissions/?dry-run=true. Requires a valid API key and a json file containing a submission object. If the request is valid (and the json submission object is validated) returns a response with code 200 and json body with the message value "success". -### validate +### apitest -Proxy endpoint to the ClinVar validate API endpoint: "https://submit.ncbi.nlm.nih.gov/apitest/v1/submissions". Requires a valid API key and a json file containing a submission object. If the json submission document is valid returns a submission ID which can be used for a real submission. If the json submission document is not validated, the endpoint returns a list of errors which will help fixing the document. +Proxy endpoint to the validation API endpoint: (apitest) "https://submit.ncbi.nlm.nih.gov/apitest/v1/submissions". Requires a valid API key and a json file containing a submission object. If the json submission document is valid returns a submission ID which can be used for a real submission. If the json submission document is not validated, the endpoint returns a list of errors which will help fixing the document. ## Running the application using Docker-compose An example containing a demo setup for the app is included in the docker-compose file. Start the docker-compose demo using this command: diff --git a/preClinVar/main.py b/preClinVar/main.py index c37259f..6324990 100644 --- a/preClinVar/main.py +++ b/preClinVar/main.py @@ -49,8 +49,8 @@ async def apitest_status(api_key: str = Form(), submission_id: str = Form()) -> ) -@app.post("/validate") -async def validate(api_key: str = Form(), json_file: UploadFile = File(...)): +@app.post("/apitest") +async def apitest(api_key: str = Form(), json_file: UploadFile = File(...)): """A proxy to the apitest ClinVar API endpoint""" # Create a submission header header = build_header(api_key) diff --git a/tests/test_main.py b/tests/test_main.py index 0e4abe6..acb37e3 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -290,8 +290,8 @@ def test_dry_run(): @responses.activate -def test_validate_wrong_api_key(): - """Test the validate API proxy endpoint without a valid ClinVar API key""" +def test_apitest_wrong_api_key(): + """Test the apitest API proxy endpoint without a valid ClinVar API key""" # GIVEN a json submission file json_file = {"json_file": open(subm_json_path, "rb")} @@ -304,7 +304,7 @@ def test_validate_wrong_api_key(): status=401, # The ClinVar API returs code 201 when request is successful (created) ) - response = client.post("/validate", data={"api_key": DEMO_API_KEY}, files=json_file) + response = client.post("/apitest", data={"api_key": DEMO_API_KEY}, files=json_file) # THEN the ClinVar API should return "unauthorized" assert response.status_code == 401 # Not authorized @@ -312,8 +312,8 @@ def test_validate_wrong_api_key(): @responses.activate -def test_validate(): - """Test the endpoint validate, a proxy to ClinVar apitest, with a mocked ClinVar API response.""" +def test_apitest(): + """Tests the endpoint apitest, a proxy to ClinVar apitest, with a mocked ClinVar API response.""" # GIVEN a json submission file json_file = {"json_file": open(subm_json_path, "rb")} @@ -326,7 +326,7 @@ def test_validate(): status=201, # The ClinVar API returs code 201 when request is successful (created) ) - response = client.post("/validate", data={"api_key": DEMO_API_KEY}, files=json_file) + response = client.post("/apitest", data={"api_key": DEMO_API_KEY}, files=json_file) # THEN the ClinVar API proxy should return "success" assert response.status_code == 201 # Created From cd512c59b6d143dc6ea2774de6298cee503b8e98 Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Fri, 26 Apr 2024 13:43:23 +0200 Subject: [PATCH 4/6] Add a test --- preClinVar/constants.py | 3 ++- preClinVar/main.py | 18 +++++++++++++++++- tests/test_main.py | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/preClinVar/constants.py b/preClinVar/constants.py index 7670b3d..6677643 100644 --- a/preClinVar/constants.py +++ b/preClinVar/constants.py @@ -1,4 +1,5 @@ -DRY_RUN_SUBMISSION_URL = "https://submit.ncbi.nlm.nih.gov/api/v1/submissions/?dry-run=true" +SUBMISSION_URL = "https://submit.ncbi.nlm.nih.gov/api/v1/submissions" +DRY_RUN_SUBMISSION_URL = f"{SUBMISSION_URL}/?dry-run=true" VALIDATE_SUBMISSION_URL = "https://submit.ncbi.nlm.nih.gov/apitest/v1/submissions" CLNSIG_TERMS = [ diff --git a/preClinVar/main.py b/preClinVar/main.py index 6324990..326bf3b 100644 --- a/preClinVar/main.py +++ b/preClinVar/main.py @@ -10,7 +10,7 @@ from preClinVar.__version__ import VERSION from preClinVar.build import build_header, build_submission -from preClinVar.constants import DRY_RUN_SUBMISSION_URL, VALIDATE_SUBMISSION_URL +from preClinVar.constants import DRY_RUN_SUBMISSION_URL, SUBMISSION_URL, VALIDATE_SUBMISSION_URL from preClinVar.file_parser import csv_lines, file_fields_to_submission, tsv_lines from preClinVar.validate import validate_submission @@ -217,3 +217,19 @@ async def csv_2_json( status_code=400, content={"message": f"Created json file contains validation errors: {valid_results[1]}"}, ) + + +@app.post("/status") +async def status(api_key: str = Form(), submission_id: str = Form()) -> JSONResponse: + """Returns the status (validation) of a submission.""" + + # Create a submission header + header = build_header(api_key) + + actions_url = f"{SUBMISSION_URL}/{submission_id}/actions/" + actions_resp = requests.get(actions_url, headers=header) + + return JSONResponse( + status_code=actions_resp.status_code, + content=actions_resp.json(), + ) diff --git a/tests/test_main.py b/tests/test_main.py index acb37e3..cb0e0b4 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -7,7 +7,7 @@ from fastapi.testclient import TestClient from preClinVar.__version__ import VERSION -from preClinVar.constants import DRY_RUN_SUBMISSION_URL, VALIDATE_SUBMISSION_URL +from preClinVar.constants import DRY_RUN_SUBMISSION_URL, SUBMISSION_URL, VALIDATE_SUBMISSION_URL from preClinVar.demo import ( casedata_old_csv, casedata_old_csv_path, @@ -367,7 +367,7 @@ def test_apitest_status(): responses.GET, f"{VALIDATE_SUBMISSION_URL}/{DEMO_SUBMISSION_ID}/actions/", json={"actions": actions}, - status=200, # The ClinVar API returns code 201 when request is successful (created) + status=200, ) # GIVEN a call to the apitest_status endpoint @@ -379,3 +379,35 @@ def test_apitest_status(): assert response.status_code == 200 assert response.json()["actions"][0]["id"] assert response.json()["actions"][0]["responses"][0]["files"] + + +@responses.activate +def test_status_submitted(): + """Test the status endpoint, proxy to the https://submit.ncbi.nlm.nih.gov/api/v1/submissions/SUBnnnnnn/actions ClinVar endpoint.""" + + # GIVEN a mocked submitted response from ClinVar: + actions: list[dict] = [ + { + "id": f"{DEMO_SUBMISSION_ID}-1", + "responses": [], + "status": "submitted", + "targetDb": "clinvar", + "updated": "2024-04-26T13:39:24.384085Z", + } + ] + + responses.add( + responses.GET, + f"{SUBMISSION_URL}/{DEMO_SUBMISSION_ID}/actions/", + json={"actions": actions}, + status=200, + ) + + # GIVEN a call to the status endpoint + response = client.post( + "/status", data={"api_key": DEMO_API_KEY, "submission_id": DEMO_SUBMISSION_ID} + ) + + # THEN the response should contain the provided actions + assert response.status_code == 200 + assert response.json()["actions"][0]["status"] == "submitted" From 8ec2a325922dfb99d64c7ccd442f5ee1717d3498 Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Fri, 26 Apr 2024 13:47:49 +0200 Subject: [PATCH 5/6] Updated changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 392c223..b826f0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [unreleased] ### Added -- An endpoint `/apitest-status` that returns a response containing a link to the eventual json file with the submissions data errors +- An endpoint `/apitest-status` that returns a response containing the status of a test submission, with link to the eventual json file with the submissions data errors +- An endpoint `/status` that returns a response containing the status of a submission (submitted, processing, processed, error), with eventual error details ### Changed - Renamed `/validate` endpoint to `/apitest` From 3e39ac5092614d18376bb92ea28243c43aa5b3e1 Mon Sep 17 00:00:00 2001 From: Chiara Rasi Date: Fri, 26 Apr 2024 13:48:49 +0200 Subject: [PATCH 6/6] Fix test --- tests/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index cb0e0b4..30c4b79 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -408,6 +408,6 @@ def test_status_submitted(): "/status", data={"api_key": DEMO_API_KEY, "submission_id": DEMO_SUBMISSION_ID} ) - # THEN the response should contain the provided actions + # THEN the response should contain the provided status assert response.status_code == 200 assert response.json()["actions"][0]["status"] == "submitted"