From 1b147a36ed86904d0f67648c39735d5ce82e8f0b Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Thu, 30 Jul 2026 16:08:30 -0400 Subject: [PATCH 1/9] feat(normalize): add RleSubunitMode to select RLE repeat subunit length For a reference-derived ambiguous insertion, more than one factor of the seed length may be circularly expandable to recreate the alternate sequence. VRS <= 2.0 selects the greatest such factor as the repeatSubunitLength; ga4gh/vrs#700 changes this to the smallest. Add RleSubunitMode.{LARGEST,SMALLEST} and thread it through _normalize_allele(), normalize() and the translator (instance default plus per-call kwarg, mirroring rle_seq_limit). _factor_gen() now yields factors ascending or descending per the mode, so the first valid factor found is the selected one. repeatSubunitLength is inherent to the ReferenceLengthExpression digest, so the modes can yield different computed identifiers for one input Allele. LARGEST remains the default; behavior is unchanged unless the mode is passed explicitly. Only step 6.c (reference-derived ambiguous insertion) is affected. Reference-agree alleles, substitutions and deletions take repeatSubunitLength from the seed length directly and are mode-invariant. Refs #637 --- src/ga4gh/vrs/__init__.py | 3 +- src/ga4gh/vrs/extras/translator.py | 43 +++++++++++++++++++-- src/ga4gh/vrs/normalize.py | 60 ++++++++++++++++++++++++++---- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/ga4gh/vrs/__init__.py b/src/ga4gh/vrs/__init__.py index 16d36647..11cef649 100644 --- a/src/ga4gh/vrs/__init__.py +++ b/src/ga4gh/vrs/__init__.py @@ -5,7 +5,7 @@ from ga4gh.vrs import models from ga4gh.vrs.enderef import vrs_deref, vrs_enref from ga4gh.vrs.models import VrsType -from ga4gh.vrs.normalize import normalize +from ga4gh.vrs.normalize import RleSubunitMode, normalize try: __version__ = version(__name__) @@ -19,6 +19,7 @@ __all__ = [ "VRS_VERSION", + "RleSubunitMode", "VrsType", "models", "normalize", diff --git a/src/ga4gh/vrs/extras/translator.py b/src/ga4gh/vrs/extras/translator.py index 91c45532..df57543b 100644 --- a/src/ga4gh/vrs/extras/translator.py +++ b/src/ga4gh/vrs/extras/translator.py @@ -17,7 +17,10 @@ from ga4gh.vrs import models, normalize from ga4gh.vrs.dataproxy import SequenceProxy, _DataProxy from ga4gh.vrs.extras.decorators import lazy_property -from ga4gh.vrs.normalize import denormalize_reference_length_expression +from ga4gh.vrs.normalize import ( + RleSubunitMode, + denormalize_reference_length_expression, +) from ga4gh.vrs.utils.hgvs_tools import HgvsTools _logger = logging.getLogger(__name__) @@ -75,11 +78,13 @@ def __init__( default_assembly_name: str = "GRCh38", identify: bool = True, rle_seq_limit: int | None = 50, + rle_subunit_mode: RleSubunitMode = RleSubunitMode.LARGEST, ) -> None: self.default_assembly_name = default_assembly_name self.data_proxy = data_proxy self.identify = identify self.rle_seq_limit = rle_seq_limit + self.rle_subunit_mode = rle_subunit_mode self.from_translators: dict[str, VariationFromStrProtocol] = {} self.to_translators: dict[str, VariationToStrProtocol] = {} @@ -111,6 +116,10 @@ def translate_from( To exclude `sequence` from the response, set to 0. For no limit, set to `None`. Defaults value set in instance variable, `rle_seq_limit`. + rle_subunit_mode (RleSubunitMode): Which valid factor of the seed + length to select as the `repeatSubunitLength` for a + reference-derived ambiguous insertion. + Defaults value set in instance variable, `rle_subunit_mode`. do_normalize (bool): `True` if fully justified normalization should be performed. `False` otherwise. Defaults to `True` """ @@ -181,9 +190,21 @@ def __init__( data_proxy: _DataProxy, default_assembly_name: str = "GRCh38", identify: bool = True, + rle_subunit_mode: RleSubunitMode = RleSubunitMode.LARGEST, ) -> None: - """Initialize AlleleTranslator class""" - super().__init__(data_proxy, default_assembly_name, identify) + """Initialize AlleleTranslator class + + :param rle_subunit_mode: Default mode for selecting the `repeatSubunitLength` of + a reference-derived ambiguous insertion normalized to a + `ReferenceLengthExpression`. Overridable per call via the + `rle_subunit_mode` kwarg. See `RleSubunitMode`. + """ + super().__init__( + data_proxy, + default_assembly_name, + identify, + rle_subunit_mode=rle_subunit_mode, + ) self.from_translators = { "beacon": self._from_beacon, @@ -232,6 +253,10 @@ def _from_beacon(self, beacon_expr: str, **kwargs) -> models.Allele | None: To exclude `sequence` from the response, set to 0. For no limit, set to `None`. Defaults value set in instance variable, `rle_seq_limit`. + rle_subunit_mode (RleSubunitMode): Which valid factor of the seed length + to select as the `repeatSubunitLength` for a reference-derived + ambiguous insertion. + Defaults value set in instance variable, `rle_subunit_mode`. do_normalize (bool): `True` if fully justified normalization should be performed. `False` otherwise. Defaults to `True` @@ -297,6 +322,10 @@ def _from_gnomad(self, gnomad_expr: str, **kwargs) -> models.Allele | None: To exclude `sequence` from the response, set to 0. For no limit, set to `None`. Defaults value set in instance variable, `rle_seq_limit`. + rle_subunit_mode (RleSubunitMode): Which valid factor of the seed length + to select as the `repeatSubunitLength` for a reference-derived + ambiguous insertion. + Defaults value set in instance variable, `rle_subunit_mode`. do_normalize (bool): `True` if fully justified normalization should be performed. `False` otherwise. Defaults to `True` @@ -371,6 +400,10 @@ def _from_spdi(self, spdi_expr: str, **kwargs) -> models.Allele | None: To exclude `sequence` from the response, set to 0. For no limit, set to `None`. Defaults value set in instance variable, `rle_seq_limit`. + rle_subunit_mode (RleSubunitMode): Which valid factor of the seed length + to select as the `repeatSubunitLength` for a reference-derived + ambiguous insertion. + Defaults value set in instance variable, `rle_subunit_mode`. do_normalize (bool): `True` if fully justified normalization should be performed. `False` otherwise. Defaults to `True` @@ -509,6 +542,9 @@ def _post_process_imported_allele( normalization, this sets the limit for the length of the `sequence`. To exclude `sequence` from the response, set to 0. For no limit, set to `None`. + rle_subunit_mode (RleSubunitMode): Which valid factor of the seed length to + select as the `repeatSubunitLength` for a reference-derived ambiguous + insertion. Defaults value set in instance variable, `rle_subunit_mode`. do_normalize (bool): `True` if fully justified normalization should be performed. `False` otherwise. Defaults to `True` """ @@ -517,6 +553,7 @@ def _post_process_imported_allele( allele, self.data_proxy, rle_seq_limit=kwargs.get("rle_seq_limit", self.rle_seq_limit), + rle_subunit_mode=kwargs.get("rle_subunit_mode", self.rle_subunit_mode), ) if self.identify: diff --git a/src/ga4gh/vrs/normalize.py b/src/ga4gh/vrs/normalize.py index fad73d87..95b791be 100644 --- a/src/ga4gh/vrs/normalize.py +++ b/src/ga4gh/vrs/normalize.py @@ -6,7 +6,8 @@ import itertools import logging -from enum import IntEnum +from collections.abc import Iterator +from enum import IntEnum, StrEnum from typing import NamedTuple from bioutils.normalize import NormalizationMode @@ -20,6 +21,28 @@ _logger = logging.getLogger(__name__) +class RleSubunitMode(StrEnum): + """Define which repeat subunit length to select for a reference-derived ambiguous + insertion normalized to a ``ReferenceLengthExpression``. + + For a reference-derived ambiguous insertion, more than one factor of the seed length + may be circularly expandable to recreate the alternate sequence. This selects which + of those candidate factors becomes the ``repeatSubunitLength``. + + ``repeatSubunitLength`` is inherent to the ``ReferenceLengthExpression`` digest, so + the two modes may produce different computed identifiers for the same input Allele. + + :cvar LARGEST: Select the greatest valid factor. VRS <= 2.0 behavior, and the + current default. + :cvar SMALLEST: Select the smallest valid factor. Specified by VRS as of + https://github.com/ga4gh/vrs/pull/700, and will become the default when + vrs-python targets that spec version. + """ + + LARGEST = "largest" + SMALLEST = "smallest" + + class PosType(IntEnum): """Define the kind of position on a location""" @@ -85,7 +108,10 @@ def _get_new_allele_location_pos( def _normalize_allele( - input_allele: models.Allele, data_proxy: _DataProxy, rle_seq_limit: int = 50 + input_allele: models.Allele, + data_proxy: _DataProxy, + rle_seq_limit: int = 50, + rle_subunit_mode: RleSubunitMode = RleSubunitMode.LARGEST, ): """Normalize Allele using "fully-justified" normalization adapted from NCBI's VOCA. Fully-justified normalization expands such ambiguous representation over the @@ -111,6 +137,9 @@ def _normalize_allele( of the `sequence`. To exclude `sequence` from the response, set to 0. For no limit, set to `None`. + :param rle_subunit_mode: Which valid factor of the seed length to select as the + `repeatSubunitLength` for a reference-derived ambiguous insertion. Defaults to + `RleSubunitMode.LARGEST`, matching VRS <= 2.0. See `RleSubunitMode`. """ # Algorithm applies to LiteralSequenceExpression alleles only; other states are returned unchanged if not isinstance(input_allele.state, models.LiteralSequenceExpression): @@ -204,7 +233,7 @@ def _normalize_allele( # 5.c if len_extended_alt > len_extended_ref: - factors = _factor_gen(seed_length) + factors = _factor_gen(seed_length, rle_subunit_mode) for cycle_length in factors: if cycle_length > len_extended_ref: continue @@ -308,17 +337,31 @@ def denormalize_reference_length_expression( return alt -def _factor_gen(n: int): - """Yield all factors of an integer `n`, in descending order""" +def _factor_gen(n: int, mode: RleSubunitMode = RleSubunitMode.LARGEST) -> Iterator[int]: + """Yield all factors of an integer `n` + + :param n: The integer to factor + :param mode: `RleSubunitMode.LARGEST` to yield factors in descending order, + `RleSubunitMode.SMALLEST` to yield them in ascending order. The first valid + factor found by the caller is the one selected, so the order determines whether + the largest or smallest valid factor wins. + """ + upper_factors = [] lower_factors = [] i = 1 while i * i <= n: if n % i == 0: - yield n // i + upper_factors.append(n // i) if n // i != i: lower_factors.append(i) i += 1 - yield from reversed(lower_factors) + + if mode == RleSubunitMode.SMALLEST: + yield from lower_factors + yield from reversed(upper_factors) + else: + yield from upper_factors + yield from reversed(lower_factors) def _define_rle_allele( @@ -371,6 +414,9 @@ def normalize(vo, data_proxy: _DataProxy | None = None, **kwargs): :param data_proxy: GA4GH sequence dataproxy instance, if needed :keyword rle_seq_limit: If RLE is set as the new state, set the limit for the length of the `sequence`. To exclude `state.sequence`, set to 0. + :keyword rle_subunit_mode: Which valid factor of the seed length to select as the + `repeatSubunitLength` for a reference-derived ambiguous insertion. Defaults to + `RleSubunitMode.LARGEST`. See `RleSubunitMode`. :return: normalized object, or unmodified input object if the normalization algorithm does not provide normalization steps for the given type. :raise TypeError: if given object isn't a pydantic.BaseModel From c5f071c03d1e2099a7ad8fd38827ba017b737d68 Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Thu, 30 Jul 2026 16:09:34 -0400 Subject: [PATCH 2/9] test(normalize): cover RLE subunit mode selection and round-tripping Declarative table of 11 alleles asserted under both RleSubunitMode values, using the local seqrepo dataproxy so no cassettes are involved. Differing cases show LARGEST's repeatSubunitLength tracking the insertion size rather than the repeat unit: A x 9 -> A x 11 gives 2 vs 1, A x 9 -> A x 18 gives 9 vs 1, and a GT microsatellite insertion of 2 and 3 units gives 4 and 6 vs 2. A x 9 -> A x 20 covers the prime-seed fall-through where LARGEST already yields 1. Mode-invariant cases pin the branches the spec change does not touch: reference-agree, substitution, deletion, a short tandem duplication, and a CAG insertion whose seed length exceeds the modified reference length. Also assert the default mode still equals LARGEST, and that each RLE state denormalizes back to its literal sequence under either mode, so translate_to output is unaffected. Refs #637 --- tests/test_vrs_normalize.py | 237 ++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/tests/test_vrs_normalize.py b/tests/test_vrs_normalize.py index 83c27cdd..21b6cb49 100644 --- a/tests/test_vrs_normalize.py +++ b/tests/test_vrs_normalize.py @@ -1,6 +1,11 @@ import pytest from ga4gh.vrs import models, normalize +from ga4gh.vrs.dataproxy import SequenceProxy +from ga4gh.vrs.normalize import ( + RleSubunitMode, + denormalize_reference_length_expression, +) # Single nucleotide same-as-reference allele. allele_dict1 = { @@ -929,3 +934,235 @@ def test_normalize_partial_rle_del_ins(rest_dataproxy): tail_del_4 = models.Allele(**tail_del_4bp) tail_del_4_norm = normalize(tail_del_4, rest_dataproxy, rle_seq_limit=0) assert tail_del_4_norm == models.Allele(**tail_del_4bp_normalized) + + +############################################################################# +# RLE repeat subunit mode +# +# For a reference-derived ambiguous insertion, more than one factor of the seed +# length may be circularly expandable to recreate the alternate sequence. VRS +# <= 2.0 selects the greatest such factor as the `repeatSubunitLength` +# (`RleSubunitMode.LARGEST`); ga4gh/vrs#700 changes this to the smallest +# (`RleSubunitMode.SMALLEST`). +# +# `repeatSubunitLength` is inherent to the `ReferenceLengthExpression` digest, +# so the two modes can yield different computed identifiers for one input +# Allele. `LARGEST` remains the default for backwards compatibility. +# +# Only step 6.c of the algorithm (reference-derived ambiguous insertion) is +# affected. Reference-agree alleles, substitutions and deletions derive +# `repeatSubunitLength` from the seed length directly and are mode-invariant. +############################################################################# + +# NC_000001.11, homopolymer: ...CTATTAAAAAAAAAGAAGG... (A x 9 at 236900409-236900418) +_HOMOPOLYMER_AC = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" +# NC_000001.11, GT microsatellite: ATGCAC[GTGTGTGTGT]CAAGCT (1007173-1007183) +_MICROSAT_AC = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" +# NC_000002.12, short GT repeat at 155980373 +_GT_REPEAT_AC = "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP" +# NC_000019.10, CAG repeat at 289464 +_CAG_REPEAT_AC = "SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl" +# NC_000011.10, non-repetitive locus +_PLAIN_AC = "SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV" + + +def _rle(length: int, sequence: str, repeat_subunit_length: int) -> dict: + """Build an expected ReferenceLengthExpression state dict""" + return { + "type": "ReferenceLengthExpression", + "length": length, + "sequence": sequence, + "repeatSubunitLength": repeat_subunit_length, + } + + +# Each case declares the unnormalized input, the span the Allele normalizes to +# (identical under both modes, since only `state` is mode-dependent), and the +# expected `state` under each mode. Cases where both states are equal assert +# that the mode does not perturb that branch of the algorithm. +rle_subunit_mode_tests = [ + { + "id": "homopolymer_ins_2bp", + # A x 9 -> A x 11. seed length 2; factors 2 and 1 both valid. + # The repeating unit is plainly "A", which only SMALLEST reports. + "input": (_HOMOPOLYMER_AC, 236900409, 236900418, "A" * 11), + "normalized_span": (236900409, 236900418), + "largest_state": _rle(11, "A" * 11, 2), + "smallest_state": _rle(11, "A" * 11, 1), + }, + { + "id": "homopolymer_ins_9bp", + # A x 9 -> A x 18. seed length 9; LARGEST tracks the insertion size + # rather than the repeat unit. + "input": (_HOMOPOLYMER_AC, 236900409, 236900418, "A" * 18), + "normalized_span": (236900409, 236900418), + "largest_state": _rle(18, "A" * 18, 9), + "smallest_state": _rle(18, "A" * 18, 1), + }, + { + "id": "homopolymer_ins_11bp_prime_seed", + # A x 9 -> A x 20. seed length 11 is prime and exceeds the modified + # reference length (9), so LARGEST already falls through to 1. This is + # the non-monotonicity that motivated ga4gh/vrs#699. + "input": (_HOMOPOLYMER_AC, 236900409, 236900418, "A" * 20), + "normalized_span": (236900409, 236900418), + "largest_state": _rle(20, "A" * 20, 1), + "smallest_state": _rle(20, "A" * 20, 1), + }, + { + "id": "microsatellite_ins_1_unit", + # One GT unit into a GT x 5 tract. seed length 2, so 2 is both the + # greatest and the smallest valid factor. + "input": (_MICROSAT_AC, 1007183, 1007183, "GT"), + "normalized_span": (1007173, 1007183), + "largest_state": _rle(12, "GT" * 6, 2), + "smallest_state": _rle(12, "GT" * 6, 2), + }, + { + "id": "microsatellite_ins_2_units", + # Two GT units. seed length 4; both 4 and 2 are valid, and the true + # repeat unit is "GT". Demonstrates the mode is not homopolymer-only. + "input": (_MICROSAT_AC, 1007183, 1007183, "GTGT"), + "normalized_span": (1007173, 1007183), + "largest_state": _rle(14, "GT" * 7, 4), + "smallest_state": _rle(14, "GT" * 7, 2), + }, + { + "id": "microsatellite_ins_3_units", + # Three GT units. seed length 6; LARGEST reports 6, SMALLEST 2. + "input": (_MICROSAT_AC, 1007183, 1007183, "GTGTGT"), + "normalized_span": (1007173, 1007183), + "largest_state": _rle(16, "GT" * 8, 6), + "smallest_state": _rle(16, "GT" * 8, 2), + }, + { + "id": "tandem_dup_gt", + # Tandem duplication of GT in a tract too short for a larger factor to + # be valid; SMALLEST must not shrink this below the real unit length. + "input": (_GT_REPEAT_AC, 155980373, 155980375, "GTGT"), + "normalized_span": (155980373, 155980375), + "largest_state": _rle(4, "GTGT", 2), + "smallest_state": _rle(4, "GTGT", 2), + }, + { + "id": "trinucleotide_ins_cag", + # Two CAG units into a CAG tract. seed length 6 exceeds the modified + # reference length, so 3 is selected under both modes. + "input": (_CAG_REPEAT_AC, 289464, 289464, "CAGCAG"), + "normalized_span": (289464, 289469), + "largest_state": _rle(11, "CAGCAGCAGCA", 3), + "smallest_state": _rle(11, "CAGCAGCAGCA", 3), + }, + { + "id": "deletion_2bp", + # Deletion (step 6.b): repeatSubunitLength is the seed length, no + # factoring, so the mode is irrelevant. + "input": (_GT_REPEAT_AC, 155980375, 155980377, ""), + "normalized_span": (155980375, 155980377), + "largest_state": _rle(0, "", 2), + "smallest_state": _rle(0, "", 2), + }, + { + "id": "reference_agree", + # Reference-agree allele (step 3.a): repeatSubunitLength is the input + # sequence length, no factoring. + "input": (_PLAIN_AC, 26090950, 26090951, "C"), + "normalized_span": (26090950, 26090951), + "largest_state": _rle(1, "C", 1), + "smallest_state": _rle(1, "C", 1), + }, + { + "id": "substitution", + # Substitution (step 3.b): stays a LiteralSequenceExpression. + "input": (_PLAIN_AC, 26090950, 26090951, "G"), + "normalized_span": (26090950, 26090951), + "largest_state": {"type": "LiteralSequenceExpression", "sequence": "G"}, + "smallest_state": {"type": "LiteralSequenceExpression", "sequence": "G"}, + }, +] + + +def _build_allele(refget_accession: str, start: int, end: int, sequence: str) -> dict: + """Build an unnormalized LiteralSequenceExpression Allele dict""" + return { + "type": "Allele", + "location": { + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": refget_accession, + }, + "start": start, + "end": end, + }, + "state": {"type": "LiteralSequenceExpression", "sequence": sequence}, + } + + +def _expected_allele(case: dict, mode: RleSubunitMode) -> dict: + """Build the expected normalized Allele dict for `case` under `mode`""" + refget_accession = case["input"][0] + start, end = case["normalized_span"] + state = case[ + "largest_state" if mode is RleSubunitMode.LARGEST else "smallest_state" + ] + return { + "type": "Allele", + "location": { + "type": "SequenceLocation", + "sequenceReference": { + "type": "SequenceReference", + "refgetAccession": refget_accession, + }, + "start": start, + "end": end, + }, + "state": state, + } + + +@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) +@pytest.mark.parametrize("mode", list(RleSubunitMode), ids=lambda m: m.value) +def test_normalize_rle_subunit_mode(dataproxy, case, mode): + """Normalization selects the repeat subunit length dictated by `rle_subunit_mode`""" + allele = models.Allele(**_build_allele(*case["input"])) + normalized = normalize(allele, dataproxy, rle_seq_limit=None, rle_subunit_mode=mode) + assert normalized == models.Allele(**_expected_allele(case, mode)) + + +@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) +def test_normalize_rle_subunit_mode_default_is_largest(dataproxy, case): + """Omitting `rle_subunit_mode` preserves pre-existing (LARGEST) behavior""" + allele = models.Allele(**_build_allele(*case["input"])) + normalized = normalize(allele, dataproxy, rle_seq_limit=None) + assert normalized == models.Allele(**_expected_allele(case, RleSubunitMode.LARGEST)) + + +@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) +@pytest.mark.parametrize("mode", list(RleSubunitMode), ids=lambda m: m.value) +def test_normalize_rle_subunit_mode_round_trip(dataproxy, case, mode): + """An RLE state denormalizes back to its literal sequence under either mode. + + `denormalize_reference_length_expression` reconstructs the alternate sequence + from the *prefix* of the reference at the normalized location, while step 6.c + validates the cycle against the *suffix*. Assert the selected + `repeatSubunitLength` round-trips for both modes so that `translate_to` output + (e.g. SPDI, HGVS) is unaffected by the mode. + """ + allele = models.Allele(**_build_allele(*case["input"])) + normalized = normalize(allele, dataproxy, rle_seq_limit=None, rle_subunit_mode=mode) + if not isinstance(normalized.state, models.ReferenceLengthExpression): + pytest.skip(f"{case['id']} does not normalize to a ReferenceLengthExpression") + + ref_seq = SequenceProxy( + dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" + )[normalized.location.start : normalized.location.end] + + assert ( + denormalize_reference_length_expression( + ref_seq=ref_seq, + repeat_subunit_length=normalized.state.repeatSubunitLength, + alt_length=normalized.state.length, + ) + == normalized.state.sequence.root + ) From 7446db9fbbc2efef791f9b57b3736fa72b834c43 Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Thu, 30 Jul 2026 17:03:27 -0400 Subject: [PATCH 3/9] test(normalize): round-trip corpus RLE alleles through denormalize Explicitly declare the 14 pre-existing corpus alleles that normalize to a ReferenceLengthExpression, along with the RLE parameters and the literal alternate sequence each reconstructs to, and assert the full round trip input -> RLE -> denormalize_reference_length_expression() -> alt. The table supplies the alt sequences the *_normalized dicts omit: most of those tests pass rle_seq_limit=0, so state.sequence is absent and only the reconstructed length could be checked. allele_dict2 is excluded because its indefinite Range positions give no single reference span to reconstruct from. --- tests/test_vrs_normalize.py | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/test_vrs_normalize.py b/tests/test_vrs_normalize.py index 21b6cb49..1398ae60 100644 --- a/tests/test_vrs_normalize.py +++ b/tests/test_vrs_normalize.py @@ -1166,3 +1166,107 @@ def test_normalize_rle_subunit_mode_round_trip(dataproxy, case, mode): ) == normalized.state.sequence.root ) + + +# Pre-existing corpus alleles that normalize to a ReferenceLengthExpression, with the +# expected RLE parameters and the literal alternate sequence each reconstructs to. The +# `*_normalized` dicts above cannot supply the alt sequence: most of those tests pass +# `rle_seq_limit=0`, so they omit `state.sequence` entirely. Declaring it here is what +# makes the round trip a real assertion rather than a self-comparison. +# +# `allele_dict2` is intentionally absent: its location uses indefinite `Range` +# positions, so there is no single reference span to reconstruct the alt from. +rle_denormalize_tests = [ + {"input": allele_dict1, "repeat_subunit_length": 1, "length": 1, "alt": "C"}, + {"input": allele_dict4, "repeat_subunit_length": 2, "length": 4, "alt": "GTGT"}, + { + "input": allele_dict5, + "repeat_subunit_length": 3, + "length": 11, + "alt": "CAGCAGCAGCA", + }, + {"input": allele_dict6, "repeat_subunit_length": 2, "length": 2, "alt": "AA"}, + {"input": clinvar_deletion, "repeat_subunit_length": 1, "length": 0, "alt": ""}, + { + "input": clinvar_microsatellite, + "repeat_subunit_length": 4, + "length": 4, + "alt": "AATA", + }, + { + "input": clinvar_tandem_repeat, + "repeat_subunit_length": 3, + "length": 5, + "alt": "CTCCT", + }, + { + "input": clinvar_microsatellite_insertion, + "repeat_subunit_length": 3, + "length": 31, + "alt": "CCTCCTCCTCCTCCTCCTCCTCCTCCTCCTC", + }, + { + "input": partial_repeat_insertion, + "repeat_subunit_length": 2, + "length": 5, + "alt": "CTCTC", + }, + { + "input": middle_ins_4bp, + "repeat_subunit_length": 4, + "length": 9, + "alt": "CCTCCCTCC", + }, + { + "input": deletion_spanning_boundary, + "repeat_subunit_length": 4, + "length": 1, + "alt": "C", + }, + {"input": middle_del_2bp, "repeat_subunit_length": 2, "length": 1, "alt": "C"}, + {"input": tail_del_2bp, "repeat_subunit_length": 2, "length": 1, "alt": "C"}, + {"input": tail_del_4bp, "repeat_subunit_length": 4, "length": 0, "alt": ""}, +] + +rle_denormalize_test_ids = [ + "allele_dict1", + "allele_dict4", + "allele_dict5", + "allele_dict6", + "clinvar_deletion", + "clinvar_microsatellite", + "clinvar_tandem_repeat", + "clinvar_microsatellite_insertion", + "partial_repeat_insertion", + "middle_ins_4bp", + "deletion_spanning_boundary", + "middle_del_2bp", + "tail_del_2bp", + "tail_del_4bp", +] + + +@pytest.mark.parametrize("case", rle_denormalize_tests, ids=rle_denormalize_test_ids) +def test_normalize_rle_denormalize_round_trip(dataproxy, case): + """Corpus alleles round-trip: input -> RLE -> denormalize -> literal alt sequence""" + normalized = normalize( + models.Allele(**case["input"]), dataproxy, rle_seq_limit=None + ) + + assert isinstance(normalized.state, models.ReferenceLengthExpression) + assert normalized.state.repeatSubunitLength == case["repeat_subunit_length"] + assert normalized.state.length == case["length"] + assert normalized.state.sequence.root == case["alt"] + + ref_seq = SequenceProxy( + dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" + )[normalized.location.start : normalized.location.end] + + assert ( + denormalize_reference_length_expression( + ref_seq=ref_seq, + repeat_subunit_length=normalized.state.repeatSubunitLength, + alt_length=normalized.state.length, + ) + == case["alt"] + ) From e40e6ca6e2f3fb71e28023fc037c27a789b35a2e Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Thu, 30 Jul 2026 17:54:26 -0400 Subject: [PATCH 4/9] test(normalize): clarify RLE subunit mode cases and assert why modes agree - Collapse the five accession constants onto the four sequences they actually name. _HOMOPOLYMER_AC and _MICROSAT_AC were the same chr1 accession, and the comments on two others named the wrong chromosome (chrX was labelled NC_000002.12, chr6 was labelled NC_000011.10). - Rename homopolymer_* ids to poly_a_* and describe each locus at its case, so ids describe the behavior under test rather than the substrate. - Correct the tandem_dup_gt and trinucleotide_ins_cag comments. Both claimed the modes agreed because larger factors were too long; in fact the deciding factor is that every competing candidate is rejected as an invalid cycle. - Fix step numbers to match the 0-indexed spec list at vrs.ga4gh.org/en/stable/conventions/normalization.html: the factor search is 5.c.1, returning via 5.d; reference-agree is 2.a, substitution 2.b, deletion 5.b. Restores the numbering the corpus comments already used. - Add an `agreement` field and test_normalize_rle_subunit_mode_agreement, which observes the step 5.c.1 candidates to assert *why* the modes agree, so a case drifting between categories fails instead of silently still passing. --- tests/test_vrs_normalize.py | 233 ++++++++++++++++++++++++++++-------- 1 file changed, 181 insertions(+), 52 deletions(-) diff --git a/tests/test_vrs_normalize.py b/tests/test_vrs_normalize.py index 1398ae60..487525bf 100644 --- a/tests/test_vrs_normalize.py +++ b/tests/test_vrs_normalize.py @@ -1,3 +1,5 @@ +from importlib import import_module + import pytest from ga4gh.vrs import models, normalize @@ -7,6 +9,11 @@ denormalize_reference_length_expression, ) +# `ga4gh.vrs.normalize` the attribute is the `normalize` function re-exported by +# `ga4gh.vrs`, so the module itself has to be fetched through importlib in order +# to patch its internals. +normalize_module = import_module("ga4gh.vrs.normalize") + # Single nucleotide same-as-reference allele. allele_dict1 = { "location": { @@ -949,21 +956,18 @@ def test_normalize_partial_rle_del_ins(rest_dataproxy): # so the two modes can yield different computed identifiers for one input # Allele. `LARGEST` remains the default for backwards compatibility. # -# Only step 6.c of the algorithm (reference-derived ambiguous insertion) is -# affected. Reference-agree alleles, substitutions and deletions derive -# `repeatSubunitLength` from the seed length directly and are mode-invariant. +# Only the factor search in step 5.c.1 is affected (the reference-derived +# ambiguous insertion returned by 5.d). Reference-agree alleles (2.a) and +# deletions (5.b) take `repeatSubunitLength` from the seed length directly, and +# substitutions (2.b) stay a LiteralSequenceExpression, so all three are +# mode-invariant by construction. ############################################################################# -# NC_000001.11, homopolymer: ...CTATTAAAAAAAAAGAAGG... (A x 9 at 236900409-236900418) -_HOMOPOLYMER_AC = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" -# NC_000001.11, GT microsatellite: ATGCAC[GTGTGTGTGT]CAAGCT (1007173-1007183) -_MICROSAT_AC = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" -# NC_000002.12, short GT repeat at 155980373 -_GT_REPEAT_AC = "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP" -# NC_000019.10, CAG repeat at 289464 -_CAG_REPEAT_AC = "SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl" -# NC_000011.10, non-repetitive locus -_PLAIN_AC = "SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV" +# One constant per reference sequence; each case's comment names its own locus. +_CHR1 = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" # GRCh38 chr1, NC_000001.11 +_CHR6 = "SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV" # GRCh38 chr6, NC_000006.12 +_CHR19 = "SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl" # GRCh38 chr19, NC_000019.10 +_CHRX = "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP" # GRCh38 chrX, NC_000023.11 def _rle(length: int, sequence: str, repeat_subunit_length: int) -> dict: @@ -977,107 +981,154 @@ def _rle(length: int, sequence: str, repeat_subunit_length: int) -> dict: # Each case declares the unnormalized input, the span the Allele normalizes to -# (identical under both modes, since only `state` is mode-dependent), and the -# expected `state` under each mode. Cases where both states are equal assert -# that the mode does not perturb that branch of the algorithm. +# (identical under both modes, since only `state` is mode-dependent), the +# expected `state` under each mode, and why the modes agree or differ. +# +# `agreement` records the mechanism, not just the outcome, because the modes +# coincide for three unrelated reasons that would otherwise be indistinguishable: +# +# DIVERGES the modes select different factors +# NO_FACTORING step 5.c is never reached, so no factor search happens +# SINGLE_VALID_FACTOR 5.c is reached but only one factor is a valid cycle, so +# both search orders converge on it +# LARGEST_DEGRADES the seed length exceeds the modified reference length and +# has no smaller usable factor, so LARGEST falls through to +# the same answer SMALLEST reaches directly +# +# Candidate factors quoted below are the ones the algorithm actually tests: the +# search skips any factor greater than the modified reference length without +# evaluating it, then returns the first valid cycle it finds. +DIVERGES = "diverges" +NO_FACTORING = "no_factoring" +SINGLE_VALID_FACTOR = "single_valid_factor" +LARGEST_DEGRADES = "largest_degrades" + rle_subunit_mode_tests = [ { - "id": "homopolymer_ins_2bp", - # A x 9 -> A x 11. seed length 2; factors 2 and 1 both valid. - # The repeating unit is plainly "A", which only SMALLEST reports. - "input": (_HOMOPOLYMER_AC, 236900409, 236900418, "A" * 11), + "id": "poly_a_ins_2bp", + # chr1 A x 9 tract at 236900409-236900418: ...CTATT[AAAAAAAAA]GAAGG... + # Insert AA. seed length 2; LARGEST tests 2 (valid), SMALLEST tests 1 + # (valid). The repeating unit is plainly "A", which only SMALLEST reports. + "input": (_CHR1, 236900409, 236900418, "A" * 11), "normalized_span": (236900409, 236900418), "largest_state": _rle(11, "A" * 11, 2), "smallest_state": _rle(11, "A" * 11, 1), + "agreement": DIVERGES, }, { - "id": "homopolymer_ins_9bp", - # A x 9 -> A x 18. seed length 9; LARGEST tracks the insertion size - # rather than the repeat unit. - "input": (_HOMOPOLYMER_AC, 236900409, 236900418, "A" * 18), + "id": "poly_a_ins_9bp", + # Same tract, A x 9 -> A x 18. seed length 9; LARGEST tests 9 (valid) and + # so tracks the insertion size rather than the repeat unit. + "input": (_CHR1, 236900409, 236900418, "A" * 18), "normalized_span": (236900409, 236900418), "largest_state": _rle(18, "A" * 18, 9), "smallest_state": _rle(18, "A" * 18, 1), + "agreement": DIVERGES, }, { - "id": "homopolymer_ins_11bp_prime_seed", - # A x 9 -> A x 20. seed length 11 is prime and exceeds the modified - # reference length (9), so LARGEST already falls through to 1. This is - # the non-monotonicity that motivated ga4gh/vrs#699. - "input": (_HOMOPOLYMER_AC, 236900409, 236900418, "A" * 20), + "id": "poly_a_ins_11bp_prime_seed", + # Same tract, A x 9 -> A x 20. seed length 11 is prime and exceeds the + # modified reference length (9), so its only factors are 11 (skipped as + # too long) and 1. LARGEST therefore tests 1 first as well and lands on + # the same answer as SMALLEST. This is the non-monotonicity that + # motivated ga4gh/vrs#699. + "input": (_CHR1, 236900409, 236900418, "A" * 20), "normalized_span": (236900409, 236900418), "largest_state": _rle(20, "A" * 20, 1), "smallest_state": _rle(20, "A" * 20, 1), + "agreement": LARGEST_DEGRADES, }, { "id": "microsatellite_ins_1_unit", - # One GT unit into a GT x 5 tract. seed length 2, so 2 is both the - # greatest and the smallest valid factor. - "input": (_MICROSAT_AC, 1007183, 1007183, "GT"), + # chr1 GT x 5 tract at 1007173-1007183. Insert one GT unit. seed length 2, + # so 2 is both the greatest and the smallest factor > 1; SMALLEST tests 1 + # first and rejects it, since GTGTGTGTGT has no single-base period. + "input": (_CHR1, 1007183, 1007183, "GT"), "normalized_span": (1007173, 1007183), "largest_state": _rle(12, "GT" * 6, 2), "smallest_state": _rle(12, "GT" * 6, 2), + "agreement": SINGLE_VALID_FACTOR, }, { "id": "microsatellite_ins_2_units", - # Two GT units. seed length 4; both 4 and 2 are valid, and the true - # repeat unit is "GT". Demonstrates the mode is not homopolymer-only. - "input": (_MICROSAT_AC, 1007183, 1007183, "GTGT"), + # Same tract, two GT units. seed length 4; LARGEST tests 4 (valid), + # SMALLEST tests 1 (invalid) then 2 (valid), which is the true repeat + # unit. Shows the mode matters beyond single-base repeats. + "input": (_CHR1, 1007183, 1007183, "GTGT"), "normalized_span": (1007173, 1007183), "largest_state": _rle(14, "GT" * 7, 4), "smallest_state": _rle(14, "GT" * 7, 2), + "agreement": DIVERGES, }, { "id": "microsatellite_ins_3_units", - # Three GT units. seed length 6; LARGEST reports 6, SMALLEST 2. - "input": (_MICROSAT_AC, 1007183, 1007183, "GTGTGT"), + # Same tract, three GT units. seed length 6; LARGEST tests 6 (valid), + # SMALLEST tests 1 (invalid) then 2 (valid). + "input": (_CHR1, 1007183, 1007183, "GTGTGT"), "normalized_span": (1007173, 1007183), "largest_state": _rle(16, "GT" * 8, 6), "smallest_state": _rle(16, "GT" * 8, 2), + "agreement": DIVERGES, }, { "id": "tandem_dup_gt", - # Tandem duplication of GT in a tract too short for a larger factor to - # be valid; SMALLEST must not shrink this below the real unit length. - "input": (_GT_REPEAT_AC, 155980373, 155980375, "GTGT"), + # chrX isolated GT at 155980373-155980375: ...GGCTAAG[GT]TAAGTGTAGG... + # No adjacent GT tract, so nothing rolls and the span is unchanged. + # Tandem duplication to GTGT gives seed length 2, whose only factors are + # 2 and 1: LARGEST tests 2 (valid) and nothing larger is ever a + # candidate, while SMALLEST tests 1 and rejects it because "GT" is not a + # single-base repeat. The validity check is what keeps SMALLEST from + # shrinking this below the real unit length. + "input": (_CHRX, 155980373, 155980375, "GTGT"), "normalized_span": (155980373, 155980375), "largest_state": _rle(4, "GTGT", 2), "smallest_state": _rle(4, "GTGT", 2), + "agreement": SINGLE_VALID_FACTOR, }, { "id": "trinucleotide_ins_cag", - # Two CAG units into a CAG tract. seed length 6 exceeds the modified - # reference length, so 3 is selected under both modes. - "input": (_CAG_REPEAT_AC, 289464, 289464, "CAGCAG"), + # chr19 CAG tract at 289464: ...GGTGGCT[]CAGCACTTTGGG... Insert CAGCAG at + # a zero-width span; rolling right expands the modified reference to + # "CAGCA" (5 bases). seed length 6 has factors 6, 3, 2, 1. LARGEST skips + # 6 as longer than the modified reference and tests 3 (valid); SMALLEST + # tests 1 and 2 (both invalid against "CAGCA") before reaching the same 3. + # Both modes converge because 3 is the only valid factor, not merely + # because 6 was too long. + "input": (_CHR19, 289464, 289464, "CAGCAG"), "normalized_span": (289464, 289469), "largest_state": _rle(11, "CAGCAGCAGCA", 3), "smallest_state": _rle(11, "CAGCAGCAGCA", 3), + "agreement": SINGLE_VALID_FACTOR, }, { "id": "deletion_2bp", - # Deletion (step 6.b): repeatSubunitLength is the seed length, no - # factoring, so the mode is irrelevant. - "input": (_GT_REPEAT_AC, 155980375, 155980377, ""), + # Deletion (step 5.b): repeatSubunitLength is the seed length, and step + # 5.c is never reached, so no factor search happens at all. + "input": (_CHRX, 155980375, 155980377, ""), "normalized_span": (155980375, 155980377), "largest_state": _rle(0, "", 2), "smallest_state": _rle(0, "", 2), + "agreement": NO_FACTORING, }, { "id": "reference_agree", - # Reference-agree allele (step 3.a): repeatSubunitLength is the input - # sequence length, no factoring. - "input": (_PLAIN_AC, 26090950, 26090951, "C"), + # Reference-agree allele (step 2.a): returns before rolling, with + # repeatSubunitLength set to the input sequence length. + "input": (_CHR6, 26090950, 26090951, "C"), "normalized_span": (26090950, 26090951), "largest_state": _rle(1, "C", 1), "smallest_state": _rle(1, "C", 1), + "agreement": NO_FACTORING, }, { "id": "substitution", - # Substitution (step 3.b): stays a LiteralSequenceExpression. - "input": (_PLAIN_AC, 26090950, 26090951, "G"), + # Substitution (step 2.b): stays a LiteralSequenceExpression, so there is + # no repeatSubunitLength to select and no factor search. + "input": (_CHR6, 26090950, 26090951, "G"), "normalized_span": (26090950, 26090951), "largest_state": {"type": "LiteralSequenceExpression", "sequence": "G"}, "smallest_state": {"type": "LiteralSequenceExpression", "sequence": "G"}, + "agreement": NO_FACTORING, }, ] @@ -1130,6 +1181,84 @@ def test_normalize_rle_subunit_mode(dataproxy, case, mode): assert normalized == models.Allele(**_expected_allele(case, mode)) +def _normalize_recording_factors( + dataproxy, case: dict, mode: RleSubunitMode, monkeypatch: pytest.MonkeyPatch +) -> tuple[models.Allele, list[tuple[int, bool]]]: + """Normalize `case` under `mode`, recording each step 5.c.1 candidate factor. + + Returns the normalized Allele and the `(cycle_length, is_valid)` pairs the + factor search actually evaluated, in order. Factors longer than the modified + reference sequence are skipped before evaluation and so do not appear. + """ + tried: list[tuple[int, bool]] = [] + unpatched = normalize_module._is_valid_cycle + + def recording_is_valid_cycle( + cycle_start: int, extended_ref_seq: str, extended_alt_seq: str + ) -> bool: + valid: bool = unpatched(cycle_start, extended_ref_seq, extended_alt_seq) + tried.append((len(extended_ref_seq) - cycle_start, valid)) + return valid + + monkeypatch.setattr(normalize_module, "_is_valid_cycle", recording_is_valid_cycle) + allele = models.Allele(**_build_allele(*case["input"])) + normalized = normalize(allele, dataproxy, rle_seq_limit=None, rle_subunit_mode=mode) + return normalized, tried + + +@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) +def test_normalize_rle_subunit_mode_agreement(dataproxy, case, monkeypatch): + """The modes agree or differ for the reason the case declares. + + Equal expected states are not self-explanatory: the modes coincide either + because step 5.c is never reached, because only one candidate factor forms a + valid cycle, or because LARGEST degrades to 1 when the seed length exceeds + the modified reference length. Assert the mechanism so that a case silently + changing category is a failure rather than a still-passing test. + """ + agreement = case["agreement"] + by_mode = { + mode: _normalize_recording_factors(dataproxy, case, mode, monkeypatch) + for mode in RleSubunitMode + } + largest, largest_tried = by_mode[RleSubunitMode.LARGEST] + smallest, smallest_tried = by_mode[RleSubunitMode.SMALLEST] + + if agreement == DIVERGES: + assert largest.state != smallest.state + return + + assert largest.state == smallest.state + + if agreement == NO_FACTORING: + # Step 5.c never runs, so the mode cannot matter for this branch. + assert largest_tried == [] + assert smallest_tried == [] + return + + # The remaining categories do reach the factor search. + assert largest_tried + assert smallest_tried + + evaluated = largest_tried + smallest_tried + valid_factors = {cycle_length for cycle_length, valid in evaluated if valid} + rejected_any = any(not valid for _, valid in evaluated) + + if agreement == SINGLE_VALID_FACTOR: + # A competing factor was evaluated and rejected, which is what forces the + # two search orders onto the same answer. + assert valid_factors == {largest.state.repeatSubunitLength} + assert rejected_any + elif agreement == LARGEST_DEGRADES: + # Nothing was rejected: every factor above 1 was skipped as longer than + # the modified reference sequence, so LARGEST never had a competing + # candidate to prefer and lands on 1 like SMALLEST. + assert largest.state.repeatSubunitLength == 1 + assert not rejected_any + else: + pytest.fail(f"unknown agreement category {agreement!r}") + + @pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) def test_normalize_rle_subunit_mode_default_is_largest(dataproxy, case): """Omitting `rle_subunit_mode` preserves pre-existing (LARGEST) behavior""" @@ -1144,7 +1273,7 @@ def test_normalize_rle_subunit_mode_round_trip(dataproxy, case, mode): """An RLE state denormalizes back to its literal sequence under either mode. `denormalize_reference_length_expression` reconstructs the alternate sequence - from the *prefix* of the reference at the normalized location, while step 6.c + from the *prefix* of the reference at the normalized location, while step 5.c.1 validates the cycle against the *suffix*. Assert the selected `repeatSubunitLength` round-trips for both modes so that `translate_to` output (e.g. SPDI, HGVS) is unaffected by the mode. From da7c064cc830e4eb6da6a1a1a9f1ab0fe791d908 Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Thu, 30 Jul 2026 18:11:45 -0400 Subject: [PATCH 5/9] test(normalize): use REST proxy for RLE mode cases --- ..._denormalize_round_trip[allele_dict6].yaml | 14 + ...ormalize_round_trip[clinvar_deletion].yaml | 62 +++ ...ze_round_trip[clinvar_microsatellite].yaml | 110 +++++ ...rip[clinvar_microsatellite_insertion].yaml | 386 ++++++++++++++++++ ...ize_round_trip[clinvar_tandem_repeat].yaml | 122 ++++++ ...ound_trip[deletion_spanning_boundary].yaml | 26 ++ ...enormalize_round_trip[middle_del_2bp].yaml | 26 ++ ...enormalize_round_trip[middle_ins_4bp].yaml | 26 ++ ..._round_trip[partial_repeat_insertion].yaml | 26 ++ ..._denormalize_round_trip[tail_del_2bp].yaml | 38 ++ ..._denormalize_round_trip[tail_del_4bp].yaml | 26 ++ ...le_subunit_mode[largest-deletion_2bp].yaml | 38 ++ ...de[largest-microsatellite_ins_1_unit].yaml | 170 ++++++++ ..._subunit_mode[largest-poly_a_ins_2bp].yaml | 181 ++++++++ ...subunit_mode[largest-reference_agree].yaml | 37 ++ ...e_subunit_mode[largest-tandem_dup_gt].yaml | 97 +++++ ...t_mode[largest-trinucleotide_ins_cag].yaml | 134 ++++++ tests/test_vrs_normalize.py | 42 +- 18 files changed, 1547 insertions(+), 14 deletions(-) create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml create mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml new file mode 100644 index 00000000..860407c7 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml @@ -0,0 +1,14 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=100210777&end=100210779 + response: + body: + string: AA + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml new file mode 100644 index 00000000..d2074781 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml @@ -0,0 +1,62 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66926&end=66927 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66925&end=66926 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66927&end=66928 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66926&end=66926 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66927&end=66927 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml new file mode 100644 index 00000000..ec8985c7 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml @@ -0,0 +1,110 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766400&end=766404 + response: + body: + string: ATAA + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766399&end=766400 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766398&end=766399 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766404&end=766405 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766405&end=766406 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766406&end=766407 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766407&end=766408 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766404&end=766407 + response: + body: + string: ATA + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766399&end=766407 + response: + body: + string: AATAAATA + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml new file mode 100644 index 00000000..020adb17 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml @@ -0,0 +1,386 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752908&end=1752908 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752907&end=1752908 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752908&end=1752909 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752909&end=1752910 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752910&end=1752911 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752911&end=1752912 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752912&end=1752913 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752913&end=1752914 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752914&end=1752915 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752915&end=1752916 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752916&end=1752917 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752917&end=1752918 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752918&end=1752919 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752919&end=1752920 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752920&end=1752921 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752921&end=1752922 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752922&end=1752923 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752923&end=1752924 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752924&end=1752925 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752925&end=1752926 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752926&end=1752927 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752927&end=1752928 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752928&end=1752929 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752929&end=1752930 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752930&end=1752931 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752931&end=1752932 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752932&end=1752933 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752933&end=1752934 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752934&end=1752935 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752935&end=1752936 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752936&end=1752937 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752908&end=1752936 + response: + body: + string: CCTCCTCCTCCTCCTCCTCCTCCTCCTC + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml new file mode 100644 index 00000000..97634e0f --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml @@ -0,0 +1,122 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930137&end=930140 + response: + body: + string: TCC + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930136&end=930137 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930135&end=930136 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930140&end=930141 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930141&end=930142 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930142&end=930143 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930143&end=930144 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930144&end=930145 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930140&end=930144 + response: + body: + string: TCCT + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930136&end=930144 + response: + body: + string: CTCCTCCT + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml new file mode 100644 index 00000000..d76a01ac --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml @@ -0,0 +1,26 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752912&end=1752916 + response: + body: + string: CTCC + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752916&end=1752916 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml new file mode 100644 index 00000000..8395670f --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml @@ -0,0 +1,26 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752913&end=1752915 + response: + body: + string: TC + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752912&end=1752915 + response: + body: + string: CTC + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml new file mode 100644 index 00000000..f8724823 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml @@ -0,0 +1,26 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752911&end=1752915 + response: + body: + string: CCTC + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752911&end=1752916 + response: + body: + string: CCTCC + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml new file mode 100644 index 00000000..2e0a7d3b --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml @@ -0,0 +1,26 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752915&end=1752915 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752915&end=1752918 + response: + body: + string: CTC + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml new file mode 100644 index 00000000..adb14c44 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752934&end=1752936 + response: + body: + string: TC + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752936&end=1752936 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752933&end=1752936 + response: + body: + string: CTC + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml new file mode 100644 index 00000000..fe8f87b3 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml @@ -0,0 +1,26 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752932&end=1752936 + response: + body: + string: CCTC + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752932&end=1752932 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml new file mode 100644 index 00000000..d6598513 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml @@ -0,0 +1,38 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980375&end=155980377 + response: + body: + string: TA + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980377&end=155980378 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980377&end=155980377 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml new file mode 100644 index 00000000..5b1cc262 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml @@ -0,0 +1,170 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007183 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007182&end=1007183 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007181&end=1007182 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007180&end=1007181 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007179&end=1007180 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007178&end=1007179 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007177&end=1007178 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007176&end=1007177 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007175&end=1007176 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007174&end=1007175 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007174 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007172&end=1007173 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007184 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007183 + response: + body: + string: GTGTGTGTGT + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml new file mode 100644 index 00000000..6085d4d0 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml @@ -0,0 +1,181 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO + response: + body: + string: "{\n \"added\": \"2016-08-27T21:17:00Z\",\n \"aliases\": [\n \"GRCh38:1\",\n + \ \"GRCh38:chr1\",\n \"GRCh38.p1:1\",\n \"GRCh38.p1:chr1\",\n \"GRCh38.p10:1\",\n + \ \"GRCh38.p10:chr1\",\n \"GRCh38.p11:1\",\n \"GRCh38.p11:chr1\",\n + \ \"GRCh38.p12:1\",\n \"GRCh38.p12:chr1\",\n \"GRCh38.p2:1\",\n \"GRCh38.p2:chr1\",\n + \ \"GRCh38.p3:1\",\n \"GRCh38.p3:chr1\",\n \"GRCh38.p4:1\",\n \"GRCh38.p4:chr1\",\n + \ \"GRCh38.p5:1\",\n \"GRCh38.p5:chr1\",\n \"GRCh38.p6:1\",\n \"GRCh38.p6:chr1\",\n + \ \"GRCh38.p7:1\",\n \"GRCh38.p7:chr1\",\n \"GRCh38.p8:1\",\n \"GRCh38.p8:chr1\",\n + \ \"GRCh38.p9:1\",\n \"GRCh38.p9:chr1\",\n \"MD5:6aef897c3d6ff0c78aff06ac189178dd\",\n + \ \"NCBI:NC_000001.11\",\n \"refseq:NC_000001.11\",\n \"SEGUID:FCUd6VJ6uikS/VWLbhGdVmj2rOA\",\n + \ \"SHA1:14251de9527aba2912fd558b6e119d5668f6ace0\",\n \"VMC:GS_Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n + \ \"sha512t24u:Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n \"ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\"\n + \ ],\n \"alphabet\": \"ACGMNRT\",\n \"length\": 248956422\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900418 + response: + body: + string: AAAAAAAAA + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900418 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900417&end=236900418 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900416&end=236900417 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900415&end=236900416 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900414&end=236900415 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900413&end=236900414 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900412&end=236900413 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900411&end=236900412 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900410&end=236900411 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900410 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900408&end=236900409 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900419 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml new file mode 100644 index 00000000..19c99ed1 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml @@ -0,0 +1,37 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV + response: + body: + string: "{\n \"added\": \"2016-08-27T21:22:36Z\",\n \"aliases\": [\n \"GRCh38:6\",\n + \ \"GRCh38:chr6\",\n \"GRCh38.p1:6\",\n \"GRCh38.p1:chr6\",\n \"GRCh38.p10:6\",\n + \ \"GRCh38.p10:chr6\",\n \"GRCh38.p11:6\",\n \"GRCh38.p11:chr6\",\n + \ \"GRCh38.p12:6\",\n \"GRCh38.p12:chr6\",\n \"GRCh38.p2:6\",\n \"GRCh38.p2:chr6\",\n + \ \"GRCh38.p3:6\",\n \"GRCh38.p3:chr6\",\n \"GRCh38.p4:6\",\n \"GRCh38.p4:chr6\",\n + \ \"GRCh38.p5:6\",\n \"GRCh38.p5:chr6\",\n \"GRCh38.p6:6\",\n \"GRCh38.p6:chr6\",\n + \ \"GRCh38.p7:6\",\n \"GRCh38.p7:chr6\",\n \"GRCh38.p8:6\",\n \"GRCh38.p8:chr6\",\n + \ \"GRCh38.p9:6\",\n \"GRCh38.p9:chr6\",\n \"MD5:5691468a67c7e7a7b5f2a3a683792c29\",\n + \ \"NCBI:NC_000006.12\",\n \"refseq:NC_000006.12\",\n \"SEGUID:WZuaTlR1qIRxrJ5dpG2Z0ydeqX4\",\n + \ \"SHA1:599b9a4e5475a88471ac9e5da46d99d3275ea97e\",\n \"VMC:GS_0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV\",\n + \ \"sha512t24u:0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV\",\n \"ga4gh:SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV\"\n + \ ],\n \"alphabet\": \"ACGNTY\",\n \"length\": 170805979\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV?start=26090950&end=26090951 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml new file mode 100644 index 00000000..18bb57ae --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml @@ -0,0 +1,97 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP + response: + body: + string: "{\n \"added\": \"2016-08-27T23:57:18Z\",\n \"aliases\": [\n \"GRCh38:X\",\n + \ \"GRCh38:chrX\",\n \"GRCh38.p1:X\",\n \"GRCh38.p1:chrX\",\n \"GRCh38.p10:X\",\n + \ \"GRCh38.p10:chrX\",\n \"GRCh38.p11:X\",\n \"GRCh38.p11:chrX\",\n + \ \"GRCh38.p12:X\",\n \"GRCh38.p12:chrX\",\n \"GRCh38.p2:X\",\n \"GRCh38.p2:chrX\",\n + \ \"GRCh38.p3:X\",\n \"GRCh38.p3:chrX\",\n \"GRCh38.p4:X\",\n \"GRCh38.p4:chrX\",\n + \ \"GRCh38.p5:X\",\n \"GRCh38.p5:chrX\",\n \"GRCh38.p6:X\",\n \"GRCh38.p6:chrX\",\n + \ \"GRCh38.p7:X\",\n \"GRCh38.p7:chrX\",\n \"GRCh38.p8:X\",\n \"GRCh38.p8:chrX\",\n + \ \"GRCh38.p9:X\",\n \"GRCh38.p9:chrX\",\n \"MD5:2b3a55ff7f58eb308420c8a9b11cac50\",\n + \ \"NCBI:NC_000023.11\",\n \"refseq:NC_000023.11\",\n \"SEGUID:Z9QbQrrPjpjXSMJesDYqC3A43lA\",\n + \ \"SHA1:67d41b42bacf8e98d748c25eb0362a0b7038de50\",\n \"VMC:GS_w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP\",\n + \ \"sha512t24u:w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP\",\n \"ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP\"\n + \ ],\n \"alphabet\": \"ACGNRSTWY\",\n \"length\": 156040895\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980373&end=155980375 + response: + body: + string: GT + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980375&end=155980375 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980374&end=155980375 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980373&end=155980374 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980372&end=155980373 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980375&end=155980376 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml new file mode 100644 index 00000000..6f663ecb --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml @@ -0,0 +1,134 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl + response: + body: + string: "{\n \"added\": \"2016-08-24T08:19:02Z\",\n \"aliases\": [\n \"Ensembl:19\",\n + \ \"ensembl:19\",\n \"GRCh38:19\",\n \"GRCh38:chr19\",\n \"GRCh38.p1:19\",\n + \ \"GRCh38.p1:chr19\",\n \"GRCh38.p10:19\",\n \"GRCh38.p10:chr19\",\n + \ \"GRCh38.p11:19\",\n \"GRCh38.p11:chr19\",\n \"GRCh38.p12:19\",\n + \ \"GRCh38.p12:chr19\",\n \"GRCh38.p2:19\",\n \"GRCh38.p2:chr19\",\n + \ \"GRCh38.p3:19\",\n \"GRCh38.p3:chr19\",\n \"GRCh38.p4:19\",\n \"GRCh38.p4:chr19\",\n + \ \"GRCh38.p5:19\",\n \"GRCh38.p5:chr19\",\n \"GRCh38.p6:19\",\n \"GRCh38.p6:chr19\",\n + \ \"GRCh38.p7:19\",\n \"GRCh38.p7:chr19\",\n \"GRCh38.p8:19\",\n \"GRCh38.p8:chr19\",\n + \ \"GRCh38.p9:19\",\n \"GRCh38.p9:chr19\",\n \"MD5:b0eba2c7bb5c953d1e06a508b5e487de\",\n + \ \"NCBI:NC_000019.10\",\n \"refseq:NC_000019.10\",\n \"SEGUID:AHxM5/L8jIX08UhBBkKXkiO5rhY\",\n + \ \"SHA1:007c4ce7f2fc8c85f4f148410642979223b9ae16\",\n \"VMC:GS_IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\",\n + \ \"sha512t24u:IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\",\n \"ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\"\n + \ ],\n \"alphabet\": \"ACGNT\",\n \"length\": 58617616\n}\n" + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289464&end=289464 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289463&end=289464 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289464&end=289465 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289465&end=289466 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289466&end=289467 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289467&end=289468 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289468&end=289469 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289469&end=289470 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289464&end=289469 + response: + body: + string: CAGCA + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_vrs_normalize.py b/tests/test_vrs_normalize.py index 487525bf..4a5bf45b 100644 --- a/tests/test_vrs_normalize.py +++ b/tests/test_vrs_normalize.py @@ -1174,15 +1174,21 @@ def _expected_allele(case: dict, mode: RleSubunitMode) -> dict: @pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) @pytest.mark.parametrize("mode", list(RleSubunitMode), ids=lambda m: m.value) -def test_normalize_rle_subunit_mode(dataproxy, case, mode): +@pytest.mark.vcr +def test_normalize_rle_subunit_mode(rest_dataproxy, case, mode): """Normalization selects the repeat subunit length dictated by `rle_subunit_mode`""" allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize(allele, dataproxy, rle_seq_limit=None, rle_subunit_mode=mode) + normalized = normalize( + allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode + ) assert normalized == models.Allele(**_expected_allele(case, mode)) def _normalize_recording_factors( - dataproxy, case: dict, mode: RleSubunitMode, monkeypatch: pytest.MonkeyPatch + rest_dataproxy, + case: dict, + mode: RleSubunitMode, + monkeypatch: pytest.MonkeyPatch, ) -> tuple[models.Allele, list[tuple[int, bool]]]: """Normalize `case` under `mode`, recording each step 5.c.1 candidate factor. @@ -1202,12 +1208,15 @@ def recording_is_valid_cycle( monkeypatch.setattr(normalize_module, "_is_valid_cycle", recording_is_valid_cycle) allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize(allele, dataproxy, rle_seq_limit=None, rle_subunit_mode=mode) + normalized = normalize( + allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode + ) return normalized, tried @pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) -def test_normalize_rle_subunit_mode_agreement(dataproxy, case, monkeypatch): +@pytest.mark.vcr +def test_normalize_rle_subunit_mode_agreement(rest_dataproxy, case, monkeypatch): """The modes agree or differ for the reason the case declares. Equal expected states are not self-explanatory: the modes coincide either @@ -1218,7 +1227,7 @@ def test_normalize_rle_subunit_mode_agreement(dataproxy, case, monkeypatch): """ agreement = case["agreement"] by_mode = { - mode: _normalize_recording_factors(dataproxy, case, mode, monkeypatch) + mode: _normalize_recording_factors(rest_dataproxy, case, mode, monkeypatch) for mode in RleSubunitMode } largest, largest_tried = by_mode[RleSubunitMode.LARGEST] @@ -1260,16 +1269,18 @@ def test_normalize_rle_subunit_mode_agreement(dataproxy, case, monkeypatch): @pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) -def test_normalize_rle_subunit_mode_default_is_largest(dataproxy, case): +@pytest.mark.vcr +def test_normalize_rle_subunit_mode_default_is_largest(rest_dataproxy, case): """Omitting `rle_subunit_mode` preserves pre-existing (LARGEST) behavior""" allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize(allele, dataproxy, rle_seq_limit=None) + normalized = normalize(allele, rest_dataproxy, rle_seq_limit=None) assert normalized == models.Allele(**_expected_allele(case, RleSubunitMode.LARGEST)) @pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) @pytest.mark.parametrize("mode", list(RleSubunitMode), ids=lambda m: m.value) -def test_normalize_rle_subunit_mode_round_trip(dataproxy, case, mode): +@pytest.mark.vcr +def test_normalize_rle_subunit_mode_round_trip(rest_dataproxy, case, mode): """An RLE state denormalizes back to its literal sequence under either mode. `denormalize_reference_length_expression` reconstructs the alternate sequence @@ -1279,12 +1290,14 @@ def test_normalize_rle_subunit_mode_round_trip(dataproxy, case, mode): (e.g. SPDI, HGVS) is unaffected by the mode. """ allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize(allele, dataproxy, rle_seq_limit=None, rle_subunit_mode=mode) + normalized = normalize( + allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode + ) if not isinstance(normalized.state, models.ReferenceLengthExpression): pytest.skip(f"{case['id']} does not normalize to a ReferenceLengthExpression") ref_seq = SequenceProxy( - dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" + rest_dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" )[normalized.location.start : normalized.location.end] assert ( @@ -1376,10 +1389,11 @@ def test_normalize_rle_subunit_mode_round_trip(dataproxy, case, mode): @pytest.mark.parametrize("case", rle_denormalize_tests, ids=rle_denormalize_test_ids) -def test_normalize_rle_denormalize_round_trip(dataproxy, case): +@pytest.mark.vcr +def test_normalize_rle_denormalize_round_trip(rest_dataproxy, case): """Corpus alleles round-trip: input -> RLE -> denormalize -> literal alt sequence""" normalized = normalize( - models.Allele(**case["input"]), dataproxy, rle_seq_limit=None + models.Allele(**case["input"]), rest_dataproxy, rle_seq_limit=None ) assert isinstance(normalized.state, models.ReferenceLengthExpression) @@ -1388,7 +1402,7 @@ def test_normalize_rle_denormalize_round_trip(dataproxy, case): assert normalized.state.sequence.root == case["alt"] ref_seq = SequenceProxy( - dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" + rest_dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" )[normalized.location.start : normalized.location.end] assert ( From fa669fc88c315b3b5091e784c2a788191ba1dee6 Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Thu, 30 Jul 2026 18:21:30 -0400 Subject: [PATCH 6/9] fix(normalize): validate RLE subunit mode --- src/ga4gh/vrs/extras/translator.py | 2 +- src/ga4gh/vrs/normalize.py | 6 +- ..._denormalize_round_trip[allele_dict6].yaml | 14 - ...ormalize_round_trip[clinvar_deletion].yaml | 62 --- ...ze_round_trip[clinvar_microsatellite].yaml | 110 ---- ...ize_round_trip[clinvar_tandem_repeat].yaml | 122 ----- ...ound_trip[deletion_spanning_boundary].yaml | 26 - ...enormalize_round_trip[middle_del_2bp].yaml | 26 - ...enormalize_round_trip[middle_ins_4bp].yaml | 26 - ..._round_trip[partial_repeat_insertion].yaml | 26 - ..._denormalize_round_trip[tail_del_2bp].yaml | 38 -- ..._denormalize_round_trip[tail_del_4bp].yaml | 26 - ...l => test_normalize_rle_subunit_mode.yaml} | 159 +++--- ...le_subunit_mode[largest-deletion_2bp].yaml | 38 -- ...de[largest-microsatellite_ins_1_unit].yaml | 170 ------ ..._subunit_mode[largest-poly_a_ins_2bp].yaml | 181 ------- ...subunit_mode[largest-reference_agree].yaml | 37 -- ...e_subunit_mode[largest-tandem_dup_gt].yaml | 97 ---- ...t_mode[largest-trinucleotide_ins_cag].yaml | 134 ----- tests/test_vrs_normalize.py | 490 ++---------------- 20 files changed, 115 insertions(+), 1675 deletions(-) delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml delete mode 100644 tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml rename tests/cassettes/{test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml => test_normalize_rle_subunit_mode.yaml} (65%) delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml diff --git a/src/ga4gh/vrs/extras/translator.py b/src/ga4gh/vrs/extras/translator.py index df57543b..fc0ff335 100644 --- a/src/ga4gh/vrs/extras/translator.py +++ b/src/ga4gh/vrs/extras/translator.py @@ -84,7 +84,7 @@ def __init__( self.data_proxy = data_proxy self.identify = identify self.rle_seq_limit = rle_seq_limit - self.rle_subunit_mode = rle_subunit_mode + self.rle_subunit_mode = RleSubunitMode(rle_subunit_mode) self.from_translators: dict[str, VariationFromStrProtocol] = {} self.to_translators: dict[str, VariationToStrProtocol] = {} diff --git a/src/ga4gh/vrs/normalize.py b/src/ga4gh/vrs/normalize.py index 95b791be..e039a994 100644 --- a/src/ga4gh/vrs/normalize.py +++ b/src/ga4gh/vrs/normalize.py @@ -7,7 +7,7 @@ import itertools import logging from collections.abc import Iterator -from enum import IntEnum, StrEnum +from enum import Enum, IntEnum from typing import NamedTuple from bioutils.normalize import NormalizationMode @@ -21,7 +21,7 @@ _logger = logging.getLogger(__name__) -class RleSubunitMode(StrEnum): +class RleSubunitMode(str, Enum): """Define which repeat subunit length to select for a reference-derived ambiguous insertion normalized to a ``ReferenceLengthExpression``. @@ -141,6 +141,8 @@ def _normalize_allele( `repeatSubunitLength` for a reference-derived ambiguous insertion. Defaults to `RleSubunitMode.LARGEST`, matching VRS <= 2.0. See `RleSubunitMode`. """ + rle_subunit_mode = RleSubunitMode(rle_subunit_mode) + # Algorithm applies to LiteralSequenceExpression alleles only; other states are returned unchanged if not isinstance(input_allele.state, models.LiteralSequenceExpression): _logger.warning( diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml deleted file mode 100644 index 860407c7..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[allele_dict6].yaml +++ /dev/null @@ -1,14 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=100210777&end=100210779 - response: - body: - string: AA - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml deleted file mode 100644 index d2074781..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_deletion].yaml +++ /dev/null @@ -1,62 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66926&end=66927 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66925&end=66926 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66927&end=66928 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66926&end=66926 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=66927&end=66927 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml deleted file mode 100644 index ec8985c7..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite].yaml +++ /dev/null @@ -1,110 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766400&end=766404 - response: - body: - string: ATAA - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766399&end=766400 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766398&end=766399 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766404&end=766405 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766405&end=766406 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766406&end=766407 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766407&end=766408 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766404&end=766407 - response: - body: - string: ATA - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=766399&end=766407 - response: - body: - string: AATAAATA - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml deleted file mode 100644 index 97634e0f..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_tandem_repeat].yaml +++ /dev/null @@ -1,122 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930137&end=930140 - response: - body: - string: TCC - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930136&end=930137 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930135&end=930136 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930140&end=930141 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930141&end=930142 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930142&end=930143 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930143&end=930144 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930144&end=930145 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930140&end=930144 - response: - body: - string: TCCT - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=930136&end=930144 - response: - body: - string: CTCCTCCT - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml deleted file mode 100644 index d76a01ac..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[deletion_spanning_boundary].yaml +++ /dev/null @@ -1,26 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752912&end=1752916 - response: - body: - string: CTCC - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752916&end=1752916 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml deleted file mode 100644 index 8395670f..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_del_2bp].yaml +++ /dev/null @@ -1,26 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752913&end=1752915 - response: - body: - string: TC - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752912&end=1752915 - response: - body: - string: CTC - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml deleted file mode 100644 index f8724823..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[middle_ins_4bp].yaml +++ /dev/null @@ -1,26 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752911&end=1752915 - response: - body: - string: CCTC - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752911&end=1752916 - response: - body: - string: CCTCC - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml deleted file mode 100644 index 2e0a7d3b..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[partial_repeat_insertion].yaml +++ /dev/null @@ -1,26 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752915&end=1752915 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752915&end=1752918 - response: - body: - string: CTC - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml deleted file mode 100644 index adb14c44..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_2bp].yaml +++ /dev/null @@ -1,38 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752934&end=1752936 - response: - body: - string: TC - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752936&end=1752936 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752933&end=1752936 - response: - body: - string: CTC - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml b/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml deleted file mode 100644 index fe8f87b3..00000000 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[tail_del_4bp].yaml +++ /dev/null @@ -1,26 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752932&end=1752936 - response: - body: - string: CCTC - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752932&end=1752932 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml b/tests/cassettes/test_normalize_rle_subunit_mode.yaml similarity index 65% rename from tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml rename to tests/cassettes/test_normalize_rle_subunit_mode.yaml index 020adb17..da2d6ece 100644 --- a/tests/cassettes/test_normalize_rle_denormalize_round_trip[clinvar_microsatellite_insertion].yaml +++ b/tests/cassettes/test_normalize_rle_subunit_mode.yaml @@ -3,10 +3,21 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752908&end=1752908 + uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO response: body: - string: '' + string: "{\n \"added\": \"2016-08-27T21:17:00Z\",\n \"aliases\": [\n \"GRCh38:1\",\n + \ \"GRCh38:chr1\",\n \"GRCh38.p1:1\",\n \"GRCh38.p1:chr1\",\n \"GRCh38.p10:1\",\n + \ \"GRCh38.p10:chr1\",\n \"GRCh38.p11:1\",\n \"GRCh38.p11:chr1\",\n + \ \"GRCh38.p12:1\",\n \"GRCh38.p12:chr1\",\n \"GRCh38.p2:1\",\n \"GRCh38.p2:chr1\",\n + \ \"GRCh38.p3:1\",\n \"GRCh38.p3:chr1\",\n \"GRCh38.p4:1\",\n \"GRCh38.p4:chr1\",\n + \ \"GRCh38.p5:1\",\n \"GRCh38.p5:chr1\",\n \"GRCh38.p6:1\",\n \"GRCh38.p6:chr1\",\n + \ \"GRCh38.p7:1\",\n \"GRCh38.p7:chr1\",\n \"GRCh38.p8:1\",\n \"GRCh38.p8:chr1\",\n + \ \"GRCh38.p9:1\",\n \"GRCh38.p9:chr1\",\n \"MD5:6aef897c3d6ff0c78aff06ac189178dd\",\n + \ \"NCBI:NC_000001.11\",\n \"refseq:NC_000001.11\",\n \"SEGUID:FCUd6VJ6uikS/VWLbhGdVmj2rOA\",\n + \ \"SHA1:14251de9527aba2912fd558b6e119d5668f6ace0\",\n \"VMC:GS_Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n + \ \"sha512t24u:Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n \"ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\"\n + \ ],\n \"alphabet\": \"ACGMNRT\",\n \"length\": 248956422\n}\n" headers: {} status: code: 200 @@ -15,10 +26,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752907&end=1752908 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900418 response: body: - string: C + string: AAAAAAAAA headers: {} status: code: 200 @@ -27,22 +38,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752908&end=1752909 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900418 response: body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752909&end=1752910 - response: - body: - string: C + string: '' headers: {} status: code: 200 @@ -51,10 +50,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752910&end=1752911 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900417&end=236900418 response: body: - string: T + string: A headers: {} status: code: 200 @@ -63,10 +62,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752911&end=1752912 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900416&end=236900417 response: body: - string: C + string: A headers: {} status: code: 200 @@ -75,10 +74,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752912&end=1752913 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900415&end=236900416 response: body: - string: C + string: A headers: {} status: code: 200 @@ -87,10 +86,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752913&end=1752914 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900414&end=236900415 response: body: - string: T + string: A headers: {} status: code: 200 @@ -99,10 +98,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752914&end=1752915 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900413&end=236900414 response: body: - string: C + string: A headers: {} status: code: 200 @@ -111,10 +110,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752915&end=1752916 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900412&end=236900413 response: body: - string: C + string: A headers: {} status: code: 200 @@ -123,10 +122,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752916&end=1752917 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900411&end=236900412 response: body: - string: T + string: A headers: {} status: code: 200 @@ -135,10 +134,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752917&end=1752918 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900410&end=236900411 response: body: - string: C + string: A headers: {} status: code: 200 @@ -147,10 +146,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752918&end=1752919 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900410 response: body: - string: C + string: A headers: {} status: code: 200 @@ -159,7 +158,7 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752919&end=1752920 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900408&end=236900409 response: body: string: T @@ -171,10 +170,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752920&end=1752921 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900419 response: body: - string: C + string: G headers: {} status: code: 200 @@ -183,10 +182,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752921&end=1752922 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007183 response: body: - string: C + string: '' headers: {} status: code: 200 @@ -195,7 +194,7 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752922&end=1752923 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007182&end=1007183 response: body: string: T @@ -207,22 +206,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752923&end=1752924 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007181&end=1007182 response: body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752924&end=1752925 - response: - body: - string: C + string: G headers: {} status: code: 200 @@ -231,7 +218,7 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752925&end=1752926 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007180&end=1007181 response: body: string: T @@ -243,22 +230,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752926&end=1752927 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007179&end=1007180 response: body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752927&end=1752928 - response: - body: - string: C + string: G headers: {} status: code: 200 @@ -267,7 +242,7 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752928&end=1752929 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007178&end=1007179 response: body: string: T @@ -279,22 +254,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752929&end=1752930 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007177&end=1007178 response: body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752930&end=1752931 - response: - body: - string: C + string: G headers: {} status: code: 200 @@ -303,7 +266,7 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752931&end=1752932 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007176&end=1007177 response: body: string: T @@ -315,10 +278,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752932&end=1752933 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007175&end=1007176 response: body: - string: C + string: G headers: {} status: code: 200 @@ -327,10 +290,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752933&end=1752934 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007174&end=1007175 response: body: - string: C + string: T headers: {} status: code: 200 @@ -339,10 +302,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752934&end=1752935 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007174 response: body: - string: T + string: G headers: {} status: code: 200 @@ -351,7 +314,7 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752935&end=1752936 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007172&end=1007173 response: body: string: C @@ -363,10 +326,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752936&end=1752937 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007184 response: body: - string: G + string: C headers: {} status: code: 200 @@ -375,10 +338,10 @@ interactions: body: null headers: {} method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1752908&end=1752936 + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007183 response: body: - string: CCTCCTCCTCCTCCTCCTCCTCCTCCTC + string: GTGTGTGTGT headers: {} status: code: 200 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml deleted file mode 100644 index d6598513..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode[largest-deletion_2bp].yaml +++ /dev/null @@ -1,38 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980375&end=155980377 - response: - body: - string: TA - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980377&end=155980378 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980377&end=155980377 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml deleted file mode 100644 index 5b1cc262..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode[largest-microsatellite_ins_1_unit].yaml +++ /dev/null @@ -1,170 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007183 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007182&end=1007183 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007181&end=1007182 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007180&end=1007181 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007179&end=1007180 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007178&end=1007179 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007177&end=1007178 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007176&end=1007177 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007175&end=1007176 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007174&end=1007175 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007174 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007172&end=1007173 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007184 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007183 - response: - body: - string: GTGTGTGTGT - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml deleted file mode 100644 index 6085d4d0..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode[largest-poly_a_ins_2bp].yaml +++ /dev/null @@ -1,181 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO - response: - body: - string: "{\n \"added\": \"2016-08-27T21:17:00Z\",\n \"aliases\": [\n \"GRCh38:1\",\n - \ \"GRCh38:chr1\",\n \"GRCh38.p1:1\",\n \"GRCh38.p1:chr1\",\n \"GRCh38.p10:1\",\n - \ \"GRCh38.p10:chr1\",\n \"GRCh38.p11:1\",\n \"GRCh38.p11:chr1\",\n - \ \"GRCh38.p12:1\",\n \"GRCh38.p12:chr1\",\n \"GRCh38.p2:1\",\n \"GRCh38.p2:chr1\",\n - \ \"GRCh38.p3:1\",\n \"GRCh38.p3:chr1\",\n \"GRCh38.p4:1\",\n \"GRCh38.p4:chr1\",\n - \ \"GRCh38.p5:1\",\n \"GRCh38.p5:chr1\",\n \"GRCh38.p6:1\",\n \"GRCh38.p6:chr1\",\n - \ \"GRCh38.p7:1\",\n \"GRCh38.p7:chr1\",\n \"GRCh38.p8:1\",\n \"GRCh38.p8:chr1\",\n - \ \"GRCh38.p9:1\",\n \"GRCh38.p9:chr1\",\n \"MD5:6aef897c3d6ff0c78aff06ac189178dd\",\n - \ \"NCBI:NC_000001.11\",\n \"refseq:NC_000001.11\",\n \"SEGUID:FCUd6VJ6uikS/VWLbhGdVmj2rOA\",\n - \ \"SHA1:14251de9527aba2912fd558b6e119d5668f6ace0\",\n \"VMC:GS_Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n - \ \"sha512t24u:Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n \"ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\"\n - \ ],\n \"alphabet\": \"ACGMNRT\",\n \"length\": 248956422\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900418 - response: - body: - string: AAAAAAAAA - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900418 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900417&end=236900418 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900416&end=236900417 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900415&end=236900416 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900414&end=236900415 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900413&end=236900414 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900412&end=236900413 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900411&end=236900412 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900410&end=236900411 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900410 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900408&end=236900409 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900419 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml deleted file mode 100644 index 19c99ed1..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode[largest-reference_agree].yaml +++ /dev/null @@ -1,37 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV - response: - body: - string: "{\n \"added\": \"2016-08-27T21:22:36Z\",\n \"aliases\": [\n \"GRCh38:6\",\n - \ \"GRCh38:chr6\",\n \"GRCh38.p1:6\",\n \"GRCh38.p1:chr6\",\n \"GRCh38.p10:6\",\n - \ \"GRCh38.p10:chr6\",\n \"GRCh38.p11:6\",\n \"GRCh38.p11:chr6\",\n - \ \"GRCh38.p12:6\",\n \"GRCh38.p12:chr6\",\n \"GRCh38.p2:6\",\n \"GRCh38.p2:chr6\",\n - \ \"GRCh38.p3:6\",\n \"GRCh38.p3:chr6\",\n \"GRCh38.p4:6\",\n \"GRCh38.p4:chr6\",\n - \ \"GRCh38.p5:6\",\n \"GRCh38.p5:chr6\",\n \"GRCh38.p6:6\",\n \"GRCh38.p6:chr6\",\n - \ \"GRCh38.p7:6\",\n \"GRCh38.p7:chr6\",\n \"GRCh38.p8:6\",\n \"GRCh38.p8:chr6\",\n - \ \"GRCh38.p9:6\",\n \"GRCh38.p9:chr6\",\n \"MD5:5691468a67c7e7a7b5f2a3a683792c29\",\n - \ \"NCBI:NC_000006.12\",\n \"refseq:NC_000006.12\",\n \"SEGUID:WZuaTlR1qIRxrJ5dpG2Z0ydeqX4\",\n - \ \"SHA1:599b9a4e5475a88471ac9e5da46d99d3275ea97e\",\n \"VMC:GS_0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV\",\n - \ \"sha512t24u:0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV\",\n \"ga4gh:SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV\"\n - \ ],\n \"alphabet\": \"ACGNTY\",\n \"length\": 170805979\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV?start=26090950&end=26090951 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml deleted file mode 100644 index 18bb57ae..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode[largest-tandem_dup_gt].yaml +++ /dev/null @@ -1,97 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP - response: - body: - string: "{\n \"added\": \"2016-08-27T23:57:18Z\",\n \"aliases\": [\n \"GRCh38:X\",\n - \ \"GRCh38:chrX\",\n \"GRCh38.p1:X\",\n \"GRCh38.p1:chrX\",\n \"GRCh38.p10:X\",\n - \ \"GRCh38.p10:chrX\",\n \"GRCh38.p11:X\",\n \"GRCh38.p11:chrX\",\n - \ \"GRCh38.p12:X\",\n \"GRCh38.p12:chrX\",\n \"GRCh38.p2:X\",\n \"GRCh38.p2:chrX\",\n - \ \"GRCh38.p3:X\",\n \"GRCh38.p3:chrX\",\n \"GRCh38.p4:X\",\n \"GRCh38.p4:chrX\",\n - \ \"GRCh38.p5:X\",\n \"GRCh38.p5:chrX\",\n \"GRCh38.p6:X\",\n \"GRCh38.p6:chrX\",\n - \ \"GRCh38.p7:X\",\n \"GRCh38.p7:chrX\",\n \"GRCh38.p8:X\",\n \"GRCh38.p8:chrX\",\n - \ \"GRCh38.p9:X\",\n \"GRCh38.p9:chrX\",\n \"MD5:2b3a55ff7f58eb308420c8a9b11cac50\",\n - \ \"NCBI:NC_000023.11\",\n \"refseq:NC_000023.11\",\n \"SEGUID:Z9QbQrrPjpjXSMJesDYqC3A43lA\",\n - \ \"SHA1:67d41b42bacf8e98d748c25eb0362a0b7038de50\",\n \"VMC:GS_w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP\",\n - \ \"sha512t24u:w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP\",\n \"ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP\"\n - \ ],\n \"alphabet\": \"ACGNRSTWY\",\n \"length\": 156040895\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980373&end=155980375 - response: - body: - string: GT - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980375&end=155980375 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980374&end=155980375 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980373&end=155980374 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980372&end=155980373 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP?start=155980375&end=155980376 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml deleted file mode 100644 index 6f663ecb..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode[largest-trinucleotide_ins_cag].yaml +++ /dev/null @@ -1,134 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl - response: - body: - string: "{\n \"added\": \"2016-08-24T08:19:02Z\",\n \"aliases\": [\n \"Ensembl:19\",\n - \ \"ensembl:19\",\n \"GRCh38:19\",\n \"GRCh38:chr19\",\n \"GRCh38.p1:19\",\n - \ \"GRCh38.p1:chr19\",\n \"GRCh38.p10:19\",\n \"GRCh38.p10:chr19\",\n - \ \"GRCh38.p11:19\",\n \"GRCh38.p11:chr19\",\n \"GRCh38.p12:19\",\n - \ \"GRCh38.p12:chr19\",\n \"GRCh38.p2:19\",\n \"GRCh38.p2:chr19\",\n - \ \"GRCh38.p3:19\",\n \"GRCh38.p3:chr19\",\n \"GRCh38.p4:19\",\n \"GRCh38.p4:chr19\",\n - \ \"GRCh38.p5:19\",\n \"GRCh38.p5:chr19\",\n \"GRCh38.p6:19\",\n \"GRCh38.p6:chr19\",\n - \ \"GRCh38.p7:19\",\n \"GRCh38.p7:chr19\",\n \"GRCh38.p8:19\",\n \"GRCh38.p8:chr19\",\n - \ \"GRCh38.p9:19\",\n \"GRCh38.p9:chr19\",\n \"MD5:b0eba2c7bb5c953d1e06a508b5e487de\",\n - \ \"NCBI:NC_000019.10\",\n \"refseq:NC_000019.10\",\n \"SEGUID:AHxM5/L8jIX08UhBBkKXkiO5rhY\",\n - \ \"SHA1:007c4ce7f2fc8c85f4f148410642979223b9ae16\",\n \"VMC:GS_IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\",\n - \ \"sha512t24u:IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\",\n \"ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl\"\n - \ ],\n \"alphabet\": \"ACGNT\",\n \"length\": 58617616\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289464&end=289464 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289463&end=289464 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289464&end=289465 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289465&end=289466 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289466&end=289467 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289467&end=289468 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289468&end=289469 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289469&end=289470 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl?start=289464&end=289469 - response: - body: - string: CAGCA - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/test_vrs_normalize.py b/tests/test_vrs_normalize.py index 4a5bf45b..dce8061f 100644 --- a/tests/test_vrs_normalize.py +++ b/tests/test_vrs_normalize.py @@ -1,18 +1,7 @@ -from importlib import import_module - import pytest from ga4gh.vrs import models, normalize -from ga4gh.vrs.dataproxy import SequenceProxy -from ga4gh.vrs.normalize import ( - RleSubunitMode, - denormalize_reference_length_expression, -) - -# `ga4gh.vrs.normalize` the attribute is the `normalize` function re-exported by -# `ga4gh.vrs`, so the module itself has to be fetched through importlib in order -# to patch its internals. -normalize_module = import_module("ga4gh.vrs.normalize") +from ga4gh.vrs.normalize import RleSubunitMode # Single nucleotide same-as-reference allele. allele_dict1 = { @@ -945,190 +934,30 @@ def test_normalize_partial_rle_del_ins(rest_dataproxy): ############################################################################# # RLE repeat subunit mode -# -# For a reference-derived ambiguous insertion, more than one factor of the seed -# length may be circularly expandable to recreate the alternate sequence. VRS -# <= 2.0 selects the greatest such factor as the `repeatSubunitLength` -# (`RleSubunitMode.LARGEST`); ga4gh/vrs#700 changes this to the smallest -# (`RleSubunitMode.SMALLEST`). -# -# `repeatSubunitLength` is inherent to the `ReferenceLengthExpression` digest, -# so the two modes can yield different computed identifiers for one input -# Allele. `LARGEST` remains the default for backwards compatibility. -# -# Only the factor search in step 5.c.1 is affected (the reference-derived -# ambiguous insertion returned by 5.d). Reference-agree alleles (2.a) and -# deletions (5.b) take `repeatSubunitLength` from the seed length directly, and -# substitutions (2.b) stay a LiteralSequenceExpression, so all three are -# mode-invariant by construction. -############################################################################# -# One constant per reference sequence; each case's comment names its own locus. _CHR1 = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" # GRCh38 chr1, NC_000001.11 -_CHR6 = "SQ.0iKlIQk2oZLoeOG9P1riRU6hvL5Ux8TV" # GRCh38 chr6, NC_000006.12 -_CHR19 = "SQ.IIB53T8CNeJJdUqzn9V_JnRtQadwWCbl" # GRCh38 chr19, NC_000019.10 -_CHRX = "SQ.w0WZEvgJF0zf_P4yyTzjjv9oW1z61HHP" # GRCh38 chrX, NC_000023.11 - - -def _rle(length: int, sequence: str, repeat_subunit_length: int) -> dict: - """Build an expected ReferenceLengthExpression state dict""" - return { - "type": "ReferenceLengthExpression", - "length": length, - "sequence": sequence, - "repeatSubunitLength": repeat_subunit_length, - } - -# Each case declares the unnormalized input, the span the Allele normalizes to -# (identical under both modes, since only `state` is mode-dependent), the -# expected `state` under each mode, and why the modes agree or differ. -# -# `agreement` records the mechanism, not just the outcome, because the modes -# coincide for three unrelated reasons that would otherwise be indistinguishable: -# -# DIVERGES the modes select different factors -# NO_FACTORING step 5.c is never reached, so no factor search happens -# SINGLE_VALID_FACTOR 5.c is reached but only one factor is a valid cycle, so -# both search orders converge on it -# LARGEST_DEGRADES the seed length exceeds the modified reference length and -# has no smaller usable factor, so LARGEST falls through to -# the same answer SMALLEST reaches directly -# -# Candidate factors quoted below are the ones the algorithm actually tests: the -# search skips any factor greater than the modified reference length without -# evaluating it, then returns the first valid cycle it finds. -DIVERGES = "diverges" -NO_FACTORING = "no_factoring" -SINGLE_VALID_FACTOR = "single_valid_factor" -LARGEST_DEGRADES = "largest_degrades" - -rle_subunit_mode_tests = [ +_rle_subunit_mode_cases = [ { - "id": "poly_a_ins_2bp", - # chr1 A x 9 tract at 236900409-236900418: ...CTATT[AAAAAAAAA]GAAGG... - # Insert AA. seed length 2; LARGEST tests 2 (valid), SMALLEST tests 1 - # (valid). The repeating unit is plainly "A", which only SMALLEST reports. "input": (_CHR1, 236900409, 236900418, "A" * 11), "normalized_span": (236900409, 236900418), - "largest_state": _rle(11, "A" * 11, 2), - "smallest_state": _rle(11, "A" * 11, 1), - "agreement": DIVERGES, - }, - { - "id": "poly_a_ins_9bp", - # Same tract, A x 9 -> A x 18. seed length 9; LARGEST tests 9 (valid) and - # so tracks the insertion size rather than the repeat unit. - "input": (_CHR1, 236900409, 236900418, "A" * 18), - "normalized_span": (236900409, 236900418), - "largest_state": _rle(18, "A" * 18, 9), - "smallest_state": _rle(18, "A" * 18, 1), - "agreement": DIVERGES, - }, - { - "id": "poly_a_ins_11bp_prime_seed", - # Same tract, A x 9 -> A x 20. seed length 11 is prime and exceeds the - # modified reference length (9), so its only factors are 11 (skipped as - # too long) and 1. LARGEST therefore tests 1 first as well and lands on - # the same answer as SMALLEST. This is the non-monotonicity that - # motivated ga4gh/vrs#699. - "input": (_CHR1, 236900409, 236900418, "A" * 20), - "normalized_span": (236900409, 236900418), - "largest_state": _rle(20, "A" * 20, 1), - "smallest_state": _rle(20, "A" * 20, 1), - "agreement": LARGEST_DEGRADES, + "length": 11, + "largest": 2, + "smallest": 1, }, { - "id": "microsatellite_ins_1_unit", - # chr1 GT x 5 tract at 1007173-1007183. Insert one GT unit. seed length 2, - # so 2 is both the greatest and the smallest factor > 1; SMALLEST tests 1 - # first and rejects it, since GTGTGTGTGT has no single-base period. "input": (_CHR1, 1007183, 1007183, "GT"), "normalized_span": (1007173, 1007183), - "largest_state": _rle(12, "GT" * 6, 2), - "smallest_state": _rle(12, "GT" * 6, 2), - "agreement": SINGLE_VALID_FACTOR, + "length": 12, + "largest": 2, + "smallest": 2, }, { - "id": "microsatellite_ins_2_units", - # Same tract, two GT units. seed length 4; LARGEST tests 4 (valid), - # SMALLEST tests 1 (invalid) then 2 (valid), which is the true repeat - # unit. Shows the mode matters beyond single-base repeats. "input": (_CHR1, 1007183, 1007183, "GTGT"), "normalized_span": (1007173, 1007183), - "largest_state": _rle(14, "GT" * 7, 4), - "smallest_state": _rle(14, "GT" * 7, 2), - "agreement": DIVERGES, - }, - { - "id": "microsatellite_ins_3_units", - # Same tract, three GT units. seed length 6; LARGEST tests 6 (valid), - # SMALLEST tests 1 (invalid) then 2 (valid). - "input": (_CHR1, 1007183, 1007183, "GTGTGT"), - "normalized_span": (1007173, 1007183), - "largest_state": _rle(16, "GT" * 8, 6), - "smallest_state": _rle(16, "GT" * 8, 2), - "agreement": DIVERGES, - }, - { - "id": "tandem_dup_gt", - # chrX isolated GT at 155980373-155980375: ...GGCTAAG[GT]TAAGTGTAGG... - # No adjacent GT tract, so nothing rolls and the span is unchanged. - # Tandem duplication to GTGT gives seed length 2, whose only factors are - # 2 and 1: LARGEST tests 2 (valid) and nothing larger is ever a - # candidate, while SMALLEST tests 1 and rejects it because "GT" is not a - # single-base repeat. The validity check is what keeps SMALLEST from - # shrinking this below the real unit length. - "input": (_CHRX, 155980373, 155980375, "GTGT"), - "normalized_span": (155980373, 155980375), - "largest_state": _rle(4, "GTGT", 2), - "smallest_state": _rle(4, "GTGT", 2), - "agreement": SINGLE_VALID_FACTOR, - }, - { - "id": "trinucleotide_ins_cag", - # chr19 CAG tract at 289464: ...GGTGGCT[]CAGCACTTTGGG... Insert CAGCAG at - # a zero-width span; rolling right expands the modified reference to - # "CAGCA" (5 bases). seed length 6 has factors 6, 3, 2, 1. LARGEST skips - # 6 as longer than the modified reference and tests 3 (valid); SMALLEST - # tests 1 and 2 (both invalid against "CAGCA") before reaching the same 3. - # Both modes converge because 3 is the only valid factor, not merely - # because 6 was too long. - "input": (_CHR19, 289464, 289464, "CAGCAG"), - "normalized_span": (289464, 289469), - "largest_state": _rle(11, "CAGCAGCAGCA", 3), - "smallest_state": _rle(11, "CAGCAGCAGCA", 3), - "agreement": SINGLE_VALID_FACTOR, - }, - { - "id": "deletion_2bp", - # Deletion (step 5.b): repeatSubunitLength is the seed length, and step - # 5.c is never reached, so no factor search happens at all. - "input": (_CHRX, 155980375, 155980377, ""), - "normalized_span": (155980375, 155980377), - "largest_state": _rle(0, "", 2), - "smallest_state": _rle(0, "", 2), - "agreement": NO_FACTORING, - }, - { - "id": "reference_agree", - # Reference-agree allele (step 2.a): returns before rolling, with - # repeatSubunitLength set to the input sequence length. - "input": (_CHR6, 26090950, 26090951, "C"), - "normalized_span": (26090950, 26090951), - "largest_state": _rle(1, "C", 1), - "smallest_state": _rle(1, "C", 1), - "agreement": NO_FACTORING, - }, - { - "id": "substitution", - # Substitution (step 2.b): stays a LiteralSequenceExpression, so there is - # no repeatSubunitLength to select and no factor search. - "input": (_CHR6, 26090950, 26090951, "G"), - "normalized_span": (26090950, 26090951), - "largest_state": {"type": "LiteralSequenceExpression", "sequence": "G"}, - "smallest_state": {"type": "LiteralSequenceExpression", "sequence": "G"}, - "agreement": NO_FACTORING, + "length": 14, + "largest": 4, + "smallest": 2, }, ] @@ -1150,266 +979,45 @@ def _build_allele(refget_accession: str, start: int, end: int, sequence: str) -> } -def _expected_allele(case: dict, mode: RleSubunitMode) -> dict: - """Build the expected normalized Allele dict for `case` under `mode`""" - refget_accession = case["input"][0] - start, end = case["normalized_span"] - state = case[ - "largest_state" if mode is RleSubunitMode.LARGEST else "smallest_state" - ] - return { - "type": "Allele", - "location": { - "type": "SequenceLocation", - "sequenceReference": { - "type": "SequenceReference", - "refgetAccession": refget_accession, - }, - "start": start, - "end": end, - }, - "state": state, - } - - -@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) -@pytest.mark.parametrize("mode", list(RleSubunitMode), ids=lambda m: m.value) -@pytest.mark.vcr -def test_normalize_rle_subunit_mode(rest_dataproxy, case, mode): - """Normalization selects the repeat subunit length dictated by `rle_subunit_mode`""" - allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize( - allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode - ) - assert normalized == models.Allele(**_expected_allele(case, mode)) - - -def _normalize_recording_factors( - rest_dataproxy, - case: dict, - mode: RleSubunitMode, - monkeypatch: pytest.MonkeyPatch, -) -> tuple[models.Allele, list[tuple[int, bool]]]: - """Normalize `case` under `mode`, recording each step 5.c.1 candidate factor. - - Returns the normalized Allele and the `(cycle_length, is_valid)` pairs the - factor search actually evaluated, in order. Factors longer than the modified - reference sequence are skipped before evaluation and so do not appear. - """ - tried: list[tuple[int, bool]] = [] - unpatched = normalize_module._is_valid_cycle - - def recording_is_valid_cycle( - cycle_start: int, extended_ref_seq: str, extended_alt_seq: str - ) -> bool: - valid: bool = unpatched(cycle_start, extended_ref_seq, extended_alt_seq) - tried.append((len(extended_ref_seq) - cycle_start, valid)) - return valid - - monkeypatch.setattr(normalize_module, "_is_valid_cycle", recording_is_valid_cycle) - allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize( - allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode - ) - return normalized, tried - - -@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) -@pytest.mark.vcr -def test_normalize_rle_subunit_mode_agreement(rest_dataproxy, case, monkeypatch): - """The modes agree or differ for the reason the case declares. - - Equal expected states are not self-explanatory: the modes coincide either - because step 5.c is never reached, because only one candidate factor forms a - valid cycle, or because LARGEST degrades to 1 when the seed length exceeds - the modified reference length. Assert the mechanism so that a case silently - changing category is a failure rather than a still-passing test. - """ - agreement = case["agreement"] - by_mode = { - mode: _normalize_recording_factors(rest_dataproxy, case, mode, monkeypatch) - for mode in RleSubunitMode - } - largest, largest_tried = by_mode[RleSubunitMode.LARGEST] - smallest, smallest_tried = by_mode[RleSubunitMode.SMALLEST] - - if agreement == DIVERGES: - assert largest.state != smallest.state - return - - assert largest.state == smallest.state - - if agreement == NO_FACTORING: - # Step 5.c never runs, so the mode cannot matter for this branch. - assert largest_tried == [] - assert smallest_tried == [] - return - - # The remaining categories do reach the factor search. - assert largest_tried - assert smallest_tried - - evaluated = largest_tried + smallest_tried - valid_factors = {cycle_length for cycle_length, valid in evaluated if valid} - rejected_any = any(not valid for _, valid in evaluated) - - if agreement == SINGLE_VALID_FACTOR: - # A competing factor was evaluated and rejected, which is what forces the - # two search orders onto the same answer. - assert valid_factors == {largest.state.repeatSubunitLength} - assert rejected_any - elif agreement == LARGEST_DEGRADES: - # Nothing was rejected: every factor above 1 was skipped as longer than - # the modified reference sequence, so LARGEST never had a competing - # candidate to prefer and lands on 1 like SMALLEST. - assert largest.state.repeatSubunitLength == 1 - assert not rejected_any - else: - pytest.fail(f"unknown agreement category {agreement!r}") - - -@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) -@pytest.mark.vcr -def test_normalize_rle_subunit_mode_default_is_largest(rest_dataproxy, case): - """Omitting `rle_subunit_mode` preserves pre-existing (LARGEST) behavior""" - allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize(allele, rest_dataproxy, rle_seq_limit=None) - assert normalized == models.Allele(**_expected_allele(case, RleSubunitMode.LARGEST)) - - -@pytest.mark.parametrize("case", rle_subunit_mode_tests, ids=lambda c: c["id"]) -@pytest.mark.parametrize("mode", list(RleSubunitMode), ids=lambda m: m.value) @pytest.mark.vcr -def test_normalize_rle_subunit_mode_round_trip(rest_dataproxy, case, mode): - """An RLE state denormalizes back to its literal sequence under either mode. - - `denormalize_reference_length_expression` reconstructs the alternate sequence - from the *prefix* of the reference at the normalized location, while step 5.c.1 - validates the cycle against the *suffix*. Assert the selected - `repeatSubunitLength` round-trips for both modes so that `translate_to` output - (e.g. SPDI, HGVS) is unaffected by the mode. - """ - allele = models.Allele(**_build_allele(*case["input"])) - normalized = normalize( - allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode - ) - if not isinstance(normalized.state, models.ReferenceLengthExpression): - pytest.skip(f"{case['id']} does not normalize to a ReferenceLengthExpression") - - ref_seq = SequenceProxy( - rest_dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" - )[normalized.location.start : normalized.location.end] - - assert ( - denormalize_reference_length_expression( - ref_seq=ref_seq, - repeat_subunit_length=normalized.state.repeatSubunitLength, - alt_length=normalized.state.length, - ) - == normalized.state.sequence.root +def test_normalize_rle_subunit_mode(rest_dataproxy): + """Normalization selects the requested RLE subunit and preserves the old default""" + for case in _rle_subunit_mode_cases: + allele = models.Allele(**_build_allele(*case["input"])) + normalized_by_mode = {} + + for mode in RleSubunitMode: + normalized = normalize( + allele, + rest_dataproxy, + rle_seq_limit=None, + rle_subunit_mode=mode, + ) + assert ( + normalized.location.start, + normalized.location.end, + ) == case["normalized_span"] + assert isinstance(normalized.state, models.ReferenceLengthExpression) + assert normalized.state.length == case["length"] + assert normalized.state.repeatSubunitLength == case[mode.value] + normalized_by_mode[mode] = normalized + + default = normalize(allele, rest_dataproxy, rle_seq_limit=None) + assert default == normalized_by_mode[RleSubunitMode.LARGEST] + + poly_a = models.Allele(**_build_allele(*_rle_subunit_mode_cases[0]["input"])) + from_string = normalize( + poly_a, + rest_dataproxy, + rle_seq_limit=None, + rle_subunit_mode="smallest", ) + assert from_string.state.repeatSubunitLength == 1 -# Pre-existing corpus alleles that normalize to a ReferenceLengthExpression, with the -# expected RLE parameters and the literal alternate sequence each reconstructs to. The -# `*_normalized` dicts above cannot supply the alt sequence: most of those tests pass -# `rle_seq_limit=0`, so they omit `state.sequence` entirely. Declaring it here is what -# makes the round trip a real assertion rather than a self-comparison. -# -# `allele_dict2` is intentionally absent: its location uses indefinite `Range` -# positions, so there is no single reference span to reconstruct the alt from. -rle_denormalize_tests = [ - {"input": allele_dict1, "repeat_subunit_length": 1, "length": 1, "alt": "C"}, - {"input": allele_dict4, "repeat_subunit_length": 2, "length": 4, "alt": "GTGT"}, - { - "input": allele_dict5, - "repeat_subunit_length": 3, - "length": 11, - "alt": "CAGCAGCAGCA", - }, - {"input": allele_dict6, "repeat_subunit_length": 2, "length": 2, "alt": "AA"}, - {"input": clinvar_deletion, "repeat_subunit_length": 1, "length": 0, "alt": ""}, - { - "input": clinvar_microsatellite, - "repeat_subunit_length": 4, - "length": 4, - "alt": "AATA", - }, - { - "input": clinvar_tandem_repeat, - "repeat_subunit_length": 3, - "length": 5, - "alt": "CTCCT", - }, - { - "input": clinvar_microsatellite_insertion, - "repeat_subunit_length": 3, - "length": 31, - "alt": "CCTCCTCCTCCTCCTCCTCCTCCTCCTCCTC", - }, - { - "input": partial_repeat_insertion, - "repeat_subunit_length": 2, - "length": 5, - "alt": "CTCTC", - }, - { - "input": middle_ins_4bp, - "repeat_subunit_length": 4, - "length": 9, - "alt": "CCTCCCTCC", - }, - { - "input": deletion_spanning_boundary, - "repeat_subunit_length": 4, - "length": 1, - "alt": "C", - }, - {"input": middle_del_2bp, "repeat_subunit_length": 2, "length": 1, "alt": "C"}, - {"input": tail_del_2bp, "repeat_subunit_length": 2, "length": 1, "alt": "C"}, - {"input": tail_del_4bp, "repeat_subunit_length": 4, "length": 0, "alt": ""}, -] - -rle_denormalize_test_ids = [ - "allele_dict1", - "allele_dict4", - "allele_dict5", - "allele_dict6", - "clinvar_deletion", - "clinvar_microsatellite", - "clinvar_tandem_repeat", - "clinvar_microsatellite_insertion", - "partial_repeat_insertion", - "middle_ins_4bp", - "deletion_spanning_boundary", - "middle_del_2bp", - "tail_del_2bp", - "tail_del_4bp", -] - - -@pytest.mark.parametrize("case", rle_denormalize_tests, ids=rle_denormalize_test_ids) -@pytest.mark.vcr -def test_normalize_rle_denormalize_round_trip(rest_dataproxy, case): - """Corpus alleles round-trip: input -> RLE -> denormalize -> literal alt sequence""" - normalized = normalize( - models.Allele(**case["input"]), rest_dataproxy, rle_seq_limit=None - ) - - assert isinstance(normalized.state, models.ReferenceLengthExpression) - assert normalized.state.repeatSubunitLength == case["repeat_subunit_length"] - assert normalized.state.length == case["length"] - assert normalized.state.sequence.root == case["alt"] - - ref_seq = SequenceProxy( - rest_dataproxy, f"ga4gh:{normalized.location.get_refget_accession()}" - )[normalized.location.start : normalized.location.end] - - assert ( - denormalize_reference_length_expression( - ref_seq=ref_seq, - repeat_subunit_length=normalized.state.repeatSubunitLength, - alt_length=normalized.state.length, - ) - == case["alt"] - ) +@pytest.mark.parametrize("mode", ["typo", None]) +def test_normalize_rle_subunit_mode_invalid(mode): + """Normalization rejects an invalid `rle_subunit_mode`""" + allele = models.Allele(**_build_allele(*_rle_subunit_mode_cases[0]["input"])) + with pytest.raises(ValueError, match="is not a valid RleSubunitMode"): + normalize(allele, rle_subunit_mode=mode) From 2246997c30c058f87b457eb68c8ddffe0f8362aa Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Fri, 2 Oct 2026 17:05:50 -0400 Subject: [PATCH 7/9] refactor(normalize): add DEFAULT_RLE_SUBUNIT_MODE and clean-up docstrings Use one constant for all rle_subunit_mode defaults and describe the modes by VRS version. --- src/ga4gh/vrs/extras/translator.py | 13 ++++------- src/ga4gh/vrs/normalize.py | 36 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/ga4gh/vrs/extras/translator.py b/src/ga4gh/vrs/extras/translator.py index fc0ff335..89416d0b 100644 --- a/src/ga4gh/vrs/extras/translator.py +++ b/src/ga4gh/vrs/extras/translator.py @@ -18,6 +18,7 @@ from ga4gh.vrs.dataproxy import SequenceProxy, _DataProxy from ga4gh.vrs.extras.decorators import lazy_property from ga4gh.vrs.normalize import ( + DEFAULT_RLE_SUBUNIT_MODE, RleSubunitMode, denormalize_reference_length_expression, ) @@ -78,7 +79,7 @@ def __init__( default_assembly_name: str = "GRCh38", identify: bool = True, rle_seq_limit: int | None = 50, - rle_subunit_mode: RleSubunitMode = RleSubunitMode.LARGEST, + rle_subunit_mode: RleSubunitMode = DEFAULT_RLE_SUBUNIT_MODE, ) -> None: self.default_assembly_name = default_assembly_name self.data_proxy = data_proxy @@ -190,15 +191,9 @@ def __init__( data_proxy: _DataProxy, default_assembly_name: str = "GRCh38", identify: bool = True, - rle_subunit_mode: RleSubunitMode = RleSubunitMode.LARGEST, + rle_subunit_mode: RleSubunitMode = DEFAULT_RLE_SUBUNIT_MODE, ) -> None: - """Initialize AlleleTranslator class - - :param rle_subunit_mode: Default mode for selecting the `repeatSubunitLength` of - a reference-derived ambiguous insertion normalized to a - `ReferenceLengthExpression`. Overridable per call via the - `rle_subunit_mode` kwarg. See `RleSubunitMode`. - """ + """Initialize AlleleTranslator class""" super().__init__( data_proxy, default_assembly_name, diff --git a/src/ga4gh/vrs/normalize.py b/src/ga4gh/vrs/normalize.py index e039a994..4209bfb1 100644 --- a/src/ga4gh/vrs/normalize.py +++ b/src/ga4gh/vrs/normalize.py @@ -23,26 +23,24 @@ class RleSubunitMode(str, Enum): """Define which repeat subunit length to select for a reference-derived ambiguous - insertion normalized to a ``ReferenceLengthExpression``. - - For a reference-derived ambiguous insertion, more than one factor of the seed length - may be circularly expandable to recreate the alternate sequence. This selects which - of those candidate factors becomes the ``repeatSubunitLength``. - - ``repeatSubunitLength`` is inherent to the ``ReferenceLengthExpression`` digest, so - the two modes may produce different computed identifiers for the same input Allele. - - :cvar LARGEST: Select the greatest valid factor. VRS <= 2.0 behavior, and the - current default. - :cvar SMALLEST: Select the smallest valid factor. Specified by VRS as of - https://github.com/ga4gh/vrs/pull/700, and will become the default when - vrs-python targets that spec version. + insertion normalized to a ``ReferenceLengthExpression``. More than one factor of + the seed length may be circularly expandable to recreate the alternate sequence; + this selects which of them becomes the ``repeatSubunitLength``. The options are: + LARGEST - Select the greatest valid factor (VRS 2.0.x normalization) + SMALLEST - Select the smallest valid factor (VRS 2.1 normalization) + + ``repeatSubunitLength`` is part of the ``ReferenceLengthExpression`` digest, so the + two modes may produce different identifiers for the same input Allele. """ LARGEST = "largest" SMALLEST = "smallest" +# VRS 2.0.x normalization selects the greatest valid factor +DEFAULT_RLE_SUBUNIT_MODE = RleSubunitMode.LARGEST + + class PosType(IntEnum): """Define the kind of position on a location""" @@ -111,7 +109,7 @@ def _normalize_allele( input_allele: models.Allele, data_proxy: _DataProxy, rle_seq_limit: int = 50, - rle_subunit_mode: RleSubunitMode = RleSubunitMode.LARGEST, + rle_subunit_mode: RleSubunitMode = DEFAULT_RLE_SUBUNIT_MODE, ): """Normalize Allele using "fully-justified" normalization adapted from NCBI's VOCA. Fully-justified normalization expands such ambiguous representation over the @@ -139,7 +137,7 @@ def _normalize_allele( For no limit, set to `None`. :param rle_subunit_mode: Which valid factor of the seed length to select as the `repeatSubunitLength` for a reference-derived ambiguous insertion. Defaults to - `RleSubunitMode.LARGEST`, matching VRS <= 2.0. See `RleSubunitMode`. + `DEFAULT_RLE_SUBUNIT_MODE`. See `RleSubunitMode`. """ rle_subunit_mode = RleSubunitMode(rle_subunit_mode) @@ -339,7 +337,9 @@ def denormalize_reference_length_expression( return alt -def _factor_gen(n: int, mode: RleSubunitMode = RleSubunitMode.LARGEST) -> Iterator[int]: +def _factor_gen( + n: int, mode: RleSubunitMode = DEFAULT_RLE_SUBUNIT_MODE +) -> Iterator[int]: """Yield all factors of an integer `n` :param n: The integer to factor @@ -418,7 +418,7 @@ def normalize(vo, data_proxy: _DataProxy | None = None, **kwargs): of the `sequence`. To exclude `state.sequence`, set to 0. :keyword rle_subunit_mode: Which valid factor of the seed length to select as the `repeatSubunitLength` for a reference-derived ambiguous insertion. Defaults to - `RleSubunitMode.LARGEST`. See `RleSubunitMode`. + `DEFAULT_RLE_SUBUNIT_MODE`. See `RleSubunitMode`. :return: normalized object, or unmodified input object if the normalization algorithm does not provide normalization steps for the given type. :raise TypeError: if given object isn't a pydantic.BaseModel From e467202aa440513cbb1cd2b04bc8d4c14bbe7001 Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Fri, 2 Oct 2026 17:32:53 -0400 Subject: [PATCH 8/9] test: parametrize RLE subunit mode tests and cover translator and SPDI round trip --- .../test_normalize_rle_subunit_mode.yaml | 349 ------------------ ...unit_mode[microsatellite_ins_2_units].yaml | 170 +++++++++ ...lize_rle_subunit_mode[poly_a_ins_2bp].yaml | 134 +++++++ ...t_to_spdi[microsatellite_ins_2_units].yaml | 182 +++++++++ ...mode_smallest_to_spdi[poly_a_ins_2bp].yaml | 14 + ...ubunit_mode_translator[init_smallest].yaml | 158 ++++++++ tests/extras/test_allele_translator.py | 73 ++++ tests/test_vrs_normalize.py | 97 +++-- 8 files changed, 779 insertions(+), 398 deletions(-) delete mode 100644 tests/cassettes/test_normalize_rle_subunit_mode.yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[microsatellite_ins_2_units].yaml create mode 100644 tests/cassettes/test_normalize_rle_subunit_mode[poly_a_ins_2bp].yaml create mode 100644 tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[microsatellite_ins_2_units].yaml create mode 100644 tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[poly_a_ins_2bp].yaml create mode 100644 tests/extras/cassettes/test_rle_subunit_mode_translator[init_smallest].yaml diff --git a/tests/cassettes/test_normalize_rle_subunit_mode.yaml b/tests/cassettes/test_normalize_rle_subunit_mode.yaml deleted file mode 100644 index da2d6ece..00000000 --- a/tests/cassettes/test_normalize_rle_subunit_mode.yaml +++ /dev/null @@ -1,349 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/metadata/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO - response: - body: - string: "{\n \"added\": \"2016-08-27T21:17:00Z\",\n \"aliases\": [\n \"GRCh38:1\",\n - \ \"GRCh38:chr1\",\n \"GRCh38.p1:1\",\n \"GRCh38.p1:chr1\",\n \"GRCh38.p10:1\",\n - \ \"GRCh38.p10:chr1\",\n \"GRCh38.p11:1\",\n \"GRCh38.p11:chr1\",\n - \ \"GRCh38.p12:1\",\n \"GRCh38.p12:chr1\",\n \"GRCh38.p2:1\",\n \"GRCh38.p2:chr1\",\n - \ \"GRCh38.p3:1\",\n \"GRCh38.p3:chr1\",\n \"GRCh38.p4:1\",\n \"GRCh38.p4:chr1\",\n - \ \"GRCh38.p5:1\",\n \"GRCh38.p5:chr1\",\n \"GRCh38.p6:1\",\n \"GRCh38.p6:chr1\",\n - \ \"GRCh38.p7:1\",\n \"GRCh38.p7:chr1\",\n \"GRCh38.p8:1\",\n \"GRCh38.p8:chr1\",\n - \ \"GRCh38.p9:1\",\n \"GRCh38.p9:chr1\",\n \"MD5:6aef897c3d6ff0c78aff06ac189178dd\",\n - \ \"NCBI:NC_000001.11\",\n \"refseq:NC_000001.11\",\n \"SEGUID:FCUd6VJ6uikS/VWLbhGdVmj2rOA\",\n - \ \"SHA1:14251de9527aba2912fd558b6e119d5668f6ace0\",\n \"VMC:GS_Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n - \ \"sha512t24u:Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\",\n \"ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO\"\n - \ ],\n \"alphabet\": \"ACGMNRT\",\n \"length\": 248956422\n}\n" - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900418 - response: - body: - string: AAAAAAAAA - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900418 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900417&end=236900418 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900416&end=236900417 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900415&end=236900416 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900414&end=236900415 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900413&end=236900414 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900412&end=236900413 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900411&end=236900412 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900410&end=236900411 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900410 - response: - body: - string: A - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900408&end=236900409 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900419 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007183 - response: - body: - string: '' - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007182&end=1007183 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007181&end=1007182 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007180&end=1007181 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007179&end=1007180 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007178&end=1007179 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007177&end=1007178 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007176&end=1007177 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007175&end=1007176 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007174&end=1007175 - response: - body: - string: T - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007174 - response: - body: - string: G - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007172&end=1007173 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007184 - response: - body: - string: C - headers: {} - status: - code: 200 - message: OK -- request: - body: null - headers: {} - method: GET - uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007183 - response: - body: - string: GTGTGTGTGT - headers: {} - status: - code: 200 - message: OK -version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[microsatellite_ins_2_units].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[microsatellite_ins_2_units].yaml new file mode 100644 index 00000000..5b1cc262 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[microsatellite_ins_2_units].yaml @@ -0,0 +1,170 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007183 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007182&end=1007183 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007181&end=1007182 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007180&end=1007181 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007179&end=1007180 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007178&end=1007179 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007177&end=1007178 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007176&end=1007177 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007175&end=1007176 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007174&end=1007175 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007174 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007172&end=1007173 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007184 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007183 + response: + body: + string: GTGTGTGTGT + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_normalize_rle_subunit_mode[poly_a_ins_2bp].yaml b/tests/cassettes/test_normalize_rle_subunit_mode[poly_a_ins_2bp].yaml new file mode 100644 index 00000000..27dde796 --- /dev/null +++ b/tests/cassettes/test_normalize_rle_subunit_mode[poly_a_ins_2bp].yaml @@ -0,0 +1,134 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900418 + response: + body: + string: AAAAAAAAA + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900418 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900417&end=236900418 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900416&end=236900417 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900415&end=236900416 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900414&end=236900415 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900411&end=236900412 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900410&end=236900411 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900410 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900408&end=236900409 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900419 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[microsatellite_ins_2_units].yaml b/tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[microsatellite_ins_2_units].yaml new file mode 100644 index 00000000..ac8aefe4 --- /dev/null +++ b/tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[microsatellite_ins_2_units].yaml @@ -0,0 +1,182 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007183 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007182&end=1007183 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007181&end=1007182 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007180&end=1007181 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007179&end=1007180 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007178&end=1007179 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007177&end=1007178 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007176&end=1007177 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007175&end=1007176 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007174&end=1007175 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007174 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007172&end=1007173 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007183&end=1007184 + response: + body: + string: C + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=1007173&end=1007183 + response: + body: + string: GTGTGTGTGT + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/NC_000001.11?start=1007173&end=1007183 + response: + body: + string: GTGTGTGTGT + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[poly_a_ins_2bp].yaml b/tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[poly_a_ins_2bp].yaml new file mode 100644 index 00000000..b12d4ea9 --- /dev/null +++ b/tests/extras/cassettes/test_rle_subunit_mode_smallest_to_spdi[poly_a_ins_2bp].yaml @@ -0,0 +1,14 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/NC_000001.11?start=236900409&end=236900418 + response: + body: + string: AAAAAAAAA + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/extras/cassettes/test_rle_subunit_mode_translator[init_smallest].yaml b/tests/extras/cassettes/test_rle_subunit_mode_translator[init_smallest].yaml new file mode 100644 index 00000000..ff6ed795 --- /dev/null +++ b/tests/extras/cassettes/test_rle_subunit_mode_translator[init_smallest].yaml @@ -0,0 +1,158 @@ +interactions: +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900418 + response: + body: + string: AAAAAAAAA + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900418 + response: + body: + string: '' + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900417&end=236900418 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900416&end=236900417 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900415&end=236900416 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900414&end=236900415 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900413&end=236900414 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900412&end=236900413 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900411&end=236900412 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900410&end=236900411 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900409&end=236900410 + response: + body: + string: A + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900408&end=236900409 + response: + body: + string: T + headers: {} + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: GET + uri: http://localhost:5000/seqrepo/1/sequence/ga4gh:SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO?start=236900418&end=236900419 + response: + body: + string: G + headers: {} + status: + code: 200 + message: OK +version: 1 diff --git a/tests/extras/test_allele_translator.py b/tests/extras/test_allele_translator.py index 15f87a89..a32a3efb 100644 --- a/tests/extras/test_allele_translator.py +++ b/tests/extras/test_allele_translator.py @@ -3,6 +3,7 @@ from ga4gh.vrs import models from ga4gh.vrs.dataproxy import DataProxyValidationError from ga4gh.vrs.extras.translator import AlleleTranslator +from ga4gh.vrs.normalize import RleSubunitMode @pytest.fixture(scope="module") @@ -800,6 +801,78 @@ def test_rle_seq_limit(tlr): assert output_hgvs_expr == [input_hgvs_expr] +# Insert AA into the chr1 A x 9 tract: repeatSubunitLength 2 under LARGEST, 1 under +# SMALLEST +rle_subunit_mode_spdi = "NC_000001.11:236900409:AAAAAAAAA:AAAAAAAAAAA" + +rle_subunit_mode_translator_cases = [ + { + "id": "init_smallest", + "init_mode": RleSubunitMode.SMALLEST, + "call_kwargs": {}, + "expected": 1, + }, + { + "id": "call_overrides_init", + "init_mode": RleSubunitMode.SMALLEST, + "call_kwargs": {"rle_subunit_mode": RleSubunitMode.LARGEST}, + "expected": 2, + }, +] + + +@pytest.mark.parametrize( + "case", rle_subunit_mode_translator_cases, ids=lambda c: c["id"] +) +@pytest.mark.vcr +def test_rle_subunit_mode_translator(rest_dataproxy, case): + """The translator applies its constructor `rle_subunit_mode`, which a per-call + `rle_subunit_mode` overrides + """ + translator = AlleleTranslator( + rest_dataproxy, identify=False, rle_subunit_mode=case["init_mode"] + ) + allele = translator.translate_from( + rle_subunit_mode_spdi, "spdi", **case["call_kwargs"] + ) + assert allele.state.repeatSubunitLength == case["expected"] + + +rle_subunit_mode_smallest_to_spdi_cases = [ + { + "id": "poly_a_ins_2bp", + "spdi": "NC_000001.11:236900409:AAAAAAAAA:AAAAAAAAAAA", + "repeat_subunit_length": 1, + "expected": "NC_000001.11:236900409:9:AAAAAAAAAAA", + }, + { + "id": "microsatellite_ins_2_units", + "spdi": "NC_000001.11:1007183::GTGT", + "repeat_subunit_length": 2, + "expected": "NC_000001.11:1007173:10:GTGTGTGTGTGTGT", + }, +] + + +@pytest.mark.parametrize( + "case", rle_subunit_mode_smallest_to_spdi_cases, ids=lambda c: c["id"] +) +@pytest.mark.vcr +def test_rle_subunit_mode_smallest_to_spdi(tlr, case): + """A SMALLEST RLE without its sequence translates back to the input variant, since + translate_to rebuilds the sequence from the reference and repeatSubunitLength + """ + allele = tlr.translate_from( + case["spdi"], + "spdi", + rle_seq_limit=0, + rle_subunit_mode=RleSubunitMode.SMALLEST, + ) + assert allele.state.repeatSubunitLength == case["repeat_subunit_length"] + assert allele.state.sequence is None + assert tlr.translate_to(allele, "spdi") == [case["expected"]] + + @pytest.mark.vcr def test_to_hgvs_iri_ref_keyerror(tlr): # iriReference is passed diff --git a/tests/test_vrs_normalize.py b/tests/test_vrs_normalize.py index dce8061f..a57c9533 100644 --- a/tests/test_vrs_normalize.py +++ b/tests/test_vrs_normalize.py @@ -1,7 +1,7 @@ import pytest from ga4gh.vrs import models, normalize -from ga4gh.vrs.normalize import RleSubunitMode +from ga4gh.vrs.normalize import RleSubunitMode, _factor_gen # Single nucleotide same-as-reference allele. allele_dict1 = { @@ -937,27 +937,33 @@ def test_normalize_partial_rle_del_ins(rest_dataproxy): _CHR1 = "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO" # GRCh38 chr1, NC_000001.11 -_rle_subunit_mode_cases = [ + +def _rle(length: int, sequence: str, repeat_subunit_length: int) -> dict: + """Build a ReferenceLengthExpression state dict""" + return { + "type": "ReferenceLengthExpression", + "length": length, + "sequence": sequence, + "repeatSubunitLength": repeat_subunit_length, + } + + +rle_subunit_mode_cases = [ { + "id": "poly_a_ins_2bp", + # chr1 A x 9 tract, insert AA: seed length 2, factors 2 and 1 are both valid "input": (_CHR1, 236900409, 236900418, "A" * 11), "normalized_span": (236900409, 236900418), - "length": 11, - "largest": 2, - "smallest": 1, - }, - { - "input": (_CHR1, 1007183, 1007183, "GT"), - "normalized_span": (1007173, 1007183), - "length": 12, - "largest": 2, - "smallest": 2, + "largest": _rle(11, "A" * 11, 2), + "smallest": _rle(11, "A" * 11, 1), }, { + "id": "microsatellite_ins_2_units", + # chr1 GT x 5 tract, insert GTGT: seed length 4, SMALLEST rejects factor 1 "input": (_CHR1, 1007183, 1007183, "GTGT"), "normalized_span": (1007173, 1007183), - "length": 14, - "largest": 4, - "smallest": 2, + "largest": _rle(14, "GT" * 7, 4), + "smallest": _rle(14, "GT" * 7, 2), }, ] @@ -979,45 +985,38 @@ def _build_allele(refget_accession: str, start: int, end: int, sequence: str) -> } +@pytest.mark.parametrize("case", rle_subunit_mode_cases, ids=lambda c: c["id"]) @pytest.mark.vcr -def test_normalize_rle_subunit_mode(rest_dataproxy): - """Normalization selects the requested RLE subunit and preserves the old default""" - for case in _rle_subunit_mode_cases: - allele = models.Allele(**_build_allele(*case["input"])) - normalized_by_mode = {} - - for mode in RleSubunitMode: - normalized = normalize( - allele, - rest_dataproxy, - rle_seq_limit=None, - rle_subunit_mode=mode, - ) - assert ( - normalized.location.start, - normalized.location.end, - ) == case["normalized_span"] - assert isinstance(normalized.state, models.ReferenceLengthExpression) - assert normalized.state.length == case["length"] - assert normalized.state.repeatSubunitLength == case[mode.value] - normalized_by_mode[mode] = normalized - - default = normalize(allele, rest_dataproxy, rle_seq_limit=None) - assert default == normalized_by_mode[RleSubunitMode.LARGEST] - - poly_a = models.Allele(**_build_allele(*_rle_subunit_mode_cases[0]["input"])) - from_string = normalize( - poly_a, - rest_dataproxy, - rle_seq_limit=None, - rle_subunit_mode="smallest", - ) - assert from_string.state.repeatSubunitLength == 1 +def test_normalize_rle_subunit_mode(rest_dataproxy, case): + """Each mode normalizes to its expected state, and the default is LARGEST""" + allele = models.Allele(**_build_allele(*case["input"])) + + normalized_by_mode = {} + for mode in RleSubunitMode: + normalized = normalize( + allele, rest_dataproxy, rle_seq_limit=None, rle_subunit_mode=mode + ) + location = (normalized.location.start, normalized.location.end) + assert location == case["normalized_span"], mode + assert normalized.state.model_dump(exclude_none=True) == case[mode.value], mode + normalized_by_mode[mode] = normalized + + default = normalize(allele, rest_dataproxy, rle_seq_limit=None) + assert default == normalized_by_mode[RleSubunitMode.LARGEST] @pytest.mark.parametrize("mode", ["typo", None]) def test_normalize_rle_subunit_mode_invalid(mode): """Normalization rejects an invalid `rle_subunit_mode`""" - allele = models.Allele(**_build_allele(*_rle_subunit_mode_cases[0]["input"])) + allele = models.Allele(**_build_allele(*rle_subunit_mode_cases[0]["input"])) with pytest.raises(ValueError, match="is not a valid RleSubunitMode"): normalize(allele, rle_subunit_mode=mode) + + +def test_factor_gen(): + """Factors are yielded once each, in descending (LARGEST) or ascending (SMALLEST) + order + """ + largest = [36, 18, 12, 9, 6, 4, 3, 2, 1] + assert list(_factor_gen(36, RleSubunitMode.LARGEST)) == largest + assert list(_factor_gen(36, RleSubunitMode.SMALLEST)) == largest[::-1] From acef36b88015fc4f05e4de9cd6b8436e14eea2a0 Mon Sep 17 00:00:00 2001 From: Kyle Ferriter Date: Fri, 2 Oct 2026 17:46:59 -0400 Subject: [PATCH 9/9] refactor(normalize): yield factors lazily in either RLE subunit mode --- src/ga4gh/vrs/normalize.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/ga4gh/vrs/normalize.py b/src/ga4gh/vrs/normalize.py index 4209bfb1..088f9870 100644 --- a/src/ga4gh/vrs/normalize.py +++ b/src/ga4gh/vrs/normalize.py @@ -348,22 +348,21 @@ def _factor_gen( factor found by the caller is the one selected, so the order determines whether the largest or smallest valid factor wins. """ - upper_factors = [] - lower_factors = [] + paired_factors = [] i = 1 while i * i <= n: if n % i == 0: - upper_factors.append(n // i) - if n // i != i: - lower_factors.append(i) + # i <= sqrt(n) <= n // i. Yield the factor that is already in the requested + # order, and save its pair to yield in reverse once the loop finishes. + if mode == RleSubunitMode.SMALLEST: + factor, pair = i, n // i + else: + factor, pair = n // i, i + yield factor + if pair != factor: + paired_factors.append(pair) i += 1 - - if mode == RleSubunitMode.SMALLEST: - yield from lower_factors - yield from reversed(upper_factors) - else: - yield from upper_factors - yield from reversed(lower_factors) + yield from reversed(paired_factors) def _define_rle_allele(