Skip to content

Commit 1bee4f7

Browse files
Merge pull request #19 from matchms/stricter_linting
Stricter linting
2 parents 85e8125 + 6a364d6 commit 1bee4f7

17 files changed

Lines changed: 84 additions & 85 deletions

‎graphconstructor/adapters.py‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from dataclasses import dataclass
2-
from typing import Optional
32
import numpy as np
43
from numpy.typing import NDArray
54
from .types import MatrixMode
@@ -8,6 +7,7 @@
87

98
@dataclass
109
class MatrixInput:
10+
"""Input for graph constructors that take a dense matrix."""
1111
matrix: NDArray
1212
mode: MatrixMode # "distance" or "similarity"
1313

@@ -19,6 +19,7 @@ def __post_init__(self) -> None:
1919

2020
@dataclass
2121
class KNNInput:
22+
"""Input for graph constructors that take KNN graphs."""
2223
indices: NDArray[np.int_]
2324
distances: NDArray
2425

@@ -31,9 +32,7 @@ def __post_init__(self) -> None:
3132

3233
@dataclass
3334
class ANNInput:
34-
# A fitted ANN index, e.g., pynndescent.NNDescent
35+
"""A fitted ANN index, e.g., pynndescent.NNDescent"""
3536
index: object
3637
# Optionally, a query set to build edges from (defaults to the index's training set)
37-
query_data: Optional[NDArray] = None
38-
39-
# We don't verify protocol strictly at runtime; we use duck typing in constructors.
38+
query_data: NDArray | None = None

‎graphconstructor/graph.py‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
2+
from collections.abc import Iterable, Sequence
23
from dataclasses import dataclass
3-
from typing import Iterable, Literal, Optional, Sequence
4+
from typing import Literal
45
import networkx as nx
56
import numpy as np
67
import pandas as pd
@@ -35,8 +36,8 @@ class Graph:
3536
weighted: bool
3637
mode: str
3738
metadata: pd.DataFrame | None = None
38-
ignore_selfloops: Optional[bool] = None
39-
keep_explicit_zeros: Optional[bool] = None
39+
ignore_selfloops: bool | None = None
40+
keep_explicit_zeros: bool | None = None
4041

