Skip to content

Commit 691adc2

Browse files
committed
Add new ruff rules and apply rules to codes
1 parent d1d09e2 commit 691adc2

30 files changed

+330
-277
lines changed

pyproject.toml

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,36 @@ line-length = 88
6868
# Lint Rules: https://docs.astral.sh/ruff/rules/
6969
[tool.ruff.lint]
7070
select = [
71-
"F", # pyflakes
72-
"E", # pycodestyle (Error)
73-
"W", # pycodestyle (Warning)
74-
"I", # isort
75-
"D", # pydocstyle
71+
"I", # isort
72+
"F", # pyflakes
73+
"E", # pycodestyle (Error)
74+
"W", # pycodestyle (Warning)
75+
"D", # pydocstyle
76+
"UP", # pyupgrade
77+
"PL", # Pylint
78+
"ANN", # flake8-annotations
79+
"TC", # flake8-type-checking
80+
"B", # flake8-bugbear
81+
"SIM", # flake8-simplify
82+
"ARG", # flake8-unused-arguments
83+
"PTH", # flake8-use-pathlib
84+
"RUF", # Ruff-specific rules
7685
]
7786
ignore = [
78-
"D100", # Missing docstring in public module
79-
"D101", # Missing docstring in public class
80-
"D104", # Missing docstring in public package
81-
"D105", # Missing docstring in magic method
82-
"D205", # 1 blank line required between summary line and description
83-
"D400", # First line should end with a period
84-
"D401", # First line should be in imperative mood
85-
"D403", # First word of the first line should be properly capitalized
86-
"D415", # First line should end with a period, question mark, or exclamation point
87+
"D100", # Missing docstring in public module
88+
"D101", # Missing docstring in public class
89+
"D104", # Missing docstring in public package
90+
"D105", # Missing docstring in magic method
91+
"D205", # 1 blank line required between summary line and description
92+
"D400", # First line should end with a period
93+
"D401", # First line should be in imperative mood
94+
"D403", # First word of the first line should be properly capitalized
95+
"D415", # First line should end with a period, question mark, or exclamation point
96+
"ANN003", # Missing type annotation for **{name}
97+
"PTH123", # open() should be replaced by Path.open()
98+
"PLR0913", # Too many arguments in function definition ({c_args} > {max_args})
99+
"PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable
100+
"UP038", # Deprecated: Use X | Y in {} call instead of (X, Y)
87101
]
88102

89103
[tool.ruff.lint.pydocstyle]

src/pycirclize/annotation.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22

33
import warnings
44
from collections import defaultdict
5+
from typing import TYPE_CHECKING
56

67
import numpy as np
7-
from matplotlib.projections.polar import PolarAxes
88
from matplotlib.text import Annotation, Text
9-
from matplotlib.transforms import Bbox
10-
from numpy.typing import NDArray
119

1210
from pycirclize import config, utils
1311
from pycirclize.utils.plot import degrees
1412

13+
if TYPE_CHECKING:
14+
from matplotlib.projections.polar import PolarAxes
15+
from matplotlib.transforms import Bbox
16+
from numpy.typing import NDArray
17+
1518

1619
def adjust_annotation(ax: PolarAxes) -> None:
1720
"""Adjust annotation text position"""
@@ -20,7 +23,10 @@ def adjust_annotation(ax: PolarAxes) -> None:
2023
if len(ann_list) == 0 or config.ann_adjust.max_iter <= 0:
2124
return
2225
if len(ann_list) > config.ann_adjust.limit:
23-
warnings.warn(f"Too many annotations(={len(ann_list)}). Annotation position adjustment is not done.") # fmt: skip # noqa: E501
26+
warnings.warn(
27+
f"Too many annotations(={len(ann_list)}). Annotation position adjustment is not done.", # noqa: E501
28+
stacklevel=2,
29+
)
2430
return
2531

