Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/agct/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Provide fast liftover in Python via the ``chainfile`` crate."""

from agct.assembly_registry import Assembly, get_assembly_from_refget_id
from agct.converter import Converter, Strand
from agct.converter import Converter, LiftoverResult, Strand

__all__ = ["Assembly", "Converter", "Strand", "get_assembly_from_refget_id"]
__all__ = [
"Assembly",
"Converter",
"LiftoverResult",
"Strand",
"get_assembly_from_refget_id",
]
25 changes: 17 additions & 8 deletions src/agct/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections.abc import Callable
from enum import Enum
from pathlib import Path
from typing import NamedTuple

from wags_tails import CustomData
from wags_tails.utils.downloads import download_http, handle_gzip
Expand All @@ -22,6 +23,14 @@ class Strand(str, Enum):
NEGATIVE = "-"


class LiftoverResult(NamedTuple):
"""Declare structure of liftover response"""

chrom: str
position: int
strand: Strand


class Converter:
"""Chainfile-based liftover provider for a single sequence to sequence
association.
Expand Down Expand Up @@ -113,18 +122,18 @@ def _download_data(version: str, file: Path) -> None: # noqa: ARG001

def convert_coordinate(
self, chrom: str, pos: int, strand: Strand = Strand.POSITIVE
) -> list[tuple[str, int, Strand]]:
) -> list[LiftoverResult]:
"""Perform liftover for given params

The ``Strand`` enum provides constraints for legal strand values:

.. code-block:: python
.. code-block:: pycon

from agct import Converter, Strand, Assembly
>>> from agct import Converter, Strand, Assembly

c = Converter(Assembly.HG19, Assembly.HG38)
c.convert_coordinate("chr7", 140453136, Strand.POSITIVE)
# returns [['chr7', 140753336, '+']]
>>> c = Converter(Assembly.HG19, Assembly.HG38)
>>> c.convert_coordinate("chr7", 140453136, Strand.POSITIVE)
[LiftoverResult(chrom='chr7', position=140753336, strand=<Strand.POSITIVE: '+'>)]


:param chrom: chromosome name as given in chainfile. Usually e.g. ``"chr7"``.
Expand All @@ -144,7 +153,7 @@ def convert_coordinate(
strand,
)
results = []
formatted_results: list[tuple[str, int, Strand]] = []
formatted_results: list[LiftoverResult] = []
for result in results:
try:
pos = int(result[1])
Expand All @@ -156,5 +165,5 @@ def convert_coordinate(
except ValueError:
_logger.exception("Got invalid Strand value in %s", result)
continue
formatted_results.append((result[0], pos, strand))
formatted_results.append(LiftoverResult(result[0], pos, strand))
return formatted_results
18 changes: 9 additions & 9 deletions tests/test_liftover.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Run liftover tests."""

from agct import Assembly, Converter, Strand
from agct import Assembly, Converter, LiftoverResult, Strand


def test_hg19_to_hg38():
Expand All @@ -9,23 +9,23 @@ def test_hg19_to_hg38():

result = converter.convert_coordinate("chr7", 140439611)
assert len(result) == 1
assert result[0] == ("chr7", 140739811, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 140739811, Strand.POSITIVE)

result = converter.convert_coordinate("chr7", 140439746)
assert len(result) == 1
assert result[0] == ("chr7", 140739946, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 140739946, Strand.POSITIVE)

result = converter.convert_coordinate("chr7", 140439703)
assert len(result) == 1
assert result[0] == ("chr7", 140739903, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 140739903, Strand.POSITIVE)

result = converter.convert_coordinate("chr7", 140453136)
assert len(result) == 1
assert result[0] == ("chr7", 140753336, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 140753336, Strand.POSITIVE)

result = converter.convert_coordinate("chr1", 206072707)
assert len(result) == 1
assert result[0] == ("chr1", 206268644, Strand.NEGATIVE)
assert result[0] == LiftoverResult("chr1", 206268644, Strand.NEGATIVE)

# coordinate exceeds bounds
result = converter.convert_coordinate("chr7", 14040053136)
Expand All @@ -38,12 +38,12 @@ def test_hg38_to_hg19():

result = converter.convert_coordinate("chr7", 140739811)
assert len(result) == 1
assert result[0] == ("chr7", 140439611, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 140439611, Strand.POSITIVE)

result = converter.convert_coordinate("chr7", 140759820)
assert len(result) == 1
assert result[0] == ("chr7", 140459620, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 140459620, Strand.POSITIVE)

result = converter.convert_coordinate("chr7", 60878240)
assert len(result) == 1
assert result[0] == ("chr7", 61646115, Strand.POSITIVE)
assert result[0] == LiftoverResult("chr7", 61646115, Strand.POSITIVE)