Source code for GenomeUtils.genome.locus

#!/usr/bin/env python
"""
Filename: GenomeUtils/genome/locus.py
Author: Arash Ayat
Copyright: 2025, Alexander Schliep
Version: 0.1.3
Description: This file defines the Locus class, representing a genomic location.
License: LGPL-3.0-or-later
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal


[docs] @dataclass(frozen=True, order=True) class Locus: """Represents a 1-based inclusive genomic coordinates on a chromosome.""" chr: str start: int end: int strand: Literal["+", "-"] = "+" def __post_init__(self): """Validate coordinates after initialization.""" if self.start > self.end: raise ValueError("Start coordinate cannot be greater than end coordinate.") if self.start < 1: raise ValueError("Start coordinate cannot be less than 1.") def __len__(self) -> int: """Return the length of the locus.""" return self.end - self.start + 1 def __repr__(self): return f"{self.__class__.__name__}({self.chr}:{self.start}-{self.end}, strand={self.strand})" def __str__(self): return f"{self.chr}:{self.start}-{self.end},{self.strand}" def __eq__(self, other: Locus) -> bool: return self.chr == other.chr and self.start == other.start and self.end == other.end and self.strand == other.strand def __hash__(self) -> int: return hash((self.chr, self.start, self.end, self.strand))
[docs] def overlaps(self, other: Locus) -> bool: """Check if this locus overlaps with another.""" if self.chr != other.chr: return False return self.end >= other.start and self.start <= other.end
[docs] def contains(self, other: Locus) -> bool: """Check if this locus completely contains another.""" if self.chr != other.chr: return False return self.start <= other.start and self.end >= other.end
[docs] @classmethod def from_string(cls, string: str) -> Locus: """Create a Locus from a string representation.""" parts = string.split(":") if len(parts) != 2: raise ValueError(f"Invalid locus string: {string}") chr, rest = parts strand_parts = rest.split(",") if len(strand_parts) != 2: raise ValueError(f"Invalid locus string: {string}") coords_str, strand = strand_parts coord_parts = coords_str.split("-") if len(coord_parts) != 2: raise ValueError(f"Invalid locus string: {string}") start_str, end_str = coord_parts if not start_str.isdigit() or not end_str.isdigit(): raise ValueError(f"Invalid locus string: {string}") start = int(start_str) end = int(end_str) return cls(chr=chr, start=start, end=end, strand=strand)