2632
def get_ann_window_extent(ann: Annotation) -> Bbox:
@@ -70,7 +76,7 @@ def _get_sorted_ann_list(ax: PolarAxes) -> list[Annotation]:
7076
loc = utils.plot.get_loc(ann.xyann[0])
7177
loc2ann_list[loc].append(ann)
7278

73-
def sort_by_ann_rad(ann: Annotation):
79+
def sort_by_ann_rad(ann: Annotation) -> float:
7480
return utils.plot.degrees(ann.xyann[0])
7581

7682
return (

src/pycirclize/circos.py

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,17 @@
55
import textwrap
66
import warnings
77
from collections import defaultdict
8-
from collections.abc import Mapping
8+
from collections.abc import Callable, Sequence
99
from copy import deepcopy
1010
from pathlib import Path
11-
from typing import Any, Callable, Sequence
11+
from typing import TYPE_CHECKING, Any
1212

1313
import matplotlib.pyplot as plt
1414
import numpy as np
1515
import pandas as pd
16-
from Bio.Phylo.BaseTree import Tree
17-
from matplotlib.axes import Axes
1816
from matplotlib.collections import PatchCollection
1917
from matplotlib.colorbar import Colorbar
2018
from matplotlib.colors import Colormap, Normalize
21-
from matplotlib.figure import Figure
22-
from matplotlib.patches import Patch
2319
from matplotlib.projections.polar import PolarAxes
2420

2521
from pycirclize import config, utils
@@ -39,9 +35,18 @@
3935
to_cytoband_tooltip,
4036
to_link_tooltip,
4137
)
42-
from pycirclize.track import Track
4338
from pycirclize.tree import TreeViz
44-
from pycirclize.typing import Numeric
39+
40+
if TYPE_CHECKING:
41+
from collections.abc import Mapping
42+
43+
from Bio.Phylo.BaseTree import Tree
44+
from matplotlib.axes import Axes
45+
from matplotlib.figure import Figure
46+
from matplotlib.patches import Patch
47+
48+
from pycirclize.track import Track
49+
from pycirclize.typing import Numeric
4550

4651

