From a50b9fb189e777241aefd789abbd480ec9df4351 Mon Sep 17 00:00:00 2001 From: James Stevenson Date: Sun, 24 May 2026 13:28:38 -0400 Subject: [PATCH 1/5] fix: _from_vrs handle kwargs without error (#635) the root cause of https://github.com/biocommons/anyvar/issues/467 If you have a valid variant expression, you can attach some extra kwargs like "assembly_name" to help ground certain kinds of translation. However, if you happen to have an invalid expression, the translator will attempt each different format of translation, including `_from_vrs`. If you have attached kwargs, `_from_vrs` will raise an unexpected TypeError because it, unlike the other `_from_` methods, doesn't include kwargs in its type signature. I can't think of a reason why this might be the case. I also added a test to show this. --- src/ga4gh/vrs/extras/translator.py | 2 +- tests/extras/test_allele_translator.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ga4gh/vrs/extras/translator.py b/src/ga4gh/vrs/extras/translator.py index 00da9bb1..81f38306 100644 --- a/src/ga4gh/vrs/extras/translator.py +++ b/src/ga4gh/vrs/extras/translator.py @@ -154,7 +154,7 @@ def hgvs_tools(self) -> HgvsTools: """Instantiate and return an HgvsTools instance""" return HgvsTools(self.data_proxy) - def _from_vrs(self, var: dict) -> models._VariationBase | None: + def _from_vrs(self, var: dict, **kwargs) -> models._VariationBase | None: # noqa: ARG002 """Convert from dict representation of VRS JSON to VRS object""" if not isinstance(var, Mapping): return None diff --git a/tests/extras/test_allele_translator.py b/tests/extras/test_allele_translator.py index 24c461e8..59b68862 100644 --- a/tests/extras/test_allele_translator.py +++ b/tests/extras/test_allele_translator.py @@ -331,6 +331,11 @@ def test_from_invalid(tlr): ): tlr.translate_from("BRAF amplication") + with pytest.raises( + ValueError, match="Unable to parse data as beacon, gnomad, hgvs, spdi, vrs" + ): + tlr.translate_from("BRAF amplication", assembly_name="GRCh37") + @pytest.mark.vcr def test_from_beacon(tlr): From a9a4897d41e3a7e802fb374c84a574f1effa9c64 Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Mon, 8 Jun 2026 14:12:49 -0400 Subject: [PATCH 2/5] fix: handle key error in translate_to (#639) close #638 * Unsupported `fmt` in `translate_to` should raise `NotImplementedError` --- src/ga4gh/vrs/extras/translator.py | 8 +++++++- tests/extras/test_allele_translator.py | 21 ++++----------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/ga4gh/vrs/extras/translator.py b/src/ga4gh/vrs/extras/translator.py index 81f38306..91c45532 100644 --- a/src/ga4gh/vrs/extras/translator.py +++ b/src/ga4gh/vrs/extras/translator.py @@ -142,8 +142,14 @@ def translate_to(self, vo: models._VariationBase, fmt: str, **kwargs) -> list[st kwargs: ref_seq_limit Optional(int): If vo.state is a ReferenceLengthExpression, and `ref_seq_limit` is specified, and `fmt` is `spdi`, the reference sequence is included in the SPDI expression if it is below the limit Otherwise only the length of the reference sequence is included. If the limit is None, the reference sequence is always included. In all cases, the alt sequence is included. Default is 0 (never include reference sequence). + :raise NotImplementedError: If `fmt` is not supported """ - t = self.to_translators[fmt] + try: + t = self.to_translators[fmt] + except KeyError as e: + msg = f"{fmt} is not supported" + raise NotImplementedError(msg) from e + return t(vo, **kwargs) ############################################################################ diff --git a/tests/extras/test_allele_translator.py b/tests/extras/test_allele_translator.py index 59b68862..15f87a89 100644 --- a/tests/extras/test_allele_translator.py +++ b/tests/extras/test_allele_translator.py @@ -978,20 +978,7 @@ def test_normalize_microsatellite_counts(tlr, case): ) -# TODO: Readd these tests -# @pytest.mark.vcr -# def test_errors(tlr): -# with pytest.raises(ValueError): -# tlr._from_beacon("bogus") -# -# with pytest.raises(ValueError): -# tlr._from_gnomad("NM_182763.2:c.688+403C>T") -# -# with pytest.raises(ValueError): -# tlr._from_hgvs("NM_182763.2:c.688+403C>T") -# -# with pytest.raises(ValueError): -# tlr._from_hgvs("NM_182763.2:c.688_690inv") -# -# with pytest.raises(ValueError): -# tlr._from_spdi("NM_182763.2:c.688+403C>T") +@pytest.mark.vcr +def test_translate_to_invalid_fmt(tlr): + with pytest.raises(NotImplementedError, match="gnomad is not supported"): + tlr.translate_to(models.Allele.model_validate(snv_output), fmt="gnomad") From 79c212d8c9509c1d627640087acf599ebd5d47fb Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Wed, 12 Aug 2026 09:35:50 -0400 Subject: [PATCH 3/5] fix: JSON Schema number type should also include int (#645) close #643 --- src/ga4gh/core/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ga4gh/core/models.py b/src/ga4gh/core/models.py index aabfb45c..89e9ab94 100644 --- a/src/ga4gh/core/models.py +++ b/src/ga4gh/core/models.py @@ -197,7 +197,7 @@ class Extension(Element, BaseModelForbidExtra): ..., description="A name for the Extension. Should be indicative of its meaning and/or the type of information it value represents.", ) - value: float | str | bool | dict[str, Any] | list[Any] | None = Field( + value: int | float | str | bool | dict[str, Any] | list[Any] | None = Field( ..., description="The value of the Extension - can be any primitive or structured object", ) From 859e77a132b8f097d52e1f0023cfa3d9225aa208 Mon Sep 17 00:00:00 2001 From: Kori Kuzma Date: Fri, 14 Aug 2026 11:44:48 -0400 Subject: [PATCH 4/5] feat: add CORE_VERSION (gks-core) (#648) close #646 --- src/ga4gh/core/__init__.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ga4gh/core/__init__.py b/src/ga4gh/core/__init__.py index 1b219d1a..541c56ff 100644 --- a/src/ga4gh/core/__init__.py +++ b/src/ga4gh/core/__init__.py @@ -21,7 +21,17 @@ ) from ga4gh.core.pydantic import is_curie_type, is_pydantic_instance, pydantic_copy +try: + __version__ = version(__name__) +except PackageNotFoundError: # pragma: nocover + __version__ = "unknown" +finally: + del version, PackageNotFoundError + +CORE_VERSION = "1.0.0" + __all__ = [ + "CORE_VERSION", "CURIE_NAMESPACE", "CURIE_SEP", "GA4GH_DIGEST_REGEXP", @@ -42,10 +52,3 @@ "sha512t24u", "use_ga4gh_compute_identifier_when", ] - -try: - __version__ = version(__name__) -except PackageNotFoundError: # pragma: nocover - __version__ = "unknown" -finally: - del version, PackageNotFoundError From 21709c530792510d0979ad18bf09613dc4c89438 Mon Sep 17 00:00:00 2001 From: Novikov Kirill <75641500+knQzx@users.noreply.github.com> Date: Fri, 2 Oct 2026 05:44:53 +0200 Subject: [PATCH 5/5] fix: detect intronic RNA (r.) variants in is_intronic (#642) `is_intronic` only recognized introns on coding (c.) coordinates because it gated on the interval being a `BaseOffsetInterval`. RNA (r.) coordinates parse to a plain `Interval` whose endpoints are still `BaseOffsetPosition`, so intronic r. variants slipped past the "Intronic HGVS variants are not supported" guard inspect the interval endpoints instead of the interval type. g./p. coordinates have no base-offset positions and stay non-intronic closes #628 --------- Co-authored-by: Kyle Ferriter --- src/ga4gh/vrs/utils/hgvs_tools.py | 15 ++++++++--- tests/extras/test_hgvs_tools.py | 42 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/extras/test_hgvs_tools.py diff --git a/src/ga4gh/vrs/utils/hgvs_tools.py b/src/ga4gh/vrs/utils/hgvs_tools.py index 503d4022..42d81f86 100644 --- a/src/ga4gh/vrs/utils/hgvs_tools.py +++ b/src/ga4gh/vrs/utils/hgvs_tools.py @@ -5,6 +5,7 @@ import hgvs import hgvs.dataproviders.uta +import hgvs.location import hgvs.normalizer import hgvs.parser import hgvs.variantmapper @@ -77,9 +78,17 @@ def is_intronic(self, sv: HgvsSequenceVariant) -> bool: bool: True if the SequenceVariant is intronic, False otherwise. """ - if isinstance(sv.posedit.pos, hgvs.location.BaseOffsetInterval): - return sv.posedit.pos.start.is_intronic or sv.posedit.pos.end.is_intronic - return False + # Coding (c.) intervals are BaseOffsetInterval, but RNA (r.) intervals + # are plain Interval even when their endpoints carry a base-offset + # position, so inspect the endpoints directly rather than the interval + # type. Non-offset coordinates (g./p.) use position types without + # is_intronic and are correctly reported as not intronic. + pos = sv.posedit.pos + start = getattr(pos, "start", None) + end = getattr(pos, "end", None) + return ( + isinstance(start, hgvs.location.BaseOffsetPosition) and start.is_intronic + ) or (isinstance(end, hgvs.location.BaseOffsetPosition) and end.is_intronic) def get_edit_type(self, sv: HgvsSequenceVariant) -> str | None: """Safely extract ``type`` property from SequenceVariant""" diff --git a/tests/extras/test_hgvs_tools.py b/tests/extras/test_hgvs_tools.py new file mode 100644 index 00000000..fde53e60 --- /dev/null +++ b/tests/extras/test_hgvs_tools.py @@ -0,0 +1,42 @@ +"""Tests for ga4gh.vrs.utils.hgvs_tools.""" + +import hgvs.parser +import pytest + +from ga4gh.vrs.utils.hgvs_tools import HgvsTools + + +@pytest.fixture(scope="module") +def hgvs_tools(): + # is_intronic only parses expressions, so build HgvsTools without the + # network-dependent __init__ (which opens a UTA connection). + tools = HgvsTools.__new__(HgvsTools) + tools.parser = hgvs.parser.Parser() + return tools + + +@pytest.mark.parametrize( + ("hgvs_expr", "expected"), + [ + # coding (c.) + ("NM_000000.1:c.76-1G>A", True), # intronic (acceptor side) + ("NM_000000.1:c.87+1A>T", True), # intronic (donor side) + ("NM_000000.1:c.100A>T", False), # exonic + # RNA (r.) - previously missed: the interval is a plain Interval, not a + # BaseOffsetInterval, so the old isinstance check returned False + ("NR_000000.1:r.76-1g>a", True), # intronic (acceptor side) + ("NR_000000.1:r.77+1g>a", True), # intronic (donor side) + ("NR_000000.1:r.100a>u", False), # exonic + # RNA (r.) exonic - real-world ClinVar pins from issue #628 (offset 0) + ("NR_001566.1:r.398_399del", False), + ("NR_001566.1:r.245del", False), + ("NM_001374385.1:r.2843_2931del", False), + ("NM_001323289.2:r.2632c>a", False), + # genomic (g.) - no base-offset positions, never intronic + ("NC_000001.11:g.100A>T", False), + ], +) +def test_is_intronic(hgvs_tools, hgvs_expr, expected): + sv = hgvs_tools.parse(hgvs_expr) + assert sv is not None + assert hgvs_tools.is_intronic(sv) is expected