Skip to content
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
18 changes: 10 additions & 8 deletions genie/write_invalid_reasons.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@

def write(
syn: synapseclient.Synapse, center_mapping_synid: str, error_tracker_synid: str
):
"""Write center errors to a file
) -> None:
"""Write center errors to a file called {center}_validation_errors.txt and
save it to the errors folder in the center's folder

Args:
syn (synapseclient.Synapse): Synapse connection
center_mapping_synid (str): Center mapping Synapse id
error_tracker_synid (str): Error tracking Synapse id
center_mapping_synid (str): Center mapping table's synapse id
error_tracker_synid (str): Error tracking table's synapse id

"""
center_mapping_df = extract.get_syntabledf(
Expand All @@ -29,18 +30,19 @@ def write(
center_errors = get_center_invalid_errors(syn, error_tracker_synid)
for center in center_mapping_df["center"]:
logger.info(center)
staging_synid = center_mapping_df["stagingSynId"][
center_errors_report_name = f"{center}_validation_errors.txt"
errors_synid = center_mapping_df["errorsSynId"][
center_mapping_df["center"] == center
][0]
with open(center + "_errors.txt", "w") as errorfile:
with open(center_errors_report_name, "w") as errorfile:
if center not in center_errors:
errorfile.write("No errors!")
else:
errorfile.write(center_errors[center])

ent = synapseclient.File(center + "_errors.txt", parentId=staging_synid)
ent = synapseclient.File(center_errors_report_name, parentId=errors_synid)
syn.store(ent)
os.remove(center + "_errors.txt")
os.remove(center_errors_report_name)


def _combine_center_file_errors(
Expand Down
80 changes: 77 additions & 3 deletions tests/test_write_invalid_reasons.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
"""Test write invalid reasons module"""

import pandas as pd
import pytest
import synapseclient
from unittest import mock
from unittest.mock import patch

from genie import write_invalid_reasons
import pandas as pd
import synapseclient


@pytest.fixture
def mock_centers_mapping():

Check warning on line 12 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a return type hint to this function declaration.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUfxtj_JVKaBwm_a-T&open=AZsUfxtj_JVKaBwm_a-T&pullRequest=629

Check warning on line 12 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a docstring to this function.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUfxtj_JVKaBwm_a-S&open=AZsUfxtj_JVKaBwm_a-S&pullRequest=629
df = pd.DataFrame(
{
"center": ["A", "B"],
"errorsSynId": ["synErrA", "synErrB"],
"stagingSynId": ["synStageA", "synStageB"],
"inputSynId": ["synInputA", "synInputB"],
},
)
df.index = ["0_1", "1_1"]
return df


CENTER_ERRORSDF = pd.DataFrame(
{
Expand Down Expand Up @@ -52,3 +67,62 @@
assert center_invalid == {"SAGE": "errors", "TEST": "errors"}
patch_query.assert_called_once_with("SELECT * FROM syn3333")
assert patch_combine.call_count == 2


def test_write_writes_no_errors_and_correct_errors_and_uses_correct_parent_ids(

Check warning on line 72 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a return type hint to this function declaration.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUgM71r9Xdfh5Yq1th&open=AZsUgM71r9Xdfh5Yq1th&pullRequest=629

Check warning on line 72 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a docstring to this function.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUgM71r9Xdfh5Yq1tf&open=AZsUgM71r9Xdfh5Yq1tf&pullRequest=629
mock_centers_mapping,

Check warning on line 73 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a type hint to this function parameter.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUgM71r9Xdfh5Yq1tg&open=AZsUgM71r9Xdfh5Yq1tg&pullRequest=629
):
syn = mock.Mock()
center_errors = {"A": "A had errors"} # no errors for center B

with mock.patch.object(
write_invalid_reasons.extract,
"get_syntabledf",
return_value=mock_centers_mapping,
), mock.patch.object(
write_invalid_reasons, "get_center_invalid_errors", return_value=center_errors
), mock.patch.object(
write_invalid_reasons.synapseclient, "File"
) as m_file_cls, mock.patch.object(
write_invalid_reasons.os, "remove"
) as m_remove:
# Make open() return a different handle per file so we can assert per-center writes
file_handles = {}

def _open_side_effect(filename, mode):

Check warning on line 92 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a type hint to this function parameter.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUfxtj_JVKaBwm_a-Z&open=AZsUfxtj_JVKaBwm_a-Z&pullRequest=629

Check warning on line 92 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a docstring to this function.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUfxtj_JVKaBwm_a-X&open=AZsUfxtj_JVKaBwm_a-X&pullRequest=629

Check warning on line 92 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a type hint to this function parameter.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUfxtj_JVKaBwm_a-Y&open=AZsUfxtj_JVKaBwm_a-Y&pullRequest=629

Check warning on line 92 in tests/test_write_invalid_reasons.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add a return type hint to this function declaration.

See more on https://sonarcloud.io/project/issues?id=Sage-Bionetworks_Genie&issues=AZsUfxtj_JVKaBwm_a-a&open=AZsUfxtj_JVKaBwm_a-a&pullRequest=629
fh = mock.Mock(name=f"handle_{filename}")
ctx = mock.Mock()
ctx.__enter__ = mock.Mock(return_value=fh)
ctx.__exit__ = mock.Mock(return_value=False)
file_handles[filename] = fh
return ctx

with mock.patch("builtins.open", side_effect=_open_side_effect) as m_open:
# Make File() return different objects so we can assert store calls too (optional)
ent_a = mock.Mock(name="ent_a")
ent_b = mock.Mock(name="ent_b")
m_file_cls.side_effect = [ent_a, ent_b]

write_invalid_reasons.write(
syn=syn,
center_mapping_synid="synCenterMap",
error_tracker_synid="synErrTrack",
)

# assertions: open + writes
m_open.assert_any_call("A_validation_errors.txt", "w")
m_open.assert_any_call("B_validation_errors.txt", "w")

file_handles["A_validation_errors.txt"].write.assert_called_once_with(
"A had errors"
)
file_handles["B_validation_errors.txt"].write.assert_called_once_with("No errors!")

# assertions: correct Synapse folder IDs used (parentId)
m_file_cls.assert_any_call("A_validation_errors.txt", parentId="synErrA")
m_file_cls.assert_any_call("B_validation_errors.txt", parentId="synErrB")

# Synapse store + cleanup
assert syn.store.call_args_list == [mock.call(ent_a), mock.call(ent_b)]
m_remove.assert_any_call("A_validation_errors.txt")
m_remove.assert_any_call("B_validation_errors.txt")
Loading