bedspec 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CONTRIBUTING.md +94 -0
- LICENSE +21 -0
- bedspec/__init__.py +43 -0
- bedspec/_bedspec.py +370 -0
- bedspec/_reader.py +94 -0
- bedspec/_writer.py +29 -0
- bedspec/overlap/__init__.py +5 -0
- bedspec/overlap/_overlap.py +86 -0
- bedspec/py.typed +0 -0
- bedspec-0.6.0.dist-info/LICENSE +21 -0
- bedspec-0.6.0.dist-info/METADATA +155 -0
- bedspec-0.6.0.dist-info/RECORD +13 -0
- bedspec-0.6.0.dist-info/WHEEL +4 -0
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Development and Testing
|
|
2
|
+
|
|
3
|
+
## Primary Development Commands
|
|
4
|
+
|
|
5
|
+
To check and resolve linting issues in the codebase, run:
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
poetry run ruff check --fix
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
To check and resolve formatting issues in the codebase, run:
|
|
12
|
+
|
|
13
|
+
```console
|
|
14
|
+
poetry run ruff format
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
To check the unit tests in the codebase, run:
|
|
18
|
+
|
|
19
|
+
```console
|
|
20
|
+
poetry run pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To check the typing in the codebase, run:
|
|
24
|
+
|
|
25
|
+
```console
|
|
26
|
+
poetry run mypy
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To generate a code coverage report after testing locally, run:
|
|
30
|
+
|
|
31
|
+
```console
|
|
32
|
+
poetry run coverage html
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To check the lock file is up-to-date:
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
poetry check --lock
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Shortcut Task Commands
|
|
42
|
+
|
|
43
|
+
To be able to run shortcut task commands, first install the Poetry plugin [`poethepoet`](https://poethepoet.natn.io/index.html):
|
|
44
|
+
|
|
45
|
+
```console
|
|
46
|
+
poetry self add 'poethepoet[poetry_plugin]'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> [!NOTE]
|
|
50
|
+
> Upon the release of Poetry [v2.0.0](https://github.com/orgs/python-poetry/discussions/9793#discussioncomment-11043205), Poetry will automatically support bootstrap installation of [project-specific plugins](https://github.com/python-poetry/poetry/pull/9547) and installation of the task runner will become automatic for this project.
|
|
51
|
+
> The `pyproject.toml` syntax will be:
|
|
52
|
+
>
|
|
53
|
+
> ```toml
|
|
54
|
+
> [tool.poetry]
|
|
55
|
+
> requires-poetry = ">=2.0"
|
|
56
|
+
>
|
|
57
|
+
> [tool.poetry.requires-plugins]
|
|
58
|
+
> poethepoet = ">=0.29"
|
|
59
|
+
> ```
|
|
60
|
+
|
|
61
|
+
### For Running Individual Checks
|
|
62
|
+
|
|
63
|
+
```console
|
|
64
|
+
poetry task check-lock
|
|
65
|
+
poetry task check-format
|
|
66
|
+
poetry task check-lint
|
|
67
|
+
poetry task check-tests
|
|
68
|
+
poetry task check-typing
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### For Running All Checks
|
|
72
|
+
|
|
73
|
+
```console
|
|
74
|
+
poetry task check-all
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### For Running Individual Fixes
|
|
78
|
+
|
|
79
|
+
```console
|
|
80
|
+
poetry task fix-format
|
|
81
|
+
poetry task fix-lint
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### For Running All Fixes
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
poetry task fix-all
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### For Running All Fixes and Checks
|
|
91
|
+
|
|
92
|
+
```console
|
|
93
|
+
poetry task fix-and-check-all
|
|
94
|
+
```
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2024 Clint Valentine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
bedspec/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from ._bedspec import Bed2
|
|
2
|
+
from ._bedspec import Bed3
|
|
3
|
+
from ._bedspec import Bed4
|
|
4
|
+
from ._bedspec import Bed5
|
|
5
|
+
from ._bedspec import Bed6
|
|
6
|
+
from ._bedspec import Bed12
|
|
7
|
+
from ._bedspec import BedColor
|
|
8
|
+
from ._bedspec import BedGraph
|
|
9
|
+
from ._bedspec import BedLike
|
|
10
|
+
from ._bedspec import BedPE
|
|
11
|
+
from ._bedspec import BedStrand
|
|
12
|
+
from ._bedspec import BedType
|
|
13
|
+
from ._bedspec import Named
|
|
14
|
+
from ._bedspec import PairBed
|
|
15
|
+
from ._bedspec import PointBed
|
|
16
|
+
from ._bedspec import ReferenceSpan
|
|
17
|
+
from ._bedspec import SimpleBed
|
|
18
|
+
from ._bedspec import Stranded
|
|
19
|
+
from ._reader import BedReader
|
|
20
|
+
from ._writer import BedWriter
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"Bed2",
|
|
24
|
+
"Bed3",
|
|
25
|
+
"Bed4",
|
|
26
|
+
"Bed5",
|
|
27
|
+
"Bed6",
|
|
28
|
+
"Bed12",
|
|
29
|
+
"BedColor",
|
|
30
|
+
"BedGraph",
|
|
31
|
+
"BedLike",
|
|
32
|
+
"BedPE",
|
|
33
|
+
"BedStrand",
|
|
34
|
+
"BedType",
|
|
35
|
+
"Named",
|
|
36
|
+
"PairBed",
|
|
37
|
+
"PointBed",
|
|
38
|
+
"ReferenceSpan",
|
|
39
|
+
"SimpleBed",
|
|
40
|
+
"Stranded",
|
|
41
|
+
"BedReader",
|
|
42
|
+
"BedWriter",
|
|
43
|
+
]
|
bedspec/_bedspec.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
from dataclasses import Field
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from dataclasses import field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from enum import unique
|
|
10
|
+
from typing import Any
|
|
11
|
+
from typing import ClassVar
|
|
12
|
+
from typing import Protocol
|
|
13
|
+
from typing import TypeVar
|
|
14
|
+
from typing import final
|
|
15
|
+
from typing import runtime_checkable
|
|
16
|
+
|
|
17
|
+
from typing_extensions import Self
|
|
18
|
+
from typing_extensions import override
|
|
19
|
+
|
|
20
|
+
COMMENT_PREFIXES: set[str] = {"#", "browser", "track"}
|
|
21
|
+
"""The set of BED comment prefixes that this library supports."""
|
|
22
|
+
|
|
23
|
+
MISSING_FIELD: str = "."
|
|
24
|
+
"""The string used to indicate a missing field in a BED record."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@runtime_checkable
|
|
28
|
+
class DataclassInstance(Protocol):
|
|
29
|
+
"""A protocol for objects that are dataclass instances."""
|
|
30
|
+
|
|
31
|
+
__dataclass_fields__: ClassVar[dict[str, Field[Any]]]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@unique
|
|
35
|
+
class BedStrand(str, Enum):
|
|
36
|
+
"""BED strands for forward and reverse orientations."""
|
|
37
|
+
|
|
38
|
+
Positive = "+"
|
|
39
|
+
"""The positive BED strand."""
|
|
40
|
+
|
|
41
|
+
Negative = "-"
|
|
42
|
+
"""The negative BED strand."""
|
|
43
|
+
|
|
44
|
+
def opposite(self) -> "BedStrand":
|
|
45
|
+
"""Return the opposite BED strand."""
|
|
46
|
+
if self is BedStrand.Positive:
|
|
47
|
+
return BedStrand.Negative
|
|
48
|
+
else:
|
|
49
|
+
return BedStrand.Positive
|
|
50
|
+
|
|
51
|
+
@override
|
|
52
|
+
def __str__(self) -> str:
|
|
53
|
+
"""Return this strand as a string."""
|
|
54
|
+
return self.value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@runtime_checkable
|
|
58
|
+
class ReferenceSpan(Protocol):
|
|
59
|
+
"""A structural protocol for 0-based half-open objects located on a reference sequence."""
|
|
60
|
+
|
|
61
|
+
refname: str
|
|
62
|
+
start: int
|
|
63
|
+
end: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@runtime_checkable
|
|
67
|
+
class Named(Protocol):
|
|
68
|
+
"""A structural protocol for a named BED type."""
|
|
69
|
+
|
|
70
|
+
name: str | None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@runtime_checkable
|
|
74
|
+
class Stranded(Protocol):
|
|
75
|
+
"""A structural protocol for stranded BED types."""
|
|
76
|
+
|
|
77
|
+
strand: BedStrand | None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class BedLike(ABC, DataclassInstance):
|
|
81
|
+
"""An abstract base class for all types of BED records."""
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def territory(self) -> Iterator[ReferenceSpan]:
|
|
85
|
+
"""Return intervals that describe the territory of this BED record."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
BedType = TypeVar("BedType", bound=BedLike)
|
|
89
|
+
"""A type variable for any kind of BED record type."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class PointBed(BedLike, ABC):
|
|
94
|
+
"""An abstract class for a BED record that describes a 0-based 1-length point."""
|
|
95
|
+
|
|
96
|
+
refname: str
|
|
97
|
+
start: int
|
|
98
|
+
|
|
99
|
+
def __init_subclass__(cls) -> None:
|
|
100
|
+
if not dataclasses.is_dataclass(cls):
|
|
101
|
+
raise TypeError("You must annotate custom BED class definitions with @dataclass!")
|
|
102
|
+
return super().__init_subclass__()
|
|
103
|
+
|
|
104
|
+
@final
|
|
105
|
+
def __len__(self) -> int:
|
|
106
|
+
"""The length of this record."""
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
@override
|
|
110
|
+
def territory(self) -> Iterator[ReferenceSpan]:
|
|
111
|
+
"""Return the territory of a single point BED record which is 1-length."""
|
|
112
|
+
yield Bed3(refname=self.refname, start=self.start, end=self.start + 1)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class SimpleBed(BedLike, ReferenceSpan, ABC):
|
|
117
|
+
"""An abstract class for a BED record that describes a contiguous linear interval."""
|
|
118
|
+
|
|
119
|
+
refname: str
|
|
120
|
+
start: int
|
|
121
|
+
end: int
|
|
122
|
+
|
|
123
|
+
def __init_subclass__(cls) -> None:
|
|
124
|
+
if not dataclasses.is_dataclass(cls):
|
|
125
|
+
raise TypeError("You must annotate custom BED class definitions with @dataclass!")
|
|
126
|
+
return super().__init_subclass__()
|
|
127
|
+
|
|
128
|
+
def __post_init__(self) -> None:
|
|
129
|
+
"""Validate this linear BED record."""
|
|
130
|
+
if self.start >= self.end or self.start < 0:
|
|
131
|
+
raise ValueError("start must be greater than 0 and less than end!")
|
|
132
|
+
|
|
133
|
+
@final
|
|
134
|
+
def __len__(self) -> int:
|
|
135
|
+
"""The length of this record."""
|
|
136
|
+
return self.end - self.start
|
|
137
|
+
|
|
138
|
+
@override
|
|
139
|
+
def territory(self) -> Iterator[ReferenceSpan]:
|
|
140
|
+
"""Return the territory of a linear BED record which is just itself."""
|
|
141
|
+
yield self
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@dataclass
|
|
145
|
+
class PairBed(BedLike, ABC):
|
|
146
|
+
"""An abstract base class for a BED record that describes a pair of linear linear intervals."""
|
|
147
|
+
|
|
148
|
+
refname1: str
|
|
149
|
+
start1: int
|
|
150
|
+
end1: int
|
|
151
|
+
refname2: str
|
|
152
|
+
start2: int
|
|
153
|
+
end2: int
|
|
154
|
+
|
|
155
|
+
def __init_subclass__(cls) -> None:
|
|
156
|
+
if not dataclasses.is_dataclass(cls):
|
|
157
|
+
raise TypeError("You must annotate custom BED class definitions with @dataclass!")
|
|
158
|
+
return super().__init_subclass__()
|
|
159
|
+
|
|
160
|
+
def __post_init__(self) -> None:
|
|
161
|
+
"""Validate this pair of BED records."""
|
|
162
|
+
if self.start1 >= self.end1 or self.start1 < 0:
|
|
163
|
+
raise ValueError("start1 must be greater than 0 and less than end1!")
|
|
164
|
+
if self.start2 >= self.end2 or self.start2 < 0:
|
|
165
|
+
raise ValueError("start2 must be greater than 0 and less than end2!")
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def bed1(self) -> SimpleBed:
|
|
169
|
+
"""The first of the two intervals."""
|
|
170
|
+
return Bed3(refname=self.refname1, start=self.start1, end=self.end1)
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def bed2(self) -> SimpleBed:
|
|
174
|
+
"""The second of the two intervals."""
|
|
175
|
+
return Bed3(refname=self.refname2, start=self.start2, end=self.end2)
|
|
176
|
+
|
|
177
|
+
@override
|
|
178
|
+
def territory(self) -> Iterator[ReferenceSpan]:
|
|
179
|
+
"""Return the territory of this BED record which are two intervals."""
|
|
180
|
+
yield self.bed1
|
|
181
|
+
yield self.bed2
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
185
|
+
class BedColor:
|
|
186
|
+
"""The color of a BED record in red, green, and blue color values."""
|
|
187
|
+
|
|
188
|
+
r: int
|
|
189
|
+
g: int
|
|
190
|
+
b: int
|
|
191
|
+
|
|
192
|
+
def __post_init__(self) -> None:
|
|
193
|
+
"""Validate that all color values are well-formatted."""
|
|
194
|
+
if any(value > 255 or value < 0 for value in (self.r, self.g, self.b)):
|
|
195
|
+
raise ValueError(f"RGB color values must be in the range [0, 255] but found: {self}")
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def from_string(cls, string: str) -> Self:
|
|
199
|
+
"""Build a BED color instance from a string."""
|
|
200
|
+
try:
|
|
201
|
+
r, g, b = map(int, string.split(","))
|
|
202
|
+
except ValueError as error:
|
|
203
|
+
raise ValueError(f"Invalid string '{string}'. Expected 'int,int,int'!") from error
|
|
204
|
+
return cls(r, g, b)
|
|
205
|
+
|
|
206
|
+
@override
|
|
207
|
+
def __str__(self) -> str:
|
|
208
|
+
"""Return a comma-delimited string representation of this BED color."""
|
|
209
|
+
return f"{self.r},{self.g},{self.b}"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
213
|
+
class Bed2(PointBed):
|
|
214
|
+
"""A BED2 record that describes a single 0-based 1-length point."""
|
|
215
|
+
|
|
216
|
+
refname: str
|
|
217
|
+
start: int
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
221
|
+
class Bed3(SimpleBed):
|
|
222
|
+
"""A BED3 record that describes a contiguous linear interval."""
|
|
223
|
+
|
|
224
|
+
refname: str
|
|
225
|
+
start: int = field(kw_only=True)
|
|
226
|
+
end: int = field(kw_only=True)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
230
|
+
class Bed4(SimpleBed):
|
|
231
|
+
"""A BED4 record that describes a contiguous linear interval."""
|
|
232
|
+
|
|
233
|
+
refname: str
|
|
234
|
+
start: int = field(kw_only=True)
|
|
235
|
+
end: int = field(kw_only=True)
|
|
236
|
+
name: str | None = field(kw_only=True)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
240
|
+
class Bed5(SimpleBed, Named):
|
|
241
|
+
"""A BED5 record that describes a contiguous linear interval."""
|
|
242
|
+
|
|
243
|
+
refname: str
|
|
244
|
+
start: int = field(kw_only=True)
|
|
245
|
+
end: int = field(kw_only=True)
|
|
246
|
+
name: str | None = field(kw_only=True)
|
|
247
|
+
score: int | None = field(kw_only=True)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
251
|
+
class Bed6(SimpleBed, Named, Stranded):
|
|
252
|
+
"""A BED6 record that describes a contiguous linear interval."""
|
|
253
|
+
|
|
254
|
+
refname: str
|
|
255
|
+
start: int = field(kw_only=True)
|
|
256
|
+
end: int = field(kw_only=True)
|
|
257
|
+
name: str | None = field(kw_only=True)
|
|
258
|
+
score: int | None = field(kw_only=True)
|
|
259
|
+
strand: BedStrand | None = field(kw_only=True)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
263
|
+
class Bed12(SimpleBed, Named, Stranded):
|
|
264
|
+
"""A BED12 record that describes a contiguous linear interval."""
|
|
265
|
+
|
|
266
|
+
refname: str
|
|
267
|
+
start: int = field(kw_only=True)
|
|
268
|
+
end: int = field(kw_only=True)
|
|
269
|
+
name: str | None = field(kw_only=True)
|
|
270
|
+
score: int | None = field(kw_only=True)
|
|
271
|
+
strand: BedStrand | None = field(kw_only=True)
|
|
272
|
+
thick_start: int | None = field(kw_only=True)
|
|
273
|
+
thick_end: int | None = field(kw_only=True)
|
|
274
|
+
item_rgb: BedColor | None = field(kw_only=True)
|
|
275
|
+
block_count: int | None = field(kw_only=True)
|
|
276
|
+
block_sizes: list[int] | None = field(kw_only=True)
|
|
277
|
+
block_starts: list[int] | None = field(kw_only=True)
|
|
278
|
+
|
|
279
|
+
def __post_init__(self) -> None:
|
|
280
|
+
"""Validate this BED12 record."""
|
|
281
|
+
super(Bed12, self).__post_init__()
|
|
282
|
+
if (self.thick_start is None) != (self.thick_end is None):
|
|
283
|
+
raise ValueError("thick_start and thick_end must both be None or both be set!")
|
|
284
|
+
if self.block_count is None:
|
|
285
|
+
if self.block_sizes is not None or self.block_starts is not None:
|
|
286
|
+
raise ValueError("block_count, block_sizes, block_starts must all be set or unset!")
|
|
287
|
+
else:
|
|
288
|
+
if self.block_sizes is None or self.block_starts is None:
|
|
289
|
+
raise ValueError("block_count, block_sizes, block_starts must all be set or unset!")
|
|
290
|
+
if self.block_count <= 0:
|
|
291
|
+
raise ValueError("When set, block_count must be greater than or equal to 1!")
|
|
292
|
+
if self.block_count != len(self.block_sizes) or self.block_count != len(
|
|
293
|
+
self.block_starts
|
|
294
|
+
):
|
|
295
|
+
raise ValueError("Length of block_sizes and block_starts must equal block_count!")
|
|
296
|
+
if self.block_starts[0] != 0:
|
|
297
|
+
raise ValueError("block_starts must start with 0!")
|
|
298
|
+
if any(size <= 0 for size in self.block_sizes):
|
|
299
|
+
raise ValueError("All sizes in block_size must be greater than or equal to one!")
|
|
300
|
+
if (self.start + self.block_starts[-1] + self.block_sizes[-1]) != self.end:
|
|
301
|
+
raise ValueError("The last defined block's end must be equal to the BED end!")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
305
|
+
class BedGraph(SimpleBed):
|
|
306
|
+
"""A bedGraph feature for continuous-valued data."""
|
|
307
|
+
|
|
308
|
+
refname: str
|
|
309
|
+
start: int = field(kw_only=True)
|
|
310
|
+
end: int = field(kw_only=True)
|
|
311
|
+
value: float = field(kw_only=True)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass(slots=True, unsafe_hash=True)
|
|
315
|
+
class BedPE(PairBed, Named):
|
|
316
|
+
"""A BED record that describes a pair of BED records as per the bedtools spec."""
|
|
317
|
+
|
|
318
|
+
refname1: str = field(kw_only=True)
|
|
319
|
+
start1: int = field(kw_only=True)
|
|
320
|
+
end1: int = field(kw_only=True)
|
|
321
|
+
refname2: str = field(kw_only=True)
|
|
322
|
+
start2: int = field(kw_only=True)
|
|
323
|
+
end2: int = field(kw_only=True)
|
|
324
|
+
name: str | None = field(kw_only=True)
|
|
325
|
+
score: int | None = field(kw_only=True)
|
|
326
|
+
strand1: BedStrand | None = field(kw_only=True)
|
|
327
|
+
strand2: BedStrand | None = field(kw_only=True)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
@override
|
|
331
|
+
def bed1(self) -> Bed6:
|
|
332
|
+
"""The first of the two intervals as a BED6 record."""
|
|
333
|
+
return Bed6(
|
|
334
|
+
refname=self.refname1,
|
|
335
|
+
start=self.start1,
|
|
336
|
+
end=self.end1,
|
|
337
|
+
name=self.name,
|
|
338
|
+
score=self.score,
|
|
339
|
+
strand=self.strand1,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
@override
|
|
344
|
+
def bed2(self) -> Bed6:
|
|
345
|
+
"""The second of the two intervals as a BED6 record."""
|
|
346
|
+
return Bed6(
|
|
347
|
+
refname=self.refname2,
|
|
348
|
+
start=self.start2,
|
|
349
|
+
end=self.end2,
|
|
350
|
+
name=self.name,
|
|
351
|
+
score=self.score,
|
|
352
|
+
strand=self.strand2,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
@classmethod
|
|
356
|
+
def from_bed6(
|
|
357
|
+
cls, bed1: Bed6, bed2: Bed6, name: str | None = None, score: int | None = None
|
|
358
|
+
) -> Self:
|
|
359
|
+
return cls(
|
|
360
|
+
refname1=bed1.refname,
|
|
361
|
+
start1=bed1.start,
|
|
362
|
+
end1=bed1.end,
|
|
363
|
+
refname2=bed2.refname,
|
|
364
|
+
start2=bed2.start,
|
|
365
|
+
end2=bed2.end,
|
|
366
|
+
name=name,
|
|
367
|
+
score=score,
|
|
368
|
+
strand1=bed1.strand,
|
|
369
|
+
strand2=bed2.strand,
|
|
370
|
+
)
|
bedspec/_reader.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from io import TextIOWrapper
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from types import NoneType
|
|
4
|
+
from types import UnionType
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import get_args
|
|
7
|
+
from typing import get_origin
|
|
8
|
+
|
|
9
|
+
from typeline import TsvRecordReader
|
|
10
|
+
from typing_extensions import Self
|
|
11
|
+
from typing_extensions import override
|
|
12
|
+
|
|
13
|
+
from bedspec._bedspec import COMMENT_PREFIXES
|
|
14
|
+
from bedspec._bedspec import MISSING_FIELD
|
|
15
|
+
from bedspec._bedspec import BedColor
|
|
16
|
+
from bedspec._bedspec import BedStrand
|
|
17
|
+
from bedspec._bedspec import BedType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BedReader(TsvRecordReader[BedType]):
|
|
21
|
+
"""A reader of BED records."""
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
handle: TextIOWrapper,
|
|
27
|
+
record_type: type[BedType],
|
|
28
|
+
/,
|
|
29
|
+
header: bool = False,
|
|
30
|
+
comment_prefixes: set[str] = COMMENT_PREFIXES,
|
|
31
|
+
):
|
|
32
|
+
"""Instantiate a new BED reader.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
handle: a file-like object to read delimited data from.
|
|
36
|
+
record_type: the type of BED record we will be writing.
|
|
37
|
+
header: whether we expect the first line to be a header or not.
|
|
38
|
+
comment_prefixes: skip lines that have any of these string prefixes.
|
|
39
|
+
"""
|
|
40
|
+
super().__init__(handle, record_type, header=header, comment_prefixes=comment_prefixes)
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def _decode(self, field_type: type[Any] | str | Any, item: str) -> str:
|
|
44
|
+
"""A callback for overriding the string formatting of builtin and custom types."""
|
|
45
|
+
if field_type is BedStrand:
|
|
46
|
+
return f'"{item}"'
|
|
47
|
+
elif field_type is BedColor:
|
|
48
|
+
color = BedColor.from_string(item)
|
|
49
|
+
return f'{{"r":{color.r},"g":{color.g},"b":{color.b}}}'
|
|
50
|
+
|
|
51
|
+
is_union: bool = isinstance(field_type, UnionType)
|
|
52
|
+
type_args: tuple[type, ...] = get_args(field_type)
|
|
53
|
+
is_optional: bool = is_union and NoneType in type_args
|
|
54
|
+
|
|
55
|
+
if is_optional:
|
|
56
|
+
if item == MISSING_FIELD:
|
|
57
|
+
return "null"
|
|
58
|
+
elif type_args.count(BedStrand) == 1:
|
|
59
|
+
return f'"{item}"'
|
|
60
|
+
elif type_args.count(BedColor) == 1:
|
|
61
|
+
if item == "0":
|
|
62
|
+
return "null"
|
|
63
|
+
else:
|
|
64
|
+
color = BedColor.from_string(item)
|
|
65
|
+
return f'{{"r":{color.r},"g":{color.g},"b":{color.b}}}'
|
|
66
|
+
|
|
67
|
+
type_origin: type | None = get_origin(field_type)
|
|
68
|
+
|
|
69
|
+
if type_origin in (frozenset, list, set, tuple):
|
|
70
|
+
return f"[{item.rstrip(',')}]"
|
|
71
|
+
|
|
72
|
+
return super()._decode(field_type, item=item)
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
@override
|
|
76
|
+
def from_path(
|
|
77
|
+
cls,
|
|
78
|
+
path: Path | str,
|
|
79
|
+
record_type: type[BedType],
|
|
80
|
+
/,
|
|
81
|
+
header: bool = False,
|
|
82
|
+
comment_prefixes: set[str] = COMMENT_PREFIXES,
|
|
83
|
+
) -> Self:
|
|
84
|
+
"""Construct a BED reader from a file path.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
path: the path to the file to read delimited data from.
|
|
88
|
+
record_type: the type of the object we will be writing.
|
|
89
|
+
header: whether we expect the first line to be a header or not.
|
|
90
|
+
comment_prefixes: skip lines that have any of these string prefixes.
|
|
91
|
+
"""
|
|
92
|
+
handle = Path(path).open("r")
|
|
93
|
+
reader = cls(handle, record_type, header=header, comment_prefixes=comment_prefixes)
|
|
94
|
+
return reader
|
bedspec/_writer.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from typeline import TsvRecordWriter
|
|
4
|
+
from typing_extensions import override
|
|
5
|
+
|
|
6
|
+
from bedspec._bedspec import COMMENT_PREFIXES
|
|
7
|
+
from bedspec._bedspec import BedColor
|
|
8
|
+
from bedspec._bedspec import BedType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BedWriter(TsvRecordWriter[BedType]):
|
|
12
|
+
"""A writer for writing dataclasses into BED text data."""
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
def _encode(self, item: Any) -> Any:
|
|
16
|
+
"""A callback for overriding the encoding of builtin types and custom types."""
|
|
17
|
+
if item is None:
|
|
18
|
+
return "."
|
|
19
|
+
elif isinstance(item, (frozenset, list, set, tuple)):
|
|
20
|
+
return ",".join(map(str, item)) # pyright: ignore[reportUnknownArgumentType]
|
|
21
|
+
elif isinstance(item, BedColor):
|
|
22
|
+
return str(item)
|
|
23
|
+
return super()._encode(item=item)
|
|
24
|
+
|
|
25
|
+
def write_comment(self, comment: str) -> None:
|
|
26
|
+
"""Write a comment to the BED output."""
|
|
27
|
+
for line in comment.splitlines():
|
|
28
|
+
prefix = "" if any(line.startswith(prefix) for prefix in COMMENT_PREFIXES) else "# "
|
|
29
|
+
_ = self._handle.write(f"{prefix}{line}\n")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from collections.abc import Iterable
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from typing import Generic
|
|
6
|
+
from typing import TypeAlias
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
|
|
9
|
+
from superintervals import ( # type: ignore[import-untyped] # pyright: ignore[reportMissingTypeStubs]
|
|
10
|
+
IntervalSet, # pyright: ignore[reportUnknownVariableType]
|
|
11
|
+
)
|
|
12
|
+
from typing_extensions import override
|
|
13
|
+
|
|
14
|
+
from bedspec._bedspec import ReferenceSpan
|
|
15
|
+
|
|
16
|
+
ReferenceSpanType = TypeVar("ReferenceSpanType", bound=ReferenceSpan)
|
|
17
|
+
"""Type variable for features stored within the overlap detector."""
|
|
18
|
+
|
|
19
|
+
Refname: TypeAlias = str
|
|
20
|
+
"""A type alias for a reference sequence name string."""
|
|
21
|
+
|
|
22
|
+
IntervalTree: TypeAlias = IntervalSet # pyright: ignore[reportUnknownVariableType]
|
|
23
|
+
"""A type alias for the untyped interval set."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class OverlapDetector(Iterable[ReferenceSpanType], Generic[ReferenceSpanType]):
|
|
27
|
+
"""Detects and returns overlaps between a collection of reference features and query feature.
|
|
28
|
+
|
|
29
|
+
The overlap detector may be built with any feature-like Python object that has the following
|
|
30
|
+
properties:
|
|
31
|
+
|
|
32
|
+
* `refname`: The reference sequence name
|
|
33
|
+
* `start`: A 0-based start position
|
|
34
|
+
* `end`: A 0-based half-open end position
|
|
35
|
+
|
|
36
|
+
This detector is most efficiently used when all features to be queried are added ahead of time.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, features: Iterable[ReferenceSpanType] | None = None) -> None:
|
|
40
|
+
self._refname_to_features: dict[Refname, list[ReferenceSpanType]] = defaultdict(list)
|
|
41
|
+
self._refname_to_tree: dict[Refname, IntervalTree] = defaultdict(IntervalTree) # pyright: ignore[reportUnknownArgumentType]
|
|
42
|
+
self._refname_to_is_indexed: dict[Refname, bool] = defaultdict(lambda: False)
|
|
43
|
+
if features is not None:
|
|
44
|
+
self.add(*features)
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def __iter__(self) -> Iterator[ReferenceSpanType]:
|
|
48
|
+
"""Iterate over the features in the overlap detector."""
|
|
49
|
+
return chain(*self._refname_to_features.values())
|
|
50
|
+
|
|
51
|
+
def add(self, *features: ReferenceSpanType) -> None:
|
|
52
|
+
"""Add a feature to this overlap detector."""
|
|
53
|
+
for feature in features:
|
|
54
|
+
refname: Refname = feature.refname
|
|
55
|
+
feature_index: int = len(self._refname_to_features[refname])
|
|
56
|
+
|
|
57
|
+
self._refname_to_features[refname].append(feature)
|
|
58
|
+
self._refname_to_tree[refname].add(feature.start, feature.end - 1, feature_index) # pyright: ignore[reportUnknownMemberType]
|
|
59
|
+
self._refname_to_is_indexed[refname] = False # mark that this tree needs re-indexing
|
|
60
|
+
|
|
61
|
+
def overlapping(self, feature: ReferenceSpan) -> Iterator[ReferenceSpanType]:
|
|
62
|
+
"""Yields all the overlapping features for a given query feature."""
|
|
63
|
+
refname: Refname = feature.refname
|
|
64
|
+
|
|
65
|
+
if refname in self._refname_to_tree.keys() and not self._refname_to_is_indexed[refname]: # pyright: ignore[reportUnknownMemberType]
|
|
66
|
+
self._refname_to_tree[refname].index() # pyright: ignore[reportUnknownMemberType]
|
|
67
|
+
|
|
68
|
+
index: int
|
|
69
|
+
for index in self._refname_to_tree[refname].find_overlaps(feature.start, feature.end - 1): # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
70
|
+
yield self._refname_to_features[refname][index]
|
|
71
|
+
|
|
72
|
+
def overlaps(self, feature: ReferenceSpan) -> bool:
|
|
73
|
+
"""Determine if a query feature overlaps any other features."""
|
|
74
|
+
return next(self.overlapping(feature), None) is not None
|
|
75
|
+
|
|
76
|
+
def enclosing(self, feature: ReferenceSpan) -> Iterator[ReferenceSpanType]:
|
|
77
|
+
"""Yields all the overlapping features that completely enclose the given query feature."""
|
|
78
|
+
for overlap in self.overlapping(feature):
|
|
79
|
+
if feature.start >= overlap.start and feature.end <= overlap.end:
|
|
80
|
+
yield overlap
|
|
81
|
+
|
|
82
|
+
def enclosed_by(self, feature: ReferenceSpan) -> Iterator[ReferenceSpanType]:
|
|
83
|
+
"""Yields all the overlapping features that are enclosed by the given query feature."""
|
|
84
|
+
for overlap in self.overlapping(feature):
|
|
85
|
+
if feature.start <= overlap.start and feature.end >= overlap.end:
|
|
86
|
+
yield overlap
|
bedspec/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2024 Clint Valentine
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: bedspec
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: An HTS-specs compliant BED toolkit.
|
|
5
|
+
Home-page: https://github.com/clintval/bedspec
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: bioinformatics,BED,NGS,HTS,interval
|
|
8
|
+
Author: Clint Valentine
|
|
9
|
+
Author-email: valentine.clint@gmail.com
|
|
10
|
+
Requires-Python: >=3.10.0,<4.0.0
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Natural Language :: English
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: File Formats
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
25
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Dist: superintervals (>=0.2.2,<0.3.0)
|
|
29
|
+
Requires-Dist: typeline (>=0.6,<0.7)
|
|
30
|
+
Requires-Dist: typing-extensions (>=4.12,<5.0)
|
|
31
|
+
Project-URL: Repository, https://github.com/clintval/bedspec
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# bedspec
|
|
35
|
+
|
|
36
|
+
[](https://badge.fury.io/py/bedspec)
|
|
37
|
+
[](https://github.com/clintval/bedspec/actions/workflows/tests.yml?query=branch%3Amain)
|
|
38
|
+
[](https://github.com/clintval/typeline)
|
|
39
|
+
[](https://docs.basedpyright.com/latest/)
|
|
40
|
+
[](https://mypy-lang.org/)
|
|
41
|
+
[](https://python-poetry.org/)
|
|
42
|
+
[](https://docs.astral.sh/ruff/)
|
|
43
|
+
|
|
44
|
+
An HTS-specs compliant BED toolkit.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
The package can be installed with `pip`:
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
pip install bedspec
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quickstart
|
|
55
|
+
|
|
56
|
+
### Building a BED Feature
|
|
57
|
+
|
|
58
|
+
```pycon
|
|
59
|
+
>>> from bedspec import Bed3
|
|
60
|
+
>>>
|
|
61
|
+
>>> bed = Bed3("chr1", start=2, end=8)
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Writing
|
|
66
|
+
|
|
67
|
+
```pycon
|
|
68
|
+
>>> from bedspec import BedWriter
|
|
69
|
+
>>> from tempfile import NamedTemporaryFile
|
|
70
|
+
>>>
|
|
71
|
+
>>> temp_file = NamedTemporaryFile(mode="w+t", suffix=".txt")
|
|
72
|
+
>>>
|
|
73
|
+
>>> with BedWriter.from_path(temp_file.name, Bed3) as writer:
|
|
74
|
+
... writer.write(bed)
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Reading
|
|
79
|
+
|
|
80
|
+
```pycon
|
|
81
|
+
>>> from bedspec import BedReader
|
|
82
|
+
>>>
|
|
83
|
+
>>> with BedReader.from_path(temp_file.name, Bed3) as reader:
|
|
84
|
+
... for bed in reader:
|
|
85
|
+
... print(bed)
|
|
86
|
+
Bed3(refname='chr1', start=2, end=8)
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### BED Types
|
|
91
|
+
|
|
92
|
+
This package provides builtin classes for the following BED formats:
|
|
93
|
+
|
|
94
|
+
```pycon
|
|
95
|
+
>>> from bedspec import Bed2
|
|
96
|
+
>>> from bedspec import Bed3
|
|
97
|
+
>>> from bedspec import Bed4
|
|
98
|
+
>>> from bedspec import Bed5
|
|
99
|
+
>>> from bedspec import Bed6
|
|
100
|
+
>>> from bedspec import Bed12
|
|
101
|
+
>>> from bedspec import BedGraph
|
|
102
|
+
>>> from bedspec import BedPE
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Overlap Detection
|
|
107
|
+
|
|
108
|
+
Use a fast overlap detector for any collection of interval types, including third-party:
|
|
109
|
+
|
|
110
|
+
```pycon
|
|
111
|
+
>>> from bedspec import Bed3, Bed4
|
|
112
|
+
>>> from bedspec.overlap import OverlapDetector
|
|
113
|
+
>>>
|
|
114
|
+
>>> bed1 = Bed3("chr1", start=1, end=4)
|
|
115
|
+
>>> bed2 = Bed3("chr1", start=5, end=9)
|
|
116
|
+
>>>
|
|
117
|
+
>>> detector = OverlapDetector[Bed3]([bed1, bed2])
|
|
118
|
+
>>>
|
|
119
|
+
>>> my_feature = Bed4("chr1", start=2, end=3, name="hi-mom")
|
|
120
|
+
>>> detector.overlaps(my_feature)
|
|
121
|
+
True
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The overlap detector supports the following operations:
|
|
126
|
+
|
|
127
|
+
- `overlapping`: return all overlapping features
|
|
128
|
+
- `overlaps`: test if any overlapping features exist
|
|
129
|
+
- `enclosed_by`: return those enclosed by the input feature
|
|
130
|
+
- `enclosing`: return those enclosing the input feature
|
|
131
|
+
|
|
132
|
+
### Custom BED Types
|
|
133
|
+
|
|
134
|
+
To create a custom BED record, inherit from the relevant BED-type (`PointBed`, `SimpleBed`, `PairBed`).
|
|
135
|
+
|
|
136
|
+
For example, to create a custom BED3+1 class:
|
|
137
|
+
|
|
138
|
+
```pycon
|
|
139
|
+
>>> from dataclasses import dataclass
|
|
140
|
+
>>>
|
|
141
|
+
>>> from bedspec import SimpleBed
|
|
142
|
+
>>>
|
|
143
|
+
>>> @dataclass
|
|
144
|
+
... class Bed3Plus1(SimpleBed):
|
|
145
|
+
... refname: str
|
|
146
|
+
... start: int
|
|
147
|
+
... end: int
|
|
148
|
+
... my_custom_field: float | None
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Development and Testing
|
|
153
|
+
|
|
154
|
+
See the [contributing guide](./CONTRIBUTING.md) for more information.
|
|
155
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
CONTRIBUTING.md,sha256=selnIsM_6KTwdFyc0B3u4n2bM_sBAeSbQV-Fd7mzHuE,1803
|
|
2
|
+
LICENSE,sha256=nN9Rgc41QgFz1uJfX7AsXRQJxlcJotVEPTNPwUAqfys,1070
|
|
3
|
+
bedspec/__init__.py,sha256=MWneXWftcndpg-cSUkbLwuPMty0klBf_SpaaRm0jgKM,907
|
|
4
|
+
bedspec/_bedspec.py,sha256=5JypI3D9q1Jp326CHhU8v3hy9XONJv5Q79qc-2RRhzE,11721
|
|
5
|
+
bedspec/_reader.py,sha256=y4MqHYHvNwE-vk5Kpx2KP6PXtjc00S_D0fgE9hejzYU,3265
|
|
6
|
+
bedspec/_writer.py,sha256=KsF4A2q_fgCNFLnskEwyFW4NMwkF0JcXtrbvJ5UXbBI,1082
|
|
7
|
+
bedspec/overlap/__init__.py,sha256=aArIVkYIMBBC7YZkPP7KPcy6FjPZdKVl0Yi2as4pWNQ,76
|
|
8
|
+
bedspec/overlap/_overlap.py,sha256=s3yROqIs3AcJeCMdLWXBy4eoBz1aytGIwuQG33e-fcY,4153
|
|
9
|
+
bedspec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
bedspec-0.6.0.dist-info/LICENSE,sha256=nN9Rgc41QgFz1uJfX7AsXRQJxlcJotVEPTNPwUAqfys,1070
|
|
11
|
+
bedspec-0.6.0.dist-info/METADATA,sha256=5yEWscD0bkFQHxJ_5mWKfZQ9R29aVvKgtbXFn9ANdkg,4547
|
|
12
|
+
bedspec-0.6.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
13
|
+
bedspec-0.6.0.dist-info/RECORD,,
|