Skip to content

Commit

Permalink
fix: resolve circular import
Browse files Browse the repository at this point in the history
  • Loading branch information
msto committed Mar 8, 2024
1 parent 18bc1c2 commit 9808c99
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 126 deletions.
45 changes: 0 additions & 45 deletions pybedlite/bed_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
from typing import Tuple
from typing import List
from typing import ClassVar
from typing import Type

from pybedlite.overlap_detector import Interval


"""Maximum BED fields that can be present in a well formed BED file written to specification"""
Expand Down Expand Up @@ -192,45 +189,3 @@ def as_bed_line(self, number_of_output_fields: Optional[int] = None) -> str:
)
fields = self.bed_fields[:number_of_output_fields]
return "\t".join(fields)

def to_interval(self) -> Interval:
"""
Convert a `BedRecord` to an `Interval` instance.
Note that when the `BedRecord` does not have a specified strand, the `Interval`'s negative
attribute is set to False. This mimics the behavior of `OverlapDetector.from_bed()` when
reading a record that does not have a specified strand.
Returns:
An `Interval` corresponding to the same region specified in the record.
"""
return Interval(
refname=self.chrom,
start=self.start,
end=self.end,
negative=self.strand is BedStrand.Negative,
name=self.name,
)

@classmethod
def from_interval(cls: Type["BedRecord"], interval: Interval) -> "BedRecord":
"""
Construct a `BedRecord` instance from an `Interval` instance.
**Note that `Interval` cannot represent a `BedRecord` with a missing strand.**
Converting a record with no strand to `Interval` and then back to `BedRecord` will result in
a record with **positive strand**.
Args:
interval: The `Interval` instance to convert.
Returns:
A `BedRecord` corresponding to the same region specified in the interval.
"""
return cls(
chrom=interval.refname,
start=interval.start,
end=interval.end,
strand=BedStrand.Negative if interval.negative else BedStrand.Positive,
name=interval.name,
)
1 change: 1 addition & 0 deletions pybedlite/bed_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- :class:`~pybedtools.bed_source.BedSource` -- Reader class for parsing BED files and iterate
over their contained records
"""

import io
from typing import IO
from typing import Optional
Expand Down
45 changes: 45 additions & 0 deletions pybedlite/overlap_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@
from typing import List
from typing import Optional
from typing import Set
from typing import Type

import attr
import cgranges as cr

from pybedlite.bed_record import BedStrand
from pybedlite.bed_source import BedSource
from pybedlite.bed_record import BedRecord


@attr.s(frozen=True, auto_attribs=True)
Expand Down Expand Up @@ -100,6 +102,49 @@ def length(self) -> int:
"""Returns the length of the interval."""
return self.end - self.start

def to_bedrecord(self) -> BedRecord:
"""
Convert an `Interval` to a `BedRecord` instance.
**Note that `Interval` cannot represent a `BedRecord` with a missing strand.**
Converting a record with no strand to `Interval` and then back to `BedRecord` will result in
a record with **positive strand**.
Returns:
An `Interval` corresponding to the same region specified in the record.
"""

return BedRecord(
chrom=self.refname,
start=self.start,
end=self.end,
strand=BedStrand.Negative if self.negative else BedStrand.Positive,
name=self.name,
)

@classmethod
def from_bedrecord(cls: Type["Interval"], record: BedRecord) -> "Interval":
"""
Construct an `Interval` from a `BedRecord` instance.
Note that when the `BedRecord` does not have a specified strand, the `Interval`'s negative
attribute is set to False. This mimics the behavior of `OverlapDetector.from_bed()` when
reading a record that does not have a specified strand.
Args:
interval: The `Interval` instance to convert.
Returns:
A `BedRecord` corresponding to the same region specified in the interval.
"""
return cls(
refname=record.chrom,
start=record.start,
end=record.end,
negative=record.strand is BedStrand.Negative,
name=record.name,
)


class OverlapDetector(Iterable[Interval]):
"""Detects and returns overlaps between a set of genomic regions and another genomic region.
Expand Down
52 changes: 52 additions & 0 deletions pybedlite/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import pytest
from typing import List
from pybedlite.bed_record import BedRecord
from pybedlite.bed_record import BedStrand