4142
def __post_init__(self):
4243
# Check mode
@@ -171,8 +172,8 @@ def from_edges(
171172
directed: bool = False,
172173
weighted: bool = True,
173174
metadata: pd.DataFrame | None = None,
174-
ignore_selfloops: Optional[bool] = None,
175-
keep_explicit_zeros: Optional[bool] = None,
175+
ignore_selfloops: bool | None = None,
176+
keep_explicit_zeros: bool | None = None,
176177
sym_op: SymOp = "max",
177178
) -> "Graph":
178179
"""Build from an edge list. For undirected=True, we symmetrize later."""
@@ -188,7 +189,7 @@ def from_edges(
188189
rows = edges[:, 0].astype(int, copy=False)
189190
cols = edges[:, 1].astype(int, copy=False)
190191
else:
191-
rows, cols = map(np.asarray, zip(*edges)) if edges else (np.array([], int), np.array([], int))
192+
rows, cols = map(np.asarray, zip(*edges, strict=True)) if edges else (np.array([], int), np.array([], int))
192193

193194
if weights is None:
194195
data = np.ones_like(rows, dtype=float)
@@ -468,7 +469,7 @@ def to_igraph(self):
468469
weights = coo.data[mask] if self.weighted else np.ones(mask.sum(), dtype=float)
469470

470471
g = ig.Graph(n=self.n_nodes, directed=self.directed)
471-
g.add_edges(list(zip(rows.tolist(), cols.tolist())))
472+
g.add_edges(list(zip(rows.tolist(), cols.tolist(), strict=True)))
472473
if self.weighted:
473474
g.es["weight"] = weights.tolist()
474475
else:
@@ -581,7 +582,7 @@ def to_cytoscape(
581582
values = coo.data[edge_mask]
582583

583584
edges = []
584-
for edge_idx, (src, dst, value) in enumerate(zip(rows, cols, values)):
585+
for edge_idx, (src, dst, value) in enumerate(zip(rows, cols, values, strict=True)):
585586
data = {
586587
"id": f"e{edge_idx}",
587588
"source": node_ids[int(src)],

‎graphconstructor/importers.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def from_dense(
1616
metadata=None,
1717
sym_op="max"
1818
) -> Graph:
19+
"""Construct a Graph from a dense adjacency matrix."""
1920
return Graph.from_dense(arr, directed=directed, weighted=weighted, mode=mode, metadata=metadata, sym_op=sym_op)
2021

2122

@@ -28,6 +29,7 @@ def from_csr(
2829
metadata=None,
2930
sym_op="max"
3031
) -> Graph:
32+
"""Construct a Graph from a sparse adjacency matrix."""
3133
return Graph.from_csr(adj, directed=directed, weighted=weighted, mode=mode, metadata=metadata, sym_op=sym_op)
3234

3335

@@ -40,6 +42,7 @@ def from_knn(
4042
metadata=None,
4143
sym_op="max"
4244
) -> Graph:
45+
"""Construct a Graph from KNN neighbor indices and distances."""
4346
ind, dist = _coerce_knn_inputs(indices, distances)
4447
n_query, k = ind.shape
4548

@@ -71,10 +74,11 @@ def from_ann(
7174
metadata=None,
7275
sym_op="max"
7376
) -> Graph:
77+
"""Construct a Graph from a fitted ANN index, e.g., pynndescent.NNDescent."""
7478
idx = ann.index if hasattr(ann, "index") else ann
75-
if hasattr(idx, "indices_") and getattr(idx, "indices_") is not None:
76-
ind = np.asarray(getattr(idx, "indices_"))[:, :k]
77-
dist = np.asarray(getattr(idx, "distances_"))[:, :k]
79+
if hasattr(idx, "indices_") and idx.indices_ is not None:
80+
ind = np.asarray(idx.indices_)[:, :k]
81+
dist = np.asarray(idx.distances_)[:, :k]
7882
else:
7983
if query_data is None:
8084
raise TypeError("from_ann requires query_data when index has no cached neighbors.")

‎graphconstructor/operators/doubly_stochastic.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def apply(self, G: Graph) -> Graph:
8585
warnings.warn(
8686
"DoublyStochasticNormalize stopped early because scaling factors "
8787
"became very large. Result may not be doubly stochastic.",
88-
RuntimeWarning,
88+
RuntimeWarning, stacklevel=2,
8989
)
9090
break
9191

@@ -117,7 +117,7 @@ def apply(self, G: Graph) -> Graph:
117117
if not converged:
118118
warnings.warn(
119119
"DoublyStochasticNormalize did not converge within max_iter.",
120-
RuntimeWarning,
120+
RuntimeWarning, stacklevel=2,
121121
)
122122

123123
# Apply scaling once: A' = diag(r) * A * diag(c) (CSR-friendly)

‎graphconstructor/operators/enhanced_configuration_model.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ def _undirected(self, G: Graph) -> Graph:
362362
import warnings
363363
warnings.warn(
364364
f"ECM optimisation did not converge: {res.message}",
365-
RuntimeWarning,
365+
RuntimeWarning, stacklevel=2,
366366
)
367367

368368
# ---- p-value matrix ----------------------------------------------
@@ -381,11 +381,11 @@ def _undirected(self, G: Graph) -> Graph:
381381
W_lower_original = sp.tril(W_original, k=-1).tocoo()
382382
original_lookup = {
383383
(int(i), int(j)): w
384-
for i, j, w in zip(W_lower_original.row, W_lower_original.col, W_lower_original.data)
384+
for i, j, w in zip(W_lower_original.row, W_lower_original.col, W_lower_original.data, strict=True)
385385
}
386386

387387
original_weights = np.array(
388-
[original_lookup[(int(i), int(j))] for i, j in zip(row, col)],
388+
[original_lookup[(int(i), int(j))] for i, j in zip(row, col, strict=True)],
389389
dtype=W_original.dtype,
390390
)
391391

‎graphconstructor/operators/knn_selector.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass
2-
from typing import Literal, Optional
2+
from typing import Literal
33
import numpy as np
44
import scipy.sparse as sp
55
from ..graph import Graph
@@ -30,7 +30,7 @@ class KNNSelector(GraphOperator):
3030
"""
3131
k: int
3232
mutual: bool = False
33-
mutual_k: Optional[int] = None
33+
mutual_k: int | None = None
3434
mode: Mode = "distance"
3535
supported_modes = ["similarity", "distance"]
3636

‎graphconstructor/operators/metric_distance.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def _compute_distortions(D: GraphOperator, B, weight="weight", disjunction=sum):
4343
G.remove_edges_from(B.edges())
4444
weight_function = _weight_function(B, weight)
4545

46-
svals = dict()
46+
svals = {}
4747
for u in G.nodes():
4848
metric_dist = single_source_dijkstra_path_length(
4949
B, source=u, weight_function=weight_function, disjunction=disjunction

‎graphconstructor/types.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from __future__ import annotations
2-
from typing import Literal, Optional, Protocol, Tuple
2+
from typing import Literal, Protocol
33
import numpy as np
44
from numpy.typing import ArrayLike, NDArray
55
from scipy.sparse import csr_matrix
@@ -14,9 +14,9 @@ class ANNLike(Protocol):
1414
The minimal surface we rely on mirrors PyNNDescent and similar libraries.
1515
"""
1616

17-
def query(self, X: ArrayLike, k: int) -> Tuple[NDArray[np.int_], NDArray[np.floating]]: # indices, distances
17+
def query(self, X: ArrayLike, k: int) -> tuple[NDArray[np.int_], NDArray[np.floating]]: # indices, distances
1818
...
1919

2020
# Optional attributes commonly present on fitted ANN indexes
21-
indices_: Optional[NDArray[np.int_]]
22-
distances_: Optional[NDArray[np.floating]]
21+
indices_: NDArray[np.int_] | None
22+
distances_: NDArray[np.floating] | None

‎graphconstructor/utils.py‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Callable, Literal, Tuple, Union
1+
from collections.abc import Callable
2+
from typing import Literal
23
import numpy as np
34
import scipy.sparse as sp
45
from numpy.typing import NDArray
@@ -8,10 +9,7 @@
89

910
# Type aliases for clarity
1011
Mode = Literal["distance", "similarity"]
11-
ConversionMethod = Union[
12-
Literal["reciprocal", "negative", "exp", "gaussian"],
13-
Callable[[np.ndarray], np.ndarray]
14-
]
12+
ConversionMethod = Literal["reciprocal", "negative", "exp", "gaussian"] | Callable[[np.ndarray], np.ndarray]
1513

1614

1715
def _validate_square_matrix(M: np.ndarray) -> None:
@@ -40,7 +38,7 @@ def _drop_diagonal(A: sp.csr_matrix) -> sp.csr_matrix:
4038
return sp.csr_matrix((coo.data[mask], (coo.row[mask], coo.col[mask])), shape=A.shape)
4139

4240

43-
def _coerce_knn_inputs(indices, distances) -> Tuple[np.ndarray, np.ndarray]:
41+
def _coerce_knn_inputs(indices, distances) -> tuple[np.ndarray, np.ndarray]:
4442
ind = _to_numpy(indices)
4543
dist = _to_numpy(distances)
4644
if ind.shape != dist.shape:
@@ -60,7 +58,7 @@ def _csr_from_edges(n: int, rows: np.ndarray, cols: np.ndarray, weights: np.ndar
6058
return csr_matrix((weights, (rows, cols)), shape=(n, n))
6159

6260

63-
def _as_csr_square(M: NDArray | spmatrix) -> Tuple[sp.csr_matrix, int]:
61+
def _as_csr_square(M: NDArray | spmatrix) -> tuple[sp.csr_matrix, int]:
6462
"""Return (CSR, n) for a square matrix without densifying.
6563
6664
If `M` is dense, convert to CSR. If `M` is sparse, convert format to CSR
@@ -78,7 +76,7 @@ def _as_csr_square(M: NDArray | spmatrix) -> Tuple[sp.csr_matrix, int]:
7876
return sp.csr_matrix(arr), arr.shape[0]
7977

8078

81-
def _topk_per_row_sparse(csr: sp.csr_matrix, k: int, *, largest: bool) -> Tuple[np.ndarray, np.ndarray]:
79+
def _topk_per_row_sparse(csr: sp.csr_matrix, k: int, *, largest: bool) -> tuple[np.ndarray, np.ndarray]:
8280
"""Return (indices, values) of top-k entries per row from CSR matrix.
8381
8482
This operates strictly on the row's nonzeros without densifying.
@@ -120,7 +118,7 @@ def _topk_per_row_sparse(csr: sp.csr_matrix, k: int, *, largest: bool) -> Tuple[
120118
return ind, vals
121119

122120

123-
def _knn_from_matrix(M: NDArray | spmatrix, k: int, *, mode: MatrixMode) -> Tuple[np.ndarray, np.ndarray]:
121+
def _knn_from_matrix(M: NDArray | spmatrix, k: int, *, mode: MatrixMode) -> tuple[np.ndarray, np.ndarray]:
124122
"""Compute kNN (indices, values) from a square distance/similarity matrix.
125123
126124
Supports dense and sparse inputs without densifying sparse matrices.

‎graphconstructor/visualization/graph_statistics.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Iterable, Optional, Tuple
1+
from collections.abc import Iterable
22
import matplotlib.pyplot as plt
33
import numpy as np
44

@@ -8,13 +8,13 @@ def plot_degree_distribution(
88
*,
99
x_scale: str = "log",
1010
y_scale: str = "log",
11-
ax: Optional[plt.Axes] = None,
11+
ax: plt.Axes | None = None,
1212
normalize: bool = True,
1313
include_zero_degree: bool = False,
14-
label: Optional[str] = None,
14+
label: str | None = None,
1515
marker: str = "o",
1616
markersize: float = 5.0,
17-
) -> Tuple[plt.Figure, plt.Axes]:
17+
) -> tuple[plt.Figure, plt.Axes]:
1818
"""
1919
Plot the degree distribution p(k) vs k for a single graph.
2020
@@ -128,11 +128,11 @@ def plot_degree_distributions_grid(
128128
y_scale: str = "log",
129129
normalize: bool = True,
130130
include_zero_degree: bool = False,
131-
figsize: Optional[Tuple[float, float]] = None,
131+
figsize: tuple[float, float] | None = None,
132132
tight_layout: bool = True,
133133
sharex: bool = False,
134134
sharey: bool = False,
135-
) -> Tuple[plt.Figure, np.ndarray]:
135+
) -> tuple[plt.Figure, np.ndarray]:
136136
"""
137137
Plot a grid of degree distribution plots for multiple graphs.
138138

0 commit comments

Comments
 (0)