4752
class Circos:
@@ -57,7 +62,7 @@ def __init__(
5762
endspace: bool = True,
5863
sector2clockwise: dict[str, bool] | None = None,
5964
show_axis_for_debug: bool = False,
60-
):
65+
) -> None:
6166
"""
6267
Parameters
6368
----------
@@ -87,7 +92,7 @@ def __init__(
8792
if isinstance(space, Sequence):
8893
if len(space) != space_num:
8994
raise ValueError(f"{space=} is invalid.\nLength of space list must be {space_num}.") # fmt: skip # noqa: E501
90-
space_list = list(space) + [0]
95+
space_list = [*list(space), 0]
9196
space_deg_size = sum(space)
9297
else:
9398
space_list = [space] * space_num + [0]
@@ -181,22 +186,22 @@ def ax(self) -> PolarAxes:
181186
############################################################
182187

183188
@classmethod
184-
def set_tooltip_enabled(cls, enabled: bool = True):
189+
def set_tooltip_enabled(cls, enabled: bool = True) -> None:
185190
"""Enable/disable tooltip annotation using ipympl"""
186191
if enabled:
187192
try:
188-
import ipympl # noqa: F401
189-
from IPython import get_ipython # type: ignore
193+
import ipympl # noqa: F401, PLC0415
194+
from IPython import get_ipython # noqa: PLC0415
190195

191196
get_ipython().run_line_magic("matplotlib", "widget")
192197
config.tooltip.enabled = True
193198
except Exception:
194-
warnings.warn("Failed to enable tooltip. To enable tooltip, an interactive python environment such as jupyter and ipympl installation are required.") # fmt: skip # noqa: E501
199+
warnings.warn("Failed to enable tooltip. To enable tooltip, an interactive python environment such as jupyter and ipympl installation are required.", stacklevel=2) # fmt: skip # noqa: E501
195200
else:
196201
config.tooltip.enabled = False
197202

198203
@staticmethod
199-
def radar_chart(
204+
def radar_chart( # noqa: PLR0912, PLR0915
200205
table: str | Path | pd.DataFrame | RadarTable,
201206
*,
202207
r_lim: tuple[float, float] = (0, 100),
@@ -267,6 +272,7 @@ def radar_chart(
267272
circos : Circos
268273
Circos instance initialized for radar chart
269274
"""
275+
# TODO: Refactor complex codes
270276
if not vmin < vmax:
271277
raise ValueError(f"vmax must be larger than vmin ({vmin=}, {vmax=})")
272278
size = vmax - vmin
@@ -306,8 +312,8 @@ def radar_chart(
306312
if grid_label_formatter:
307313
text = grid_label_formatter(v)
308314
else:
309-
v = float(f"{v:.9f}") # Correct rounding error
310-
text = f"{v:.0f}" if math.isclose(int(v), float(v)) else str(v)
315+
v2 = float(f"{v:.9f}") # Correct rounding error
316+
text = f"{v2:.0f}" if math.isclose(int(v2), v2) else str(v2)
311317
track.text(text, 0, r, **grid_label_kws)
312318
# Plot vertical grid line
313319
for p in x[:-1]:
@@ -319,7 +325,7 @@ def radar_chart(
319325
else:
320326
row_name2color = cmap
321327
for row_name, values in radar_table.row_name2values.items():
322-
y = values + [values[0]]
328+
y = [*values, values[0]]
323329
color = row_name2color[row_name]
324330
line_kws = line_kws_handler(row_name) if line_kws_handler else {}
325331
line_kws.setdefault("lw", 1.0)
@@ -441,13 +447,12 @@ def chord_diagram(
441447
if isinstance(cmap, str):
442448
utils.ColorCycler.set_cmap(cmap)
443449
colors = utils.ColorCycler.get_color_list(len(names))
444-
name2color = dict(zip(names, colors))
450+
name2color = dict(zip(names, colors, strict=True))
451+
elif isinstance(cmap, defaultdict):
452+
name2color = cmap
445453
else:
446-
if isinstance(cmap, defaultdict):
447-
name2color = cmap
448-
else:
449-
name2color: dict[str, str] = defaultdict(lambda: "grey")
450-
name2color.update(cmap)
454+
name2color: dict[str, str] = defaultdict(lambda: "grey")
455+
name2color.update(cmap)
451456

452457
# Initialize circos sectors
453458
circos = Circos(matrix.to_sectors(), start, end, space=space, endspace=endspace)
@@ -876,10 +881,8 @@ def link(
876881
if "lw" not in kwargs and "linewidth" not in kwargs:
877882
kwargs.update(dict(lw=0.1))
878883

879-
if not allow_twist:
880-
# Resolve twist
881-
if (rad_end1 - rad_start1) * (rad_end2 - rad_start2) > 0:
882-
rad_start2, rad_end2 = rad_end2, rad_start2
884+
if not allow_twist and (rad_end1 - rad_start1) * (rad_end2 - rad_start2) > 0:
885+
rad_start2, rad_end2 = rad_end2, rad_start2
883886

884887
# Set tooltip content
885888
gid = gen_gid("link")

src/pycirclize/parser/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
from pycirclize.parser.table import RadarTable, StackedBarTable
66

77
__all__ = [
8+
"Bed",
89
"Genbank",
910
"Gff",
10-
"Bed",
1111
"Matrix",
1212
"RadarTable",
1313
"StackedBarTable",

src/pycirclize/parser/bed.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import csv
44
from dataclasses import dataclass
5-
from pathlib import Path
5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from pathlib import Path
69

710

811
class Bed:
912
"""BED Parser Class"""
1013

11-
def __init__(self, bed_file: str | Path):
14+
def __init__(self, bed_file: str | Path) -> None:
1215
"""
1316
Parameters
1417
----------

src/pycirclize/parser/genbank.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(
2828
name: str | None = None,
2929
min_range: None = None,
3030
max_range: None = None,
31-
):
31+
) -> None:
3232
"""
3333
Parameters
3434
----------
@@ -63,7 +63,10 @@ def __init__(
6363
raise ValueError("Failed to get genbank name.")
6464

6565
if min_range or max_range:
66-
warnings.warn("min_range & max_range is no longer used in Genbank parser.")
66+
warnings.warn(
67+
"min_range & max_range is no longer used in Genbank parser.",
68+
stacklevel=2,
69+
)
6770

6871
if len(self.records) == 0:
6972
raise ValueError(f"Failed to parse {gbk_source} as Genbank file.")
@@ -161,7 +164,7 @@ def calc_gc_skew(
161164
step_size = int(len(seq) / 1000)
162165
if window_size == 0 or step_size == 0:
163166
window_size, step_size = len(seq), int(len(seq) / 2)
164-
pos_list = list(range(0, len(seq), step_size)) + [len(seq)]
167+
pos_list = [*list(range(0, len(seq), step_size)), len(seq)]
165168
for pos in pos_list:
166169
window_start_pos = pos - int(window_size / 2)
167170
window_end_pos = pos + int(window_size / 2)
@@ -215,7 +218,7 @@ def calc_gc_content(
215218
step_size = int(len(seq) / 1000)
216219
if window_size == 0 or step_size == 0:
217220
window_size, step_size = len(seq), int(len(seq) / 2)
218-
pos_list = list(range(0, len(seq), step_size)) + [len(seq)]
221+
pos_list = [*list(range(0, len(seq), step_size)), len(seq)]
219222
for pos in pos_list:
220223
window_start_pos = pos - int(window_size / 2)
221224
window_end_pos = pos + int(window_size / 2)
@@ -326,7 +329,7 @@ def extract_features(
326329
Extracted features
327330
"""
328331
seqid2features = self.get_seqid2features(feature_type, target_strand)
329-
first_record_features = list(seqid2features.values())[0]
332+
first_record_features = next(iter(seqid2features.values()))
330333
if target_range:
331334
target_features = []
332335
for feature in first_record_features:
@@ -415,10 +418,9 @@ def _parse_gbk_source(
415418
with bz2.open(gbk_source, mode="rt", encoding="utf-8") as f:
416419
return list(SeqIO.parse(f, "genbank"))
417420
elif Path(gbk_source).suffix == ".zip":
418-
with zipfile.ZipFile(gbk_source) as zip:
419-
with zip.open(zip.namelist()[0]) as f:
420-
io = TextIOWrapper(f, encoding="utf-8")
421-
return list(SeqIO.parse(io, "genbank"))
421+
with zipfile.ZipFile(gbk_source) as z, z.open(z.namelist()[0]) as f:
422+
io = TextIOWrapper(f, encoding="utf-8")
423+
return list(SeqIO.parse(io, "genbank"))
422424
else:
423425
with open(gbk_source, encoding="utf-8") as f:
424426
return list(SeqIO.parse(f, "genbank"))
@@ -445,9 +447,9 @@ def _is_straddle_feature(self, feature: SeqFeature) -> bool:
445447
else:
446448
start = int(feature.location.parts[0].start) # type: ignore
447449
end = int(feature.location.parts[-1].end) # type: ignore
448-
return True if start > end else False
450+
return start > end
449451

450-
def __str__(self):
452+
def __str__(self) -> str:
451453
text = f"{self.name}: {len(self.records)} records\n"
452454
for num, (seqid, size) in enumerate(self.get_seqid2size().items(), 1):
453455
text += f"{num:02d}. {seqid} ({size:,} bp)\n"

0 commit comments

Comments
 (0)