From fbc2f222885d63cdf270342e71e250e9fdf0ebad Mon Sep 17 00:00:00 2001 From: Mehrnaz Charkhchi Date: Fri, 4 Oct 2024 17:44:19 +0100 Subject: [PATCH 1/3] Add default values to optional fields and vep results model unittest. --- app/tests/models/__init__.py | 0 app/tests/models/test_vcf_results_model.py | 164 +++++++++++++++++++++ app/vep/models/vcf_results_model.py | 19 +-- 3 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 app/tests/models/__init__.py create mode 100644 app/tests/models/test_vcf_results_model.py diff --git a/app/tests/models/__init__.py b/app/tests/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/tests/models/test_vcf_results_model.py b/app/tests/models/test_vcf_results_model.py new file mode 100644 index 0000000..65c4543 --- /dev/null +++ b/app/tests/models/test_vcf_results_model.py @@ -0,0 +1,164 @@ +import unittest +from pydantic import ValidationError + +from vep.models.vcf_results_model import ( + PaginationMetadata, + PredictedIntergenicConsequence, + PredictedTranscriptConsequence, + FeatureType, + Strand, + ReferenceVariantAllele, + Location, + AlternativeVariantAllele, + Variant, + Metadata, + VepResultsResponse, +) + + +class TestOptionalFields(unittest.TestCase): + + def test_predicted_intergenic_consequence(self): + consequence = PredictedIntergenicConsequence( + feature_type=None, + consequences=["intergenic_variant"] + ) + self.assertIsNone(consequence.feature_type) + self.assertEqual(consequence.consequences, ["intergenic_variant"]) + + # Missing consequences should raise a ValidationError + with self.assertRaises(ValidationError): + PredictedIntergenicConsequence(feature_type=None) + + def test_predicted_transcript_consequence(self): + consequence = PredictedTranscriptConsequence( + feature_type=FeatureType.transcript, + stable_id="ENST00000367770.8", + gene_stable_id="ENSG00000157764.13", + gene_symbol=None, + biotype="protein_coding", + is_canonical=True, + consequences=["missense_variant"], + strand=Strand.forward, + ) + self.assertIsNone(consequence.gene_symbol) + + # Valid instance without explicitly setting gene_symbol (default=None) + consequence_no_symbol = PredictedTranscriptConsequence( + feature_type=FeatureType.transcript, + stable_id="ENST00000367770.8", + gene_stable_id="ENSG00000157764.13", + biotype="protein_coding", + is_canonical=True, + consequences=["missense_variant"], + strand=Strand.forward, + ) + self.assertIsNone(consequence_no_symbol.gene_symbol) + + def test_alternative_variant_allele(self): + alternative_allele = AlternativeVariantAllele( + allele_sequence="A", + allele_type="insertion", + representative_population_allele_frequency=None, + predicted_molecular_consequences=[ + PredictedIntergenicConsequence( + feature_type=None, + consequences=["intergenic_variant"], + ) + ], + ) + self.assertIsNone(alternative_allele.representative_population_allele_frequency) + + # Valid instance without explicitly setting representative_population_allele_frequency (default=None) + alternative_allele_no_freq = AlternativeVariantAllele( + allele_sequence="A", + allele_type="insertion", + predicted_molecular_consequences=[ + PredictedIntergenicConsequence( + feature_type=None, + consequences=["intergenic_variant"], + ) + ], + ) + self.assertIsNone(alternative_allele_no_freq.representative_population_allele_frequency) + + def test_variant(self): + variant = Variant( + name=None, + allele_type="SNP", + location=Location(region_name="1", start=10000, end=10001), + reference_allele=ReferenceVariantAllele(allele_sequence="C"), + alternative_alleles=[ + AlternativeVariantAllele( + allele_sequence="T", + allele_type="SNP", + predicted_molecular_consequences=[ + PredictedIntergenicConsequence( + feature_type=None, + consequences=["intergenic_variant"] + ) + ] + ) + ] + ) + self.assertIsNone(variant.name) + + # Valid instance without explicitly setting name (default=None) + variant_no_name = Variant( + allele_type="SNP", + location=Location(region_name="1", start=10000, end=10001), + reference_allele=ReferenceVariantAllele(allele_sequence="C"), + alternative_alleles=[ + AlternativeVariantAllele( + allele_sequence="T", + allele_type="SNP", + predicted_molecular_consequences=[ + PredictedIntergenicConsequence( + feature_type=None, + consequences=["intergenic_variant"] + ) + ] + ) + ] + ) + self.assertIsNone(variant_no_name.name) + + def test_metadata_required(self): + metadata = Metadata( + pagination=PaginationMetadata(page=1, per_page=10, total=100) + ) + self.assertEqual(metadata.pagination.page, 1) + + # Invalid instance without pagination - should raise ValidationError + with self.assertRaises(ValidationError): + Metadata() + + def test_vep_results_response(self): + metadata = Metadata( + pagination=PaginationMetadata(page=1, per_page=10, total=100) + ) + variant = Variant( + name=None, + allele_type="SNV", + location=Location(region_name="1", start=10000, end=10001), + reference_allele=ReferenceVariantAllele(allele_sequence="A"), + alternative_alleles=[ + AlternativeVariantAllele( + allele_sequence="T", + allele_type="SNP", + predicted_molecular_consequences=[ + PredictedIntergenicConsequence( + feature_type=None, + consequences=["intergenic_variant"], + ) + ] + ) + ], + ) + response = VepResultsResponse(metadata=metadata, variants=[variant]) + self.assertEqual(response.metadata.pagination.page, 1) + self.assertEqual(len(response.variants), 1) + + # Invalid instance without metadata - should raise ValidationError + with self.assertRaises(ValidationError): + VepResultsResponse(variants=[variant]) diff --git a/app/vep/models/vcf_results_model.py b/app/vep/models/vcf_results_model.py index 53cbff4..4a1d014 100644 --- a/app/vep/models/vcf_results_model.py +++ b/app/vep/models/vcf_results_model.py @@ -1,7 +1,3 @@ -# generated by datamodel-codegen: -# filename: APISpecification.yaml -# timestamp: 2024-06-10T15:52:34+00:00 - from __future__ import annotations from enum import Enum @@ -9,16 +5,14 @@ from pydantic import BaseModel, Field - class PaginationMetadata(BaseModel): page: int per_page: int total: int - class PredictedIntergenicConsequence(BaseModel): - feature_type: Any = Field( - ..., + feature_type: Optional[Any] = Field( + default=None, description="The value of this field is always null. The presence of null in this field will serve as a marker that this is a consequence of an intergenic variant.", ) consequences: List[str] = Field( @@ -40,7 +34,7 @@ class PredictedTranscriptConsequence(BaseModel): feature_type: FeatureType stable_id: str = Field(..., description="transcript stable id, versioned") gene_stable_id: str = Field(..., description="gene stable id, versioned") - gene_symbol: str + gene_symbol: Optional[str] = Field(default=None) biotype: str is_canonical: bool consequences: List[str] @@ -56,11 +50,11 @@ class Location(BaseModel): start: int end: int - class Metadata(BaseModel): pagination: PaginationMetadata + class AlternativeVariantAllele(BaseModel): allele_sequence: str allele_type: str @@ -71,7 +65,10 @@ class AlternativeVariantAllele(BaseModel): class Variant(BaseModel): - name: str = Field(..., description="User's name for the variant; optional") + name: Optional[str] = Field( + default=None, + description="User's name for the variant; optional" + ) allele_type: str location: Location reference_allele: ReferenceVariantAllele From 2b45900b9953f6cdcbfe91a36505f45bbee1ff4b Mon Sep 17 00:00:00 2001 From: Mehrnaz Charkhchi Date: Fri, 4 Oct 2024 17:50:40 +0100 Subject: [PATCH 2/3] Update gene_symbol model. --- app/tests/models/test_vcf_results_model.py | 2 +- app/vep/models/vcf_results_model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/tests/models/test_vcf_results_model.py b/app/tests/models/test_vcf_results_model.py index 65c4543..6ba7e35 100644 --- a/app/tests/models/test_vcf_results_model.py +++ b/app/tests/models/test_vcf_results_model.py @@ -16,7 +16,7 @@ ) -class TestOptionalFields(unittest.TestCase): +class TestVEPResultModel(unittest.TestCase): def test_predicted_intergenic_consequence(self): consequence = PredictedIntergenicConsequence( diff --git a/app/vep/models/vcf_results_model.py b/app/vep/models/vcf_results_model.py index 4a1d014..73a5a6c 100644 --- a/app/vep/models/vcf_results_model.py +++ b/app/vep/models/vcf_results_model.py @@ -34,7 +34,7 @@ class PredictedTranscriptConsequence(BaseModel): feature_type: FeatureType stable_id: str = Field(..., description="transcript stable id, versioned") gene_stable_id: str = Field(..., description="gene stable id, versioned") - gene_symbol: Optional[str] = Field(default=None) + gene_symbol: Optional[str] = None biotype: str is_canonical: bool consequences: List[str] From 1118c9a7fe1a243d8373f8828c50207acddd7e7b Mon Sep 17 00:00:00 2001 From: Mehrnaz Charkhchi Date: Fri, 4 Oct 2024 17:52:23 +0100 Subject: [PATCH 3/3] Update test name. --- app/tests/models/test_vcf_results_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/tests/models/test_vcf_results_model.py b/app/tests/models/test_vcf_results_model.py index 6ba7e35..31110be 100644 --- a/app/tests/models/test_vcf_results_model.py +++ b/app/tests/models/test_vcf_results_model.py @@ -16,7 +16,7 @@ ) -class TestVEPResultModel(unittest.TestCase): +class TestVCFResultModel(unittest.TestCase): def test_predicted_intergenic_consequence(self): consequence = PredictedIntergenicConsequence(