Skip to content

Commit

Permalink
Merge branch 'main' into version_2.6
Browse files Browse the repository at this point in the history
  • Loading branch information
northwestwitch authored Apr 26, 2024
2 parents f804c1c + fd9db2a commit 759b901
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 40 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion preClinVar/constants.py
Original file line number Diff line number Diff line change
@@ -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 = [
Expand Down
61 changes: 40 additions & 21 deletions preClinVar/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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": [
{
Expand All @@ -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(),
)


Expand Down Expand Up @@ -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(),
)
74 changes: 60 additions & 14 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")}
Expand All @@ -304,25 +304,39 @@ 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
assert response.json()["message"] == "No valid API key provided"


@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] = [
{
Expand Down Expand Up @@ -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"

0 comments on commit 759b901

Please sign in to comment.