@pytest.fixture
def bed_records() -> List[BedRecord]:
return [
BedRecord(
chrom="1",
start=100,
end=150,
name="test_record1",
score=100,
strand=BedStrand.Positive,
thick_start=100,
thick_end=100,
item_rgb=(0, 0, 0),
block_count=1,
block_sizes=[50],
block_starts=[0],
),
BedRecord(
chrom="1",
start=200,
end=300,
name="test_record2",
score=100,
strand=BedStrand.Negative,
thick_start=210,
thick_end=290,
item_rgb=(0, 0, 0),
block_count=1,
block_sizes=[100],
block_starts=[0],
),
BedRecord(
chrom="2",
start=200,
end=300,
name="test_record3",
score=None,
strand=None,
thick_start=None,
thick_end=None,
item_rgb=None,
block_count=None,
block_sizes=None,
block_starts=None,
),
]
40 changes: 40 additions & 0 deletions pybedlite/tests/test_overlap_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from pybedlite.overlap_detector import Interval
from pybedlite.overlap_detector import OverlapDetector
from pybedlite.bed_record import BedStrand
from pybedlite.bed_record import BedRecord


def run_test(targets: List[Interval], query: Interval, results: List[Interval]) -> None:
Expand Down Expand Up @@ -148,3 +150,41 @@ def test_iterable() -> None:
assert list(detector) == [a]
detector.add_all([a, b, c, d, e])
assert list(detector) == [a, a, b, c, d, e]


def test_conversion_to_interval(bed_records: List[BedRecord]) -> None:
"""
Test that we can convert a BedRecord to an Interval.
"""

# I don't think pytest.mark.parametrize can accept a fixture and expand over its values.
# For loop it is.
for record in bed_records:
interval = Interval.from_bedrecord(record)

assert interval.refname == record.chrom
assert interval.start == record.start
assert interval.end == record.end
assert interval.negative is (record.strand is BedStrand.Negative)
assert interval.name == record.name


def test_construction_from_interval(bed_records: List[BedRecord]) -> None:
"""
Test that we can convert a BedRecord to an Interval and back.
"""

# I don't think pytest.mark.parametrize can accept a fixture and expand over its values.
# For loop it is.
for record in bed_records:
new_record = Interval.from_bedrecord(record).to_bedrecord()

assert new_record.chrom == record.chrom
assert new_record.start == record.start
assert new_record.end == record.end
assert new_record.name == record.name

if record.strand is None:
assert new_record.strand is BedStrand.Positive
else:
assert new_record.strand is record.strand
81 changes: 0 additions & 81 deletions pybedlite/tests/test_pybedlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,6 @@
from pybedlite.bed_writer import BedWriter
from pybedlite.bed_source import BedSource
from pybedlite.bed_record import BedRecord
from pybedlite.bed_record import BedStrand


@pytest.fixture
def bed_records() -> List[BedRecord]:
return [
BedRecord(
chrom="1",
start=100,
end=150,
name="test_record1",
score=100,
strand=BedStrand.Positive,
thick_start=100,
thick_end=100,
item_rgb=(0, 0, 0),
block_count=1,
block_sizes=[50],
block_starts=[0],
),
BedRecord(
chrom="1",
start=200,
end=300,
name="test_record2",
score=100,
strand=BedStrand.Negative,
thick_start=210,
thick_end=290,
item_rgb=(0, 0, 0),
block_count=1,
block_sizes=[100],
block_starts=[0],
),
BedRecord(
chrom="2",
start=200,
end=300,
name="test_record3",
score=None,
strand=None,
thick_start=None,
thick_end=None,
item_rgb=None,
block_count=None,
block_sizes=None,
block_starts=None,
),
]


SNIPPET_BED = """\
Expand Down Expand Up @@ -252,35 +203,3 @@ def test_preopened_bed_writing(
record_number=i,
num_fields=bed_field_number,
)


@pytest.mark.parametrize("record", bed_records())
def test_conversion_to_interval(record: BedRecord) -> None:
"""
Test that we can convert a BedRecord to an Interval.
"""
interval = record.to_interval()

assert interval.refname == record.chrom
assert interval.start == record.start
assert interval.end == record.end
assert interval.negative is (record.strand is BedStrand.Negative)
assert interval.name == record.name


@pytest.mark.parametrize("record", bed_records())
def test_construction_from_interval(record: BedRecord) -> None:
"""
Test that we can convert a BedRecord to an Interval and back.
"""
new_record = BedRecord.from_interval(record.to_interval())

assert new_record == record.chrom
assert new_record.start == record.start
assert new_record.end == record.end
assert new_record.name == record.name

if record.strand is None:
assert new_record.strand is BedStrand.Positive
else:
assert new_record.strand is record.strand

0 comments on commit 9808c99

Please sign in to comment.