diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fddd11..9282c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ + ## [2.6] -### 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 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` ## [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/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 80fb147..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 @@ -33,16 +33,32 @@ async def root(): return {"message": f"preClinVar v{VERSION} is up and running!"} -@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.""" +@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("/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) # 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(), ) @@ -214,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 8165afa..30c4b79 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, @@ -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,17 +312,31 @@ 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_apitest(): + """Tests the endpoint apitest, 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) + status=201, # The ClinVar API returs code 201 when request is successful (created) ) + 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 + 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] = [ { @@ -353,15 +367,47 @@ def test_validate_error(): 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 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) + # 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 ClinVar API proxy should return the expected data + # 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_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 status + assert response.status_code == 200 + assert response.json()["actions"][0]["status"] == "submitted"