diff --git a/docs/acmg_seqvars_implementation.rst b/docs/acmg_seqvars_implementation.rst index 2d1c351..8eb555d 100644 --- a/docs/acmg_seqvars_implementation.rst +++ b/docs/acmg_seqvars_implementation.rst @@ -4,14 +4,36 @@ Implementation of different ACMG criteria for sequence variants =============================================================== -This section contains the internal building blocks for the +This section contains the internal building blocks for the implementation of the different ACMG criteria for sequence variants. ----------- PS1 and PM5 ----------- -.. automodule:: src.auto_ps1_pm5 +.. automodule:: src.criteria.auto_ps1_pm5 + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + :private-members: + +----------- +PM4 and BP3 +----------- + +.. automodule:: src.criteria.auto_pm4_bp3 + :members: + :inherited-members: + :undoc-members: + :show-inheritance: + :private-members: + +------------------ +BA1, BS1, BS2, PM2 +------------------ + +.. automodule:: src.criteria.auto_ba1_bs1_bs2_pm2 :members: :inherited-members: :undoc-members: diff --git a/src/auto_acmg.py b/src/auto_acmg.py index 808ea0c..c725a3e 100644 --- a/src/auto_acmg.py +++ b/src/auto_acmg.py @@ -4,15 +4,10 @@ from loguru import logger -from src.atuo_ba1_bs1_bs_2_pm2 import AutoBA1BS1BS2PM2 -from src.auto_ps1_pm5 import AutoPS1PM5 from src.core.config import Config -from src.defs.auto_pvs1 import ( - PVS1Prediction, - PVS1PredictionPathMapping, - PVS1PredictionSeqVarPath, - PVS1PredictionStrucVarPath, -) +from src.criteria.auto_criteria import AutoACMGCriteria +from src.defs.auto_acmg import ACMGCriteria +from src.defs.auto_pvs1 import PVS1Prediction, PVS1PredictionPathMapping, PVS1PredictionSeqVarPath from src.defs.exceptions import AutoAcmgBaseException, ParseError from src.defs.genome_builds import GenomeRelease from src.defs.seqvar import SeqVar, SeqVarResolver @@ -91,13 +86,19 @@ def resolve_variant(self) -> SeqVar | StrucVar | None: return None def predict(self): - """Runs the prediction algorithm to assess the PVS1 criteria for the resolved variant. + """ + Predict ACMG criteria for the specified variant. + + This method first resolves the variant, then predicts the PVS1 criterion for sequence + variants and other ACMG criteria. - This method resolves the variant and then, based on the type of variant, predicts its - classification according to the PVS1 criteria. It handles both sequence and structural variants. + Note: + The method can resolve both sequence and structural variants, but currently only + sequence variants are supported for ACMG criteria prediction. Raises: - Exception: Handles general exceptions that may occur during prediction and logs them. + Exception: Specific exceptions are caught and logged, but generic exceptions may be + raised if the prediction fails. """ logger.info("Predicting ACMG criteria for variant: {}", self.variant_name) variant = self.resolve_variant() @@ -116,8 +117,7 @@ def predict(self): self.seqvar_pvs1_prediction_path: PVS1PredictionSeqVarPath = ( PVS1PredictionSeqVarPath.NotSet ) - self.seqvar_ps1: Optional[bool] = None - self.seqvar_pm5: Optional[bool] = None + self.seqvar_criteria: ACMGCriteria = ACMGCriteria() # PVS1 try: @@ -132,96 +132,60 @@ def predict(self): self.seqvar_pvs1_prediction = seqvar_prediction self.seqvar_pvs1_prediction_path = seqvar_prediction_path logger.info( - "PVS1 prediction for {}: {}.\n" "The prediction path is:\n{}.", - self.seqvar.user_repr, + "PVS1 prediction: {}.\nThe prediction path is:\n{}.", self.seqvar_pvs1_prediction.name, PVS1PredictionPathMapping[self.seqvar_pvs1_prediction_path], ) except AutoAcmgBaseException as e: logger.error("Failed to predict PVS1 criteria. Error: {}", e) - # PS1 and PM5 - try: - logger.info("Predicting PS1 and PM5.") - ps1pm5 = AutoPS1PM5(self.seqvar, self.genome_release, config=self.config) - ps1_pm5_prediction = ps1pm5.predict() - if not ps1_pm5_prediction: - logger.error("Failed to predict PS1&PM5 criteria.") - else: - self.seqvar_ps1, self.seqvar_pm5 = ( - ps1_pm5_prediction.PS1, - ps1_pm5_prediction.PM5, - ) - logger.info( - "PS1 prediction for {}: {}.\n" "PM5 prediction: {}.", - self.seqvar.user_repr, - self.seqvar_ps1, - self.seqvar_pm5, - ) - except AutoAcmgBaseException as e: - logger.error("Failed to predict PS1 and PM5 criteria. Error: {}", e) - - # BA1, BS1, BS2, PM2 + # Other criteria try: - logger.info("Predicting BA1, BS1, BS2, and PM2.") - ba1bs1bs2pm2 = AutoBA1BS1BS2PM2( + logger.info("Predicting other ACMG criteria.") + auto_criteria = AutoACMGCriteria( self.seqvar, self.genome_release, config=self.config ) - ba1bs1bs2pm2_prediction = ba1bs1bs2pm2.predict() - if not ba1bs1bs2pm2_prediction: - logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria.") + criteria_preciction = auto_criteria.predict() + if criteria_preciction is None: + logger.error("Failed to predict other ACMG criteria.") else: - self.seqvar_ba1, self.seqvar_bs1, self.seqvar_bs2, self.seqvar_pm2 = ( - ba1bs1bs2pm2_prediction.BA1, - ba1bs1bs2pm2_prediction.BS1, - ba1bs1bs2pm2_prediction.BS2, - ba1bs1bs2pm2_prediction.PM2, - ) - logger.info( - "BA1 prediction for {}: {}.\n" - "BS1 prediction: {}.\n" - "BS2 prediction: {}.\n" - "PM2 prediction: {}.", - self.seqvar.user_repr, - self.seqvar_ba1, - self.seqvar_bs1, - self.seqvar_bs2, - self.seqvar_pm2, - ) + self.seqvar_criteria = criteria_preciction + logger.info("Other ACMG criteria prediction:\n{}", self.seqvar_criteria) except AutoAcmgBaseException as e: - logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria. Error: {}", e) + logger.error("Failed to predict other ACMG criteria. Error: {}", e) elif isinstance(variant, StrucVar): - logger.info("Currently only PVS1 prediction is implemented for structural variants!") - logger.info( - "Classifying ACMG criteria for structural variant {}, genome release: {}.", - variant.user_repr, - self.genome_release.name, - ) - # PVS1 - try: - logger.info("Predicting PVS1.") - self.strucvar: StrucVar = variant - self.strucvar_prediction: PVS1Prediction = PVS1Prediction.NotSet # type: ignore - self.strucvar_prediction_path: PVS1PredictionStrucVarPath = PVS1PredictionStrucVarPath.NotSet # type: ignore - - pvs1 = AutoPVS1(self.strucvar, self.genome_release) - strucvar_prediction, strucvar_prediction_path = pvs1.predict() - if strucvar_prediction is None or strucvar_prediction_path is None: - logger.error("Failed to predict PVS1 criteria.") - return - else: - # Double check if the prediction path is indeed for structural variant - assert isinstance(strucvar_prediction_path, PVS1PredictionStrucVarPath) - self.strucvar_prediction = strucvar_prediction - self.strucvar_prediction_path = strucvar_prediction_path - logger.info( - "PVS1 prediction for {}: {}.\n" "The prediction path is:\n{}.", - self.strucvar.user_repr, - self.strucvar_prediction.name, - PVS1PredictionPathMapping[self.strucvar_prediction_path], - ) - except AutoAcmgBaseException as e: - logger.error("Failed to predict PVS1 criteria. Error: {}", e) - return + logger.info("Classification of structural variants is not implemented yet.") + # logger.info("Currently only PVS1 prediction is implemented for structural variants!") + # logger.info( + # "Classifying ACMG criteria for structural variant {}, genome release: {}.", + # variant.user_repr, + # self.genome_release.name, + # ) + # # PVS1 + # try: + # logger.info("Predicting PVS1.") + # self.strucvar: StrucVar = variant + # self.strucvar_prediction: PVS1Prediction = PVS1Prediction.NotSet # type: ignore + # self.strucvar_prediction_path: PVS1PredictionStrucVarPath = PVS1PredictionStrucVarPath.NotSet # type: ignore + + # pvs1 = AutoPVS1(self.strucvar, self.genome_release) + # strucvar_prediction, strucvar_prediction_path = pvs1.predict() + # if strucvar_prediction is None or strucvar_prediction_path is None: + # logger.error("Failed to predict PVS1 criteria.") + # return + # else: + # # Double check if the prediction path is indeed for structural variant + # assert isinstance(strucvar_prediction_path, PVS1PredictionStrucVarPath) + # self.strucvar_prediction = strucvar_prediction + # self.strucvar_prediction_path = strucvar_prediction_path + # logger.info( + # "PVS1 prediction for {}: {}.\n" "The prediction path is:\n{}.", + # self.strucvar.user_repr, + # self.strucvar_prediction.name, + # PVS1PredictionPathMapping[self.strucvar_prediction_path], + # ) + # except AutoAcmgBaseException as e: + # logger.error("Failed to predict PVS1 criteria. Error: {}", e) + # return logger.info("ACMG criteria prediction completed.") diff --git a/src/atuo_ba1_bs1_bs_2_pm2.py b/src/criteria/auto_ba1_bs1_bs2_pm2.py similarity index 92% rename from src/atuo_ba1_bs1_bs_2_pm2.py rename to src/criteria/auto_ba1_bs1_bs2_pm2.py index 101fa55..68dffea 100644 --- a/src/atuo_ba1_bs1_bs_2_pm2.py +++ b/src/criteria/auto_ba1_bs1_bs2_pm2.py @@ -18,13 +18,18 @@ class AutoBA1BS1BS2PM2: """Predicts BA1, BS1, BS2, PM2 criteria for sequence variants.""" def __init__( - self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None + self, + seqvar: SeqVar, + genome_release: GenomeRelease, + variant_info: VariantResult, + *, + config: Optional[Config] = None, ): #: Configuration to use. self.config = config or Config() - # Attributes to be set self.seqvar = seqvar self.genome_release = genome_release + self.variant_info = variant_info self.annonars_client = AnnonarsClient(api_base_url=self.config.api_base_url_annonars) self.prediction: BA1BS1BS2PM2 | None = None @@ -149,21 +154,16 @@ def predict(self) -> Optional[BA1BS1BS2PM2]: BA1BS1BS2PM2: The prediction result. """ try: - # Get variant information - variant_info = self._get_variant_info(self.seqvar) - if not variant_info: - raise MissingDataError("No variant information retrieved") - # Instantiate the prediction result self.prediction = BA1BS1BS2PM2() # Evaluate each criterion - self.prediction.BA1 = self.evaluate_ba1(self.seqvar, variant_info.result) + self.prediction.BA1 = self.evaluate_ba1(self.seqvar, self.variant_info) self.prediction.BS1 = self.evaluate_bs1( - self.seqvar, variant_info.result, max_credible_freq=0.01 + self.seqvar, self.variant_info, max_credible_freq=0.01 ) # Adjust max_credible_freq as needed - self.prediction.BS2 = self.evaluate_bs2(variant_info.result) - self.prediction.PM2 = self.evaluate_pm2(self.seqvar, variant_info.result) + self.prediction.BS2 = self.evaluate_bs2(self.variant_info) + self.prediction.PM2 = self.evaluate_pm2(self.seqvar, self.variant_info) except AutoAcmgBaseException as e: logger.error("Error occurred during BA1, BS1, BS2, PM5 prediction. Error: {}", e) self.prediction = None diff --git a/src/criteria/auto_criteria.py b/src/criteria/auto_criteria.py new file mode 100644 index 0000000..4f35dbe --- /dev/null +++ b/src/criteria/auto_criteria.py @@ -0,0 +1,97 @@ +from typing import Optional + +from loguru import logger + +from src.api.annonars import AnnonarsClient +from src.core.config import Config +from src.criteria.auto_ba1_bs1_bs2_pm2 import AutoBA1BS1BS2PM2 +from src.criteria.auto_pm4_bp3 import AutoPM4BP3 +from src.criteria.auto_ps1_pm5 import AutoPS1PM5 +from src.defs.annonars_variant import AnnonarsVariantResponse +from src.defs.auto_acmg import ACMGCriteria +from src.defs.exceptions import AutoAcmgBaseException +from src.defs.genome_builds import GenomeRelease +from src.defs.seqvar import SeqVar + + +class AutoACMGCriteria: + """Predict ACMG criteria for sequence variant.""" + + def __init__( + self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None + ): + #: Configuration to use. + self.config = config or Config() + # Attributes to be set + self.seqvar = seqvar + self.genome_release = genome_release + self.annonars_client = AnnonarsClient(api_base_url=self.config.api_base_url_annonars) + self.prediction: Optional[ACMGCriteria] = None + + def _get_variant_info(self, seqvar: SeqVar) -> Optional[AnnonarsVariantResponse]: + """Get variant information from Annonars. + + Returns: + AnnonarsVariantResponse: Annonars response. + """ + try: + logger.debug("Getting variant information for {}.", seqvar) + return self.annonars_client.get_variant_info(seqvar) + except AutoAcmgBaseException as e: + logger.error("Failed to get variant information. Error: {}", e) + return None + + def predict(self) -> Optional[ACMGCriteria]: + """Predict ACMG criteria for sequence variant.""" + self.prediction = ACMGCriteria() + + variant_info = self._get_variant_info(self.seqvar) + if not variant_info: + logger.error("Failed to get variant information for {}.", self.seqvar) + return None + + # PS1 and PM5 + try: + ps1pm5 = AutoPS1PM5( + self.seqvar, self.genome_release, variant_info.result, config=self.config + ) + ps1_pm5_prediction = ps1pm5.predict() + if not ps1_pm5_prediction: + logger.error("Failed to predict PS1&PM5 criteria.") + else: + self.prediction.PS1 = ps1_pm5_prediction.PS1 + self.prediction.PM5 = ps1_pm5_prediction.PM5 + except AutoAcmgBaseException as e: + logger.error("Failed to predict PS1 and PM5 criteria. Error: {}", e) + + # PM4 and BP3 + try: + pm4bp3 = AutoPM4BP3( + self.seqvar, self.genome_release, variant_info.result, config=self.config + ) + pm4_bp3_prediction = pm4bp3.predict() + if not pm4_bp3_prediction: + logger.error("Failed to predict PM4&BP3 criteria.") + else: + self.prediction.PM4 = pm4_bp3_prediction.PM4 + self.prediction.BP3 = pm4_bp3_prediction.BP3 + except AutoAcmgBaseException as e: + logger.error("Failed to predict PM4 and BP3 criteria. Error: {}", e) + + # BA1, BS1, BS2, PM2 + try: + ba1bs1bs2pm2 = AutoBA1BS1BS2PM2( + self.seqvar, self.genome_release, variant_info.result, config=self.config + ) + ba1bs1bs2pm2_prediction = ba1bs1bs2pm2.predict() + if not ba1bs1bs2pm2_prediction: + logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria.") + else: + self.prediction.BA1 = ba1bs1bs2pm2_prediction.BA1 + self.prediction.BS1 = ba1bs1bs2pm2_prediction.BS1 + self.prediction.BS2 = ba1bs1bs2pm2_prediction.BS2 + self.prediction.PM2 = ba1bs1bs2pm2_prediction.PM2 + except AutoAcmgBaseException as e: + logger.error("Failed to predict BA1, BS1, BS2, and PM2 criteria. Error: {}", e) + + return self.prediction diff --git a/src/criteria/auto_pm4_bp3.py b/src/criteria/auto_pm4_bp3.py new file mode 100644 index 0000000..a2b135f --- /dev/null +++ b/src/criteria/auto_pm4_bp3.py @@ -0,0 +1,121 @@ +"""Implementation of PM4 and BP3 rules for sequence variants.""" + +from typing import Optional + +from loguru import logger + +from src.api.annonars import AnnonarsClient +from src.core.config import Config +from src.defs.annonars_variant import AnnonarsVariantResponse, VariantResult +from src.defs.auto_acmg import PM4BP3 +from src.defs.exceptions import AlgorithmError, AutoAcmgBaseException, MissingDataError +from src.defs.genome_builds import GenomeRelease +from src.defs.seqvar import SeqVar + + +class AutoPM4BP3: + """Predicts PM4 and BP3 criteria for sequence variants.""" + + def __init__( + self, + seqvar: SeqVar, + genome_release: GenomeRelease, + variant_info: VariantResult, + *, + config: Optional[Config] = None, + ): + #: Configuration to use. + self.config = config or Config() + self.seqvar = seqvar + self.genome_release = genome_release + self.variant_info = variant_info + self.annonars_client = AnnonarsClient(api_base_url=self.config.api_base_url_annonars) + self.prediction: PM4BP3 | None = None + + def _get_variant_info(self, seqvar: SeqVar) -> Optional[AnnonarsVariantResponse]: + """Get variant information from Annonars. + + Returns: + AnnonarsVariantResponse: Annonars response. + """ + try: + logger.debug("Getting variant information for {}.", seqvar) + return self.annonars_client.get_variant_info(seqvar) + except AutoAcmgBaseException as e: + logger.error("Failed to get variant information. Error: {}", e) + return None + + def _in_repeat_region(self, variant_info: VariantResult) -> bool: + """Check if the variant is in a repeat region. + + Args: + variant_info (VariantResult): The variant information. + + Returns: + bool: True if the variant is in a repeat region, False otherwise. + """ + return False + + def _in_conserved_domain(self, variant_info: VariantResult) -> bool: + """Check if the variant is in a conserved domain. + + Args: + variant_info (VariantResult): The variant information. + + Returns: + bool: True if the variant is in a conserved domain, False otherwise. + """ + return False + + def predict(self) -> Optional[PM4BP3]: + """Predicts PM4 and BP3 criteria for the provided sequence variant. + + Implementation of the rule: + - If the variant is a stop-loss variant, PM4 is True and BP3 is False. + - If the variant is an in-frame deletion/insertion: + - If the variant is not in a repeat region and in a conserved domain, PM4 is True and BP3 is + False. + - If the variant is in a repeat region and not in a conserved domain, PM4 is False and BP3 + is True. + + Note: + Rules: + PM4: Protein length changes due to in-frame deletions/insertions in a non-repeat region + or stop-loss variants. + BP3: In-frame deletions/insertions in a repetitive region without a known function. + + Returns: + PM4BP3: PM4 and BP3 prediction. + """ + try: + # Initialize the prediction result + self.prediction = PM4BP3() + + if not self.variant_info: + raise MissingDataError("No valid primary information.") + + # Stop-loss variants are considered as PM4 + if self.variant_info.cadd and self.variant_info.cadd.ConsDetail == "stop_loss": + self.prediction.PM4 = True + self.prediction.BP3 = False + elif self.variant_info.cadd and self.variant_info.cadd.ConsDetail in [ + "inframe_deletion", + "inframe_insertion", + ]: + if not self._in_repeat_region(self.variant_info) and self._in_conserved_domain( + self.variant_info + ): + self.prediction.PM4 = True + self.prediction.BP3 = False + elif self._in_repeat_region(self.variant_info) and not self._in_conserved_domain( + self.variant_info + ): + self.prediction.PM4 = False + self.prediction.BP3 = True + + except AutoAcmgBaseException as e: + logger.error("Failed to predict PM4 and BP3 criteria. Error: {}", e) + self.prediction = None + + # Return the prediction + return self.prediction diff --git a/src/auto_ps1_pm5.py b/src/criteria/auto_ps1_pm5.py similarity index 91% rename from src/auto_ps1_pm5.py rename to src/criteria/auto_ps1_pm5.py index e678d56..0b47d0e 100644 --- a/src/auto_ps1_pm5.py +++ b/src/criteria/auto_ps1_pm5.py @@ -21,16 +21,22 @@ class AutoPS1PM5: - """Predicts PS1 criteria for sequence variants.""" + """Predicts PS1 and PM5 criteria for sequence variants.""" def __init__( - self, seqvar: SeqVar, genome_release: GenomeRelease, *, config: Optional[Config] = None + self, + seqvar: SeqVar, + genome_release: GenomeRelease, + variant_info: VariantResult, + *, + config: Optional[Config] = None, ): #: Configuration to use. self.config = config or Config() # Attributes to be set self.seqvar = seqvar self.genome_release = genome_release + self.variant_info = variant_info self.annonars_client = AnnonarsClient(api_base_url=self.config.api_base_url_annonars) self.prediction: PS1PM5 | None = None @@ -38,7 +44,7 @@ def _get_variant_info(self, seqvar: SeqVar) -> Optional[AnnonarsVariantResponse] """Get variant information from Annonars. Returns: - dict: Annonars response. + AnnonarsVariantResponse: Annonars response. """ try: logger.debug("Getting variant information for {}.", seqvar) @@ -116,17 +122,16 @@ def predict(self) -> Optional[PS1PM5]: # Initialize the prediction result self.prediction = PS1PM5() - primary_info = self._get_variant_info(self.seqvar) if ( - not primary_info - or not primary_info.result.dbnsfp - or not primary_info.result.dbnsfp.HGVSp_VEP + not self.variant_info + or not self.variant_info.dbnsfp + or not self.variant_info.dbnsfp.HGVSp_VEP ): raise MissingDataError( "No valid primary variant information for PS1/PM5 prediction." ) - primary_aa_change = self._parse_HGVSp(primary_info.result.dbnsfp.HGVSp_VEP) + primary_aa_change = self._parse_HGVSp(self.variant_info.dbnsfp.HGVSp_VEP) if not primary_aa_change: raise AlgorithmError("No valid primary amino acid change for PS1/PM5 prediction.") diff --git a/src/defs/annonars_variant.py b/src/defs/annonars_variant.py index 4403b33..77fc418 100644 --- a/src/defs/annonars_variant.py +++ b/src/defs/annonars_variant.py @@ -17,6 +17,10 @@ class VariantQuery(BaseModel): alternative: str +class Cadd(BaseModel): + ConsDetail: Optional[str] = None + + class GnomadExomes(BaseModel): alleleCounts: List[AlleleCount] @@ -121,7 +125,7 @@ class ClinvarItem(BaseModel): class VariantResult(BaseModel): - cadd: Optional[Any] = None + cadd: Optional[Cadd] = None dbsnp: Optional[Any] = None dbnsfp: Optional[Dbnsfp] = None dbscsnv: Optional[Any] = None diff --git a/src/defs/auto_acmg.py b/src/defs/auto_acmg.py index de5c0d3..422c72c 100644 --- a/src/defs/auto_acmg.py +++ b/src/defs/auto_acmg.py @@ -50,6 +50,37 @@ class AminoAcid(AutoAcmgBaseEnum): Stop = "*" +class ACMGCriteria(BaseModel): + """ACMG criteria prediction. Note: without PVS1.""" + + PS1: bool = False + PS2: bool = False + PS3: bool = False + PS4: bool = False + PM1: bool = False + PM2: bool = False + PM3: bool = False + PM4: bool = False + PM5: bool = False + PM6: bool = False + PP1: bool = False + PP2: bool = False + PP3: bool = False + PP4: bool = False + PP5: bool = False + BA1: bool = False + BS1: bool = False + BS2: bool = False + BS3: bool = False + BS4: bool = False + BP1: bool = False + BP2: bool = False + BP3: bool = False + BP4: bool = False + BP5: bool = False + BP6: bool = False + + class PS1PM5(BaseModel): """PS1 and PM5 criteria prediction.""" @@ -57,6 +88,13 @@ class PS1PM5(BaseModel): PM5: bool = False +class PM4BP3(BaseModel): + """PM4 and BP3 criteria prediction.""" + + PM4: bool = False + BP3: bool = False + + class BA1BS1BS2PM2(BaseModel): """BA1, BS1, BS2, and PM2 criteria prediction.""" diff --git a/tests/criteria/__init__.py b/tests/criteria/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/criteria/test_auto_ba1_bs1_bs2_pm2.py b/tests/criteria/test_auto_ba1_bs1_bs2_pm2.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/criteria/test_auto_pm4_pm5.py b/tests/criteria/test_auto_pm4_pm5.py new file mode 100644 index 0000000..2ae2839 --- /dev/null +++ b/tests/criteria/test_auto_pm4_pm5.py @@ -0,0 +1 @@ +pass diff --git a/tests/test_auto_ps1_pm5.py b/tests/criteria/test_auto_ps1_pm5.py similarity index 81% rename from tests/test_auto_ps1_pm5.py rename to tests/criteria/test_auto_ps1_pm5.py index 9f83b4f..7a33636 100644 --- a/tests/test_auto_ps1_pm5.py +++ b/tests/criteria/test_auto_ps1_pm5.py @@ -3,7 +3,7 @@ import pytest from src.api.annonars import AnnonarsClient -from src.auto_ps1_pm5 import AutoPS1PM5 +from src.criteria.auto_ps1_pm5 import AutoPS1PM5 from src.defs.annonars_variant import AnnonarsVariantResponse from src.defs.auto_acmg import AminoAcid from src.defs.exceptions import AlgorithmError, AutoAcmgBaseException @@ -25,8 +25,8 @@ def variant_info(): @pytest.fixture -def auto_ps1_pm5(seqvar): - return AutoPS1PM5(seqvar, GenomeRelease.GRCh38) +def auto_ps1_pm5(seqvar, variant_info): + return AutoPS1PM5(seqvar, GenomeRelease.GRCh38, variant_info.result) @patch.object(AnnonarsClient, "get_variant_info") @@ -34,28 +34,36 @@ def test_auto_ps1_pm5_get_variant_info(mock_get_variant_info, variant_info, seqv """Test getting variant information.""" mock_get_variant_info.return_value = variant_info - auto_ps1_pm5 = AutoPS1PM5(seqvar=seqvar, genome_release=GenomeRelease.GRCh38) + auto_ps1_pm5 = AutoPS1PM5( + seqvar=seqvar, genome_release=GenomeRelease.GRCh38, variant_info=variant_info.result + ) response = auto_ps1_pm5._get_variant_info(auto_ps1_pm5.seqvar) assert response is not None assert response == variant_info @patch.object(AnnonarsClient, "get_variant_info") -def test_auto_ps1_pm5_get_variant_info_none(mock_get_variant_info, seqvar): +def test_auto_ps1_pm5_get_variant_info_none(mock_get_variant_info, variant_info, seqvar): """Test getting variant information returns None.""" mock_get_variant_info.return_value = None - auto_ps1_pm5 = AutoPS1PM5(seqvar=seqvar, genome_release=GenomeRelease.GRCh38) + auto_ps1_pm5 = AutoPS1PM5( + seqvar=seqvar, genome_release=GenomeRelease.GRCh38, variant_info=variant_info.result + ) response = auto_ps1_pm5._get_variant_info(auto_ps1_pm5.seqvar) assert response is None @patch.object(AnnonarsClient, "get_variant_info") -def test_auto_ps1_pm5_get_variant_info_auto_acmg_exception(mock_get_variant_info, seqvar): +def test_auto_ps1_pm5_get_variant_info_auto_acmg_exception( + mock_get_variant_info, variant_info, seqvar +): """Test getting variant information raises AutoAcmgBaseException.""" mock_get_variant_info.side_effect = AutoAcmgBaseException("An error occurred") - auto_ps1_pm5 = AutoPS1PM5(seqvar=seqvar, genome_release=GenomeRelease.GRCh38) + auto_ps1_pm5 = AutoPS1PM5( + seqvar=seqvar, genome_release=GenomeRelease.GRCh38, variant_info=variant_info.result + ) response = auto_ps1_pm5._get_variant_info(auto_ps1_pm5.seqvar) assert response is None diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_e2e_pvs1.py b/tests/e2e/test_e2e_pvs1.py similarity index 100% rename from tests/test_e2e_pvs1.py rename to tests/e2e/test_e2e_pvs1.py diff --git a/tests/test_auto_acmg.py b/tests/test_auto_acmg.py index 7e64250..8b066c8 100644 --- a/tests/test_auto_acmg.py +++ b/tests/test_auto_acmg.py @@ -4,9 +4,10 @@ from typer.testing import CliRunner from src.auto_acmg import AutoACMG -from src.auto_ps1_pm5 import AutoPS1PM5 from src.core.config import Config -from src.defs.auto_acmg import PS1PM5 +from src.criteria.auto_criteria import AutoACMGCriteria +from src.criteria.auto_ps1_pm5 import AutoPS1PM5 +from src.defs.auto_acmg import PS1PM5, ACMGCriteria from src.defs.exceptions import AutoAcmgBaseException, ParseError from src.defs.genome_builds import GenomeRelease from src.defs.seqvar import SeqVar, SeqVarResolver @@ -66,19 +67,19 @@ def mock_auto_pvs1_failure(monkeypatch): @pytest.fixture -def mock_auto_ps1_pm5_success(monkeypatch): - mock_ps1_pm5 = MagicMock(AutoPS1PM5) - mock_ps1_pm5.predict.return_value = PS1PM5() - monkeypatch.setattr("src.auto_acmg.AutoPS1PM5", lambda *args, **kwargs: mock_ps1_pm5) - return mock_ps1_pm5 +def mock_auto_criteria_success(monkeypatch): + mock_criteria = MagicMock(AutoACMGCriteria) + mock_criteria.predict.return_value = ACMGCriteria() + monkeypatch.setattr("src.auto_acmg.AutoACMGCriteria", lambda *args, **kwargs: mock_criteria) + return mock_criteria @pytest.fixture -def mock_auto_ps1_pm5_failure(monkeypatch): - mock_ps1_pm5 = MagicMock(AutoPS1PM5) - mock_ps1_pm5.predict.side_effect = AutoAcmgBaseException("An error occurred") - monkeypatch.setattr("src.auto_acmg.AutoPS1PM5", lambda *args, **kwargs: mock_ps1_pm5) - return mock_ps1_pm5 +def mock_auto_criteria_failure(monkeypatch): + mock_criteria = MagicMock(AutoACMGCriteria) + mock_criteria.predict.side_effect = AutoAcmgBaseException("An error occurred") + monkeypatch.setattr("src.auto_acmg.AutoACMGCriteria", lambda *args, **kwargs: mock_criteria) + return mock_criteria def test_auto_acmg_resolve_sequence_variant_success( @@ -109,7 +110,7 @@ def test_auto_acmg_resolve_sequence_variant_failure(mock_seqvar_resolver_failure def test_auto_acmg_predict_seqvar_success( mock_seqvar_resolver, mock_auto_pvs1_success, - mock_auto_ps1_pm5_success, + mock_auto_criteria_success, mock_seqvar, config: Config, ): @@ -118,11 +119,11 @@ def test_auto_acmg_predict_seqvar_success( with runner.isolated_filesystem(): auto_acmg.predict() assert mock_auto_pvs1_success.predict.called - assert mock_auto_ps1_pm5_success.predict.called + assert mock_auto_criteria_success.predict.called def test_auto_acmg_predict_seqvar_resolve_failure( - mock_seqvar_resolver_failure, mock_auto_pvs1_failure, config: Config + mock_seqvar_resolver_failure, mock_auto_pvs1_failure, mock_auto_criteria_success, config: Config ): """Test the predict method for a sequence variant with a failure response due to resolve method.""" auto_acmg = AutoACMG("NM_000038.3:c.797G>A", GenomeRelease.GRCh38, config=config) @@ -132,10 +133,10 @@ def test_auto_acmg_predict_seqvar_resolve_failure( assert not mock_auto_pvs1_failure.predict.called -def test_auto_acmg_predict_seqvar_pvs1_failure( +def test_auto_acmg_predict_failure_pvs1( mock_seqvar_resolver, mock_auto_pvs1_failure, - mock_auto_ps1_pm5_success, + mock_auto_criteria_success, mock_seqvar, config: Config, ): @@ -149,10 +150,10 @@ def test_auto_acmg_predict_seqvar_pvs1_failure( assert auto_acmg.seqvar_pvs1_prediction_path == PVS1PredictionSeqVarPath.NotSet -def test_auto_acmg_predict_seqvar_ps1_pm5_failure( +def test_auto_acmg_predict_failure_criteria( mock_seqvar_resolver, mock_auto_pvs1_success, - mock_auto_ps1_pm5_failure, + mock_auto_criteria_failure, mock_seqvar, config: Config, ): @@ -162,7 +163,4 @@ def test_auto_acmg_predict_seqvar_ps1_pm5_failure( auto_acmg.predict() assert mock_seqvar_resolver.resolve_seqvar.called assert mock_auto_pvs1_success.predict.called - assert mock_auto_ps1_pm5_failure.predict.called - assert auto_acmg.seqvar == mock_seqvar - assert auto_acmg.seqvar_ps1 is None - assert auto_acmg.seqvar_pm5 is None + assert mock_auto_criteria_failure.predict.called