Skip to content

Commit fe59831

Browse files
authored
Merge pull request #103 from nsoranzo/py314
Drop support for Python 3.8, add support for 3.14
2 parents ba7d418 + b102527 commit fe59831

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+247
-334
lines changed

.flake8

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[flake8]
2+
ignore = E203,E501,E701,E704,E741,W503
3+
exclude = .git,.tox,.venv,build,doc/source/conf.py
4+
import-order-style = smarkets
5+
application-import-names = bx,bx_extras

.github/workflows/deploy.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,9 @@ jobs:
3232
run: python -m cibuildwheel --output-dir dist
3333
env:
3434
CIBW_ARCHS: ${{ matrix.archs }}
35-
# Skip building musllinux wheels for the CPython versions for which the
36-
# numpy version we build against doesn't have musllinux wheels on PyPI.
37-
# Skip building for PyPy 3.8, which is deprecated upstream.
38-
# Skip building for PyPy on i686 since NumPy 2.0 fails to build on it.
39-
CIBW_SKIP: "cp38-musllinux_* pp38-* pp*-manylinux_i686"
35+
CIBW_ENABLE: "pypy"
36+
# Skip building for PyPy 3.10 https://github.com/pypa/cibuildwheel/issues/2518
37+
CIBW_SKIP: "pp310-*"
4038
- name: Check packages
4139
run: twine check dist/*
4240
- uses: actions/upload-artifact@v4

.github/workflows/test.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: ['3.8', '3.13']
11+
python-version: ['3.9', '3.14']
1212
steps:
1313
- uses: actions/checkout@v4
1414
- uses: actions/setup-python@v5
@@ -24,7 +24,7 @@ jobs:
2424
strategy:
2525
fail-fast: false
2626
matrix:
27-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
27+
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
2828
steps:
2929
- uses: actions/checkout@v4
3030
- uses: actions/setup-python@v5

LICENSE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Copyright (c) 2005-2015 The Pennsylvania State University
2-
Copyright (c) 2013-2020 The Johns Hopkins University
2+
Copyright (c) 2013-2019 The Johns Hopkins University
3+
Copyright (c) 2019-2025 Earlham Institute
34

45
Permission is hereby granted, free of charge, to any person obtaining a copy
56
of this software and associated documentation files (the "Software"), to deal

doc/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
]
2626

2727
templates_path = ["_templates"]
28-
exclude_patterns = []
28+
exclude_patterns = ()
2929

3030

3131
# -- Options for HTML output -------------------------------------------------

lib/bx/align/axt.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def __init__(self, file, attributes=None):
128128

129129
def write(self, alignment):
130130
if len(alignment.components) != 2:
131-
raise ValueError("%d-component alignment is not compatible with axt" % len(alignment.components))
131+
raise ValueError(f"{len(alignment.components)}-component alignment is not compatible with axt")
132132
c1 = alignment.components[0]
133133
c2 = alignment.components[1]
134134

@@ -143,18 +143,7 @@ def write(self, alignment):
143143
chr1, chr2 = c1.src, c2.src
144144

145145
self.file.write(
146-
"%d %s %d %d %s %d %d %s %s\n"
147-
% (
148-
self.block,
149-
chr1,
150-
c1.start + 1,
151-
c1.start + c1.size,
152-
chr2,
153-
c2.start + 1,
154-
c2.start + c2.size,
155-
c2.strand,
156-
alignment.score,
157-
)
146+
f"{self.block} {chr1} {c1.start + 1} {c1.start + c1.size} {chr2} {c2.start + 1} {c2.start + c2.size} {c2.strand} {alignment.score}\n"
158147
)
159148
self.file.write(f"{c1.text}\n")
160149
self.file.write(f"{c2.text}\n")

lib/bx/align/core.py

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -250,24 +250,11 @@ def __init__(self, src="", start=0, size=0, strand=None, src_size=None, text="")
250250

251251
def __str__(self):
252252
if self.empty:
253-
rval = "e %s %d %d %s %d %s" % (
254-
self.src,
255-
self.start,
256-
self.size,
257-
self.strand,
258-
self.src_size,
259-
self.synteny_empty,
260-
)
253+
rval = f"e {self.src} {self.start} {self.size} {self.strand} {self.src_size} {self.synteny_empty}"
261254
else:
262-
rval = "s %s %d %d %s %d %s" % (self.src, self.start, self.size, self.strand, self.src_size, self.text)
255+
rval = f"s {self.src} {self.start} {self.size} {self.strand} {self.src_size} {self.text}"
263256
if self.synteny_left and self.synteny_right:
264-
rval += "\ni %s %s %d %s %d" % (
265-
self.src,
266-
self.synteny_left[0],
267-
self.synteny_left[1],
268-
self.synteny_right[0],
269-
self.synteny_right[1],
270-
)
257+
rval += f"\ni {self.src} {self.synteny_left[0]} {self.synteny_left[1]} {self.synteny_right[0]} {self.synteny_right[1]}"
271258
return rval
272259

273260
def get_end(self):
@@ -382,7 +369,7 @@ def coord_to_col(self, pos):
382369
raise ValueError("There is no column index. It is empty.")
383370
start, end = self.get_forward_strand_start(), self.get_forward_strand_end()
384371
if pos < start or pos > end:
385-
raise ValueError("Range error: %d not in %d-%d" % (pos, start, end))
372+
raise ValueError(f"Range error: {pos} not in {start}-{end}")
386373
if not self.index:
387374
self.index = []
388375
if self.strand == "-":

lib/bx/align/epo.py

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def _make_from_epo(cls, trg_comp, qr_comp, trg_chrom_sizes, qr_chrom_sizes):
9797
else:
9898
break
9999
S.append(min(a[1], b[1]) - max(a[0], b[0]))
100-
assert len(T) == len(Q) == len(S) - 1, "(S, T, Q) = (%d, %d, %d)" % tuple(map(len, (S, T, Q)))
100+
assert len(T) == len(Q) == len(S) - 1, f"(S, T, Q) = ({len(S)}, {len(T)}, {len(Q)})"
101101

102102
tSize = trg_chrom_sizes[trg_comp.chrom]
103103
qSize = qr_chrom_sizes[qr_comp.chrom]
@@ -138,16 +138,12 @@ def _make_from_epo(cls, trg_comp, qr_comp, trg_chrom_sizes, qr_chrom_sizes):
138138
if chain.qStrand == "-":
139139
chain = chain._replace(qEnd=chain.qSize - chain.qStart, qStart=chain.qSize - chain.qEnd)
140140

141-
assert chain.tEnd - chain.tStart == sum(S) + sum(T), "[%s] %d != %d" % (
142-
str(chain),
143-
chain.tEnd - chain.tStart,
144-
sum(S) + sum(T),
145-
)
146-
assert chain.qEnd - chain.qStart == sum(S) + sum(Q), "[%s] %d != %d" % (
147-
str(chain),
148-
chain.qEnd - chain.qStart,
149-
sum(S) + sum(Q),
150-
)
141+
assert chain.tEnd - chain.tStart == sum(S) + sum(
142+
T
143+
), f"[{str(chain)}] {chain.tEnd - chain.tStart} != {sum(S) + sum(T)}"
144+
assert chain.qEnd - chain.qStart == sum(S) + sum(
145+
Q
146+
), f"[{str(chain)}] {chain.qEnd - chain.qStart} != {sum(S) + sum(Q)}"
151147
return chain, S, T, Q
152148

153149
def slice(self, who):
@@ -223,7 +219,7 @@ def __repr__(self):
223219

224220
def __str__(self):
225221
c = self.cigar[:5] + "..." + self.cigar[-5:]
226-
return "(%s %s %s %d %d %s %s)" % tuple(self[:6] + (c,))
222+
return "({} {} {} {} {} {} {})".format(*tuple(self[:6] + (c,)))
227223

228224
@classmethod
229225
def _strfactory(cls, line):
@@ -318,10 +314,7 @@ def intervals(self, reverse, thr=0):
318314
assert sum(t[0] for t in self.cigar_iter(False) if t[1] == "M") == sum(t[1] - t[0] for t in d)
319315

320316
d_sum = sum(t[1] - t[0] for t in d)
321-
assert self.end - self.start + 1 == d_sum, "[ (%d, %d) = %d ] != %d" % (
322-
self.start,
323-
self.end,
324-
self.end - self.start + 1,
325-
d_sum,
326-
)
317+
assert (
318+
self.end - self.start + 1 == d_sum
319+
), f"[ ({self.start}, {self.end}) = {self.end - self.start + 1} ] != {d_sum}"
327320
return d[1:] # clip the (thr, thr) entry

lib/bx/align/epo_tests.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_ci(self):
3030
for i in range(self.N):
3131
assert C[i, 1] - C[i, 0] == S[i]
3232
for i in range(1, self.N):
33-
assert C[i, 0] - C[i - 1, 1] == D[i - 1], "[%d] %d != %d" % (i, C[i, 0] - C[i - 1, 1], D[i - 1])
33+
assert C[i, 0] - C[i - 1, 1] == D[i - 1], f"[{i}] {C[i, 0] - C[i - 1, 1]} != {D[i - 1]}"
3434

3535
def test_elem_u(self):
3636
# back to back, so should return a single interval
@@ -141,7 +141,7 @@ def toCigar(species, id, s):
141141
C.append(dc + mc)
142142
MSUM = sum(i[1] - i[0] for i in I)
143143
start = random.randint(50, 10000)
144-
return "%s\t%d\t1\t%d\t%d\t%d\t%s" % (species, id, start, start + MSUM - 1, random.choice((-1, 1)), "".join(C))
144+
return "{}\t{}\t1\t{}\t{}\t{}\t{}".format(species, id, start, start + MSUM - 1, random.choice((-1, 1)), "".join(C))
145145

146146

147147
class TestEpo(unittest.TestCase):
@@ -207,10 +207,10 @@ def test_rem_dash(self):
207207
qStart = random.randint(0, 1000)
208208
epo_pair = (
209209
EPOitem._strfactory(
210-
"homo_sapiens\t0\t1\t%d\t%d\t1\t%s" % (tStart, tStart + 12 - 1, "4M2D4M%dD4M" % (dash_cols + 3))
210+
"homo_sapiens\t0\t1\t{}\t{}\t1\t{}".format(tStart, tStart + 12 - 1, f"4M2D4M{dash_cols + 3}D4M")
211211
),
212212
EPOitem._strfactory(
213-
"mus_musculus\t0\t1\t%d\t%d\t1\t%s" % (qStart, qStart + 14 - 1, "7M%dD7M" % (dash_cols + 3))
213+
"mus_musculus\t0\t1\t{}\t{}\t1\t{}".format(qStart, qStart + 14 - 1, f"7M{dash_cols + 3}D7M")
214214
),
215215
)
216216
chain = Chain._make_from_epo(epo_pair[0], epo_pair[1], {"chr1": 500}, {"chr1": 800})
@@ -236,19 +236,20 @@ def test_rem_dash(self):
236236

237237
epo_pair = (
238238
EPOitem._strfactory(
239-
"homo_sapiens\t0\t1\t%d\t%d\t1\t%s" % (tStart, tStart + tm - 1, "%dD%dM" % (dash_cols + 1, tm))
239+
"homo_sapiens\t0\t1\t{}\t{}\t1\t{}".format(tStart, tStart + tm - 1, f"{dash_cols + 1}D{tm}M")
240240
),
241241
EPOitem._strfactory(
242-
"mus_musculus\t0\t1\t%d\t%d\t1\t%s"
243-
% (qStart, qStart + qm + 1 - 1, "M%dD%dM" % (dash_cols + tm - qm, qm))
242+
"mus_musculus\t0\t1\t{}\t{}\t1\t{}".format(
243+
qStart, qStart + qm + 1 - 1, f"M{dash_cols + tm - qm}D{qm}M"
244+
)
244245
),
245246
)
246247
chain = Chain._make_from_epo(epo_pair[0], epo_pair[1], {"chr1": 500}, {"chr1": 800})
247248
if chain[1][-1] != qm:
248249
pdb.set_trace()
249250
assert chain[1][-1] == qm
250251
# correct also for coordinate interpretation differences between UCSC and EPO
251-
assert (qStart + 1) - 1 == chain[0].qStart, "%d != %d" % (qStart + 1, chain[0].qStart)
252+
assert (qStart + 1) - 1 == chain[0].qStart, f"{qStart + 1} != {chain[0].qStart}"
252253

253254

254255
if __name__ == "__main__":

lib/bx/align/lav.py

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def __next__(self):
6161
continue
6262
if line == "#:eof":
6363
line = self.file.readline().rstrip()
64-
assert not line, 'extra line after #:eof (line %d, "%s")' % (self.lineNumber, line)
64+
assert not line, f'extra line after #:eof (line {self.lineNumber}, "{line}")'
6565
return None
6666
if line == "#:lav":
6767
continue
@@ -80,7 +80,7 @@ def __next__(self):
8080
if line.endswith("{"):
8181
self.parse_unknown_stanza()
8282
continue
83-
raise ValueError('incomprehensible line (line %d, "%s")' % (self.lineNumber, line))
83+
raise ValueError(f'incomprehensible line (line {self.lineNumber}, "{line}")')
8484
return self.build_alignment(score, pieces)
8585

8686
def __iter__(self):
@@ -161,7 +161,7 @@ def open_seqs(self):
161161
length2 = self.seq2_file.length
162162
assert (
163163
(species1 != species2) or (chrom1 != chrom2) or (length1 == length2)
164-
), "conflicting lengths for %s (%d and %d)" % (self.seq1_src, length1, length2)
164+
), f"conflicting lengths for {self.seq1_src} ({length1} and {length2})"
165165

166166
self.species_to_lengths = {}
167167
self.species_to_lengths[species1] = {}
@@ -190,7 +190,7 @@ def parse_s_stanza(self):
190190
)
191191

192192
line = self.fetch_line(report=" in s-stanza")
193-
assert line == "}", 'improper s-stanza terminator (line %d, "%s")' % (self.lineNumber, line)
193+
assert line == "}", f'improper s-stanza terminator (line {self.lineNumber}, "{line}")'
194194

195195
def parse_s_seq(self, line):
196196
fields = line.split()
@@ -234,7 +234,7 @@ def parse_h_stanza(self):
234234
self.seq2_header = "seq2"
235235

236236
line = self.fetch_line(report=" in h-stanza")
237-
assert line == "}", 'improper h-stanza terminator (line %d, "%s")' % (self.lineNumber, line)
237+
assert line == "}", f'improper h-stanza terminator (line {self.lineNumber}, "{line}")'
238238

239239
def parse_a_stanza(self):
240240
"""returns the pair (score,pieces)
@@ -243,7 +243,7 @@ def parse_a_stanza(self):
243243
# 's' line -- score, 1 field
244244
line = self.fetch_line(report=" in a-stanza")
245245
fields = line.split()
246-
assert fields[0] == "s", 's line expected in a-stanza (line %d, "%s")' % (self.lineNumber, line)
246+
assert fields[0] == "s", f's line expected in a-stanza (line {self.lineNumber}, "{line}")'
247247
try:
248248
score = int(fields[1])
249249
except ValueError:
@@ -252,12 +252,12 @@ def parse_a_stanza(self):
252252
# 'b' line -- begin positions in seqs, 2 fields
253253
line = self.fetch_line(report=" in a-stanza")
254254
fields = line.split()
255-
assert fields[0] == "b", 'b line expected in a-stanza (line %d, "%s")' % (self.lineNumber, line)
255+
assert fields[0] == "b", f'b line expected in a-stanza (line {self.lineNumber}, "{line}")'
256256

257257
# 'e' line -- end positions in seqs, 2 fields
258258
line = self.fetch_line(report=" in a-stanza")
259259
fields = line.split()
260-
assert fields[0] == "e", 'e line expected in a-stanza (line %d, "%s")' % (self.lineNumber, line)
260+
assert fields[0] == "e", f'e line expected in a-stanza (line {self.lineNumber}, "{line}")'
261261

262262
# 'l' lines
263263
pieces = []
@@ -276,7 +276,7 @@ def parse_a_stanza(self):
276276
pctId = float(fields[5])
277277
assert length2 == length, "length mismatch in a-stanza"
278278
pieces.append((start1 + self.seq1_start, start2 + self.seq2_start, length, pctId))
279-
assert line == "}", 'improper a-stanza terminator (line %d, "%s")' % (self.lineNumber, line)
279+
assert line == "}", f'improper a-stanza terminator (line {self.lineNumber}, "{line}")'
280280
return (score, pieces)
281281

282282
def parse_unknown_stanza(self):
@@ -298,7 +298,7 @@ def fetch_line(self, strip=True, requireLine=True, report=""):
298298
line = self.file.readline().strip().strip(strip)
299299
self.lineNumber += 1
300300
if requireLine:
301-
assert line, "unexpected blank line or end of file%s (line %d)" % (report, self.lineNumber)
301+
assert line, f"unexpected blank line or end of file{report} (line {self.lineNumber})"
302302
return line
303303

304304
def d_stanza(self):
@@ -319,20 +319,8 @@ def s_stanza(self):
319319
else:
320320
seq2_strand = "0"
321321

322-
s = ' "%s" %d %d %s %d\n' % (
323-
self.seq1_filename,
324-
self.seq2_start + 1,
325-
self.seq1_end,
326-
seq1_strand,
327-
self.seq1_contig,
328-
)
329-
s += ' "%s" %d %d %s %d\n' % (
330-
self.seq2_filename,
331-
self.seq2_start + 1,
332-
self.seq2_end,
333-
seq2_strand,
334-
self.seq2_contig,
335-
)
322+
s = f' "{self.seq1_filename}" {self.seq2_start + 1} {self.seq1_end} {seq1_strand} {self.seq1_contig}\n'
323+
s += f' "{self.seq2_filename}" {self.seq2_start + 1} {self.seq2_end} {seq2_strand} {self.seq2_contig}\n'
336324

337325
return f"s {{\n{s}}}"
338326

@@ -464,7 +452,7 @@ def __init__(self, file, attributes=None):
464452

465453
def write(self, alignment):
466454
if len(alignment.components) != 2:
467-
raise ValueError("%d-component alignment is not compatible with lav" % len(alignment.components))
455+
raise ValueError(f"{len(alignment.components)}-component alignment is not compatible with lav")
468456

469457
c1 = alignment.components[0]
470458
c2 = alignment.components[1]
@@ -502,8 +490,8 @@ def write_s_stanza(self):
502490
fname1 = build_filename(self.fname1, self.src1)
503491
fname2 = build_filename(self.fname2, self.src2)
504492
print("s {", file=self.file)
505-
print(' "%s%s" 1 %d %d 1' % (fname1, strand1, self.length1, flag1), file=self.file)
506-
print(' "%s%s" 1 %d %d 1' % (fname2, strand2, self.length2, flag2), file=self.file)
493+
print(f' "{fname1}{strand1}" 1 {self.length1} {flag1} 1', file=self.file)
494+
print(f' "{fname2}{strand2}" 1 {self.length2} {flag2} 1', file=self.file)
507495
print("}", file=self.file)
508496

509497
def write_h_stanza(self):
@@ -565,10 +553,10 @@ def write_a_stanza(self, alignment):
565553

566554
print("a {", file=self.file)
567555
print(f" s {score}", file=self.file)
568-
print(" b %d %d" % (start1 + 1, start2 + 1), file=self.file)
569-
print(" e %d %d" % (end1, end2), file=self.file)
556+
print(f" b {start1 + 1} {start2 + 1}", file=self.file)
557+
print(f" e {end1} {end2}", file=self.file)
570558
for start1, start2, size, pctId in pieces:
571-
print(" l %d %d %d %d %d" % (start1 + 1, start2 + 1, start1 + size, start2 + size, pctId), file=self.file)
559+
print(f" l {start1 + 1} {start2 + 1} {start1 + size} {start2 + size} {pctId}", file=self.file)
572560
print("}", file=self.file)
573561

574562
def write_lav_marker(self):

0 commit comments

Comments
 (0)