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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ extras = [
"hgvs>=2.0.0a0,<3.0",
"dill~=0.3.7",
"click",
"pysam==0.23.0", # pinned pending https://github.com/ga4gh/vrs-python/issues/560
"pysam>=0.24.0",
]
tests = [
"pytest",
Expand Down
15 changes: 7 additions & 8 deletions src/ga4gh/vrs/extras/translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,16 +156,15 @@ def hgvs_tools(self) -> HgvsTools:
return HgvsTools(self.data_proxy)

def _from_vrs(self, var: dict, **kwargs) -> models._VariationBase | None: # noqa: ARG002
"""Convert from dict representation of VRS JSON to VRS object"""
"""Convert from dict representation of VRS JSON to VRS object

:raise pydantic.ValidationError: If `var` is not a valid VRS variation
"""
if not isinstance(var, Mapping):
return None
if "type" not in var:
return None
try:
model = models[var["type"]]
except KeyError:
return None
return model(**var)
return models.Variation.model_validate(var).root


class AlleleTranslator(_Translator):
Expand Down Expand Up @@ -254,7 +253,7 @@ def _from_gnomad(self, gnomad_expr: str, **kwargs) -> models.Allele | None:
"""
if not isinstance(gnomad_expr, str):
return None
m = self.gnomad_re.match(gnomad_expr)
m = self.gnomad_re.fullmatch(gnomad_expr)
if not m:
return None

Expand Down Expand Up @@ -341,7 +340,7 @@ def _from_spdi(self, spdi_expr: str, **kwargs) -> models.Allele | None:
if not isinstance(spdi_expr, str):
return None

m = self.spdi_re.match(spdi_expr)
m = self.spdi_re.fullmatch(spdi_expr)
if not m:
return None

Expand Down
58 changes: 58 additions & 0 deletions tests/extras/test_allele_translator.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from pydantic import ValidationError

from ga4gh.vrs import models
from ga4gh.vrs.dataproxy import DataProxyValidationError
Expand Down Expand Up @@ -335,6 +336,21 @@ def test_from_invalid(tlr):
tlr.translate_from("BRAF amplication", assembly_name="GRCh37")


@pytest.mark.parametrize(
("expr", "fmt"),
[
# multi-allelic gnomAD-style input must not be truncated to its first ALT
("1-55516888-G-GA,GT", "gnomad"),
("19-44908822-C-T;foo", "gnomad"),
("NC_000019.10:44908821:1:T:extra", "spdi"),
],
)
def test_from_trailing_input_rejected(tlr, expr, fmt):
"""Expressions with trailing content must be rejected, not partially parsed"""
with pytest.raises(ValueError, match=f"Unable to parse data as {fmt}"):
tlr.translate_from(expr, fmt=fmt)


@pytest.mark.vcr
def test_from_gnomad(tlr):
do_normalize = False
Expand Down Expand Up @@ -962,6 +978,48 @@ def test_translate_to_invalid_fmt(tlr):
tlr.translate_to(models.Allele.model_validate(snv_output), fmt="gnomad")


def test_from_vrs_dict():
"""Regression test for ga4gh/vrs-python#489.

Translating a VRS dict must resolve the model class from its `type`
instead of crashing with TypeError (the `models` module was subscripted).
"""
tlr = AlleleTranslator(data_proxy=None, identify=False)

# valid VRS dict translates to a VRS object (previously raised TypeError)
allele = tlr.translate_from(snv_output, fmt="vrs")
assert isinstance(allele, models.Allele)
assert allele.type == "Allele"
assert allele.location.start == snv_output["location"]["start"]
assert allele.location.end == snv_output["location"]["end"]

# non-dict and missing-type inputs still return None
assert tlr._from_vrs("NC_000019.10:g.44908822C>T") is None
assert tlr._from_vrs({"location": {}}) is None


from_vrs_non_variation_type_cases = [
{"id": "unknown-name", "type": "NotARealModel"},
{"id": "non-variation-vrs-class", "type": "SequenceLocation"},
{"id": "non-model-module-attribute", "type": "Field"},
{"id": "variation-base-class", "type": "_VariationBase"},
{"id": "non-string", "type": 5},
]


@pytest.mark.parametrize(
"case", from_vrs_non_variation_type_cases, ids=lambda c: c["id"]
)
def test_from_vrs_non_variation_type(case):
"""A VRS dict whose `type` is not a VRS variation class is rejected, rather than
resolved to whatever `ga4gh.vrs.models` attribute has that name
"""
tlr = AlleleTranslator(data_proxy=None, identify=False)
with pytest.raises(ValidationError) as e:
tlr.translate_from({"type": case["type"]}, fmt="vrs")
assert e.value.errors()[0]["type"] == "union_tag_invalid"


def test_from_hgvs_uncertain_range_raises(tlr):
"""Uncertain-range HGVS cannot be represented as an Allele with a literal
sequence state; use CnvTranslator instead. See issue #609.
Expand Down
6 changes: 3 additions & 3 deletions tests/extras/test_annotate_vcf.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def test_get_vrs_object_invalid_input(vcf_annotator: VcfAnnotator, caplog):


@pytest.mark.vcr
def test_annotate_vcf_rle(vcf_annotator: VcfAnnotator, vcr_cassette):
def test_annotate_vcf_rle(vcf_annotator: VcfAnnotator, vcr_cassette, tmp_path: Path):
"""Test VCF annotation with ReferenceLengthExpression states.

Tests two variants that should produce ReferenceLengthExpression states:
Expand All @@ -242,8 +242,8 @@ def test_annotate_vcf_rle(vcf_annotator: VcfAnnotator, vcr_cassette):
"""
vcr_cassette.allow_playback_repeats = False
input_vcf = TEST_DATA_DIR / "test_rle.vcf"
output_vcf = TEST_DATA_DIR / "test_rle_output.vcf"
output_vrs_pkl = TEST_DATA_DIR / "test_rle_output.pkl"
output_vcf = tmp_path / "test_rle_output.vcf"
output_vrs_pkl = tmp_path / "test_rle_output.pkl"

# Annotate the VCF with VRS attributes enabled
vcf_annotator.annotate(
Expand Down
Loading