Skip to content

Commit 3db7072

Browse files
(pylint) making the linting stricter and cleaner, currently no issues
1 parent 92f98c0 commit 3db7072

48 files changed

Lines changed: 1228 additions & 867 deletions

Some content is hidden

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

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ All notable changes to this project will be documented in this file.
1111
- Lazily load baseline and granger engine classes via module `__getattr__` hooks to avoid import-time engine dependencies in store modules.
1212
- Hardened shared Redis patching in resolver tests by importing store modules dynamically and only patching available attributes.
1313
- Improved exception wrapper tests to preserve original HTTP exception identity and validate sync/async handler shape preservation.
14+
- Tightened resolver pylint design thresholds and refactored analyzer/connector/rca interfaces to keep strict linting without compatibility regressions.
15+
- Added structured RCA signal-input wiring and expanded edge-case tests so resolver `mypy`, `pylint`, and `pytest` all pass cleanly at 100% coverage.
1416

1517
## [v0.0.4] - 2026-04-14
1618

api/__init__.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# package marker for api submodules
21
"""
3-
Package exports for the `api` module namespace.
2+
Api module for the resolver package.
3+
4+
Copyright (c) 2026 Stefan Kumarasinghe
5+
6+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
7+
License. You may obtain a copy of the License at
8+
http://www.apache.org/licenses/LICENSE-2.0
49
"""

api/responses/base.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@
1919

2020

2121
def _coerce(obj: SerializableValue) -> SerializableValue:
22+
result: SerializableValue = obj
2223
if isinstance(obj, dict):
23-
return {k: _coerce(v) for k, v in obj.items()}
24-
if isinstance(obj, list):
25-
return [_coerce(v) for v in obj]
26-
if isinstance(obj, np.integer):
27-
return int(obj)
28-
if isinstance(obj, np.floating):
29-
return float(obj)
30-
if isinstance(obj, np.ndarray):
31-
return obj.tolist()
32-
return obj
24+
result = {k: _coerce(v) for k, v in obj.items()}
25+
elif isinstance(obj, list):
26+
result = [_coerce(v) for v in obj]
27+
elif isinstance(obj, np.integer):
28+
result = int(obj)
29+
elif isinstance(obj, np.floating):
30+
result = float(obj)
31+
elif isinstance(obj, np.ndarray):
32+
result = obj.tolist()
33+
return result
3334

3435

3536
class NpModel(BaseModel):

api/routes/__init__.py

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,33 @@
1212

1313
from fastapi import APIRouter
1414

15-
_router: APIRouter | None = None
16-
17-
18-
def _build_router() -> APIRouter:
19-
from api.routes.analyze import router as analyze_router
20-
from api.routes.causal import router as causal_router
21-
from api.routes.correlation import router as correlation_router
22-
from api.routes.events import router as events_router
23-
from api.routes.forecast import router as forecast_router
24-
from api.routes.health import router as health_router
25-
from api.routes.jobs import router as jobs_router
26-
from api.routes.logs import router as logs_router
27-
from api.routes.metrics import router as metrics_router
28-
from api.routes.ml import router as ml_router
29-
from api.routes.slo import router as slo_router
30-
from api.routes.topology import router as topology_router
31-
from api.routes.traces import router as traces_router
32-
33-
router = APIRouter()
34-
router.include_router(health_router)
35-
router.include_router(analyze_router)
36-
router.include_router(metrics_router)
37-
router.include_router(logs_router)
38-
router.include_router(traces_router)
39-
router.include_router(correlation_router)
40-
router.include_router(slo_router)
41-
router.include_router(topology_router)
42-
router.include_router(events_router)
43-
router.include_router(forecast_router)
44-
router.include_router(causal_router)
45-
router.include_router(ml_router)
46-
router.include_router(jobs_router)
47-
return router
48-
49-
50-
def __getattr__(name: str):
51-
if name == "router":
52-
global _router
53-
if _router is None:
54-
_router = _build_router()
55-
return _router
56-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
15+
from api.routes.analyze import router as analyze_router
16+
from api.routes.causal import router as causal_router
17+
from api.routes.correlation import router as correlation_router
18+
from api.routes.events import router as events_router
19+
from api.routes.forecast import router as forecast_router
20+
from api.routes.health import router as health_router
21+
from api.routes.jobs import router as jobs_router
22+
from api.routes.logs import router as logs_router
23+
from api.routes.metrics import router as metrics_router
24+
from api.routes.ml import router as ml_router
25+
from api.routes.slo import router as slo_router
26+
from api.routes.topology import router as topology_router
27+
from api.routes.traces import router as traces_router
28+
29+
router = APIRouter()
30+
router.include_router(health_router)
31+
router.include_router(analyze_router)
32+
router.include_router(metrics_router)
33+
router.include_router(logs_router)
34+
router.include_router(traces_router)
35+
router.include_router(correlation_router)
36+
router.include_router(slo_router)
37+
router.include_router(topology_router)
38+
router.include_router(events_router)
39+
router.include_router(forecast_router)
40+
router.include_router(causal_router)
41+
router.include_router(ml_router)
42+
router.include_router(jobs_router)
5743

5844
__all__ = ["router"]

api/routes/causal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def _common_causes_for_roots(causal_graph: CausalGraph, roots: list[str]) -> dic
5555

5656
async def _fetch_requested_metrics(provider: DataSourceProvider, req: CorrelateRequest) -> list[tuple[str, JSONDict]]:
5757
queries = list(dict.fromkeys((getattr(req, "metric_queries", None) or []) + DEFAULT_METRIC_QUERIES))
58-
return await safe_call(fetch_metrics(provider, queries, req.start, req.end, req.step))
58+
return await safe_call(fetch_metrics(provider, queries, req.start, req.end, step=req.step))
5959

6060

6161
@router.post(
@@ -66,6 +66,7 @@ async def _fetch_requested_metrics(provider: DataSourceProvider, req: CorrelateR
6666
@handle_exceptions
6767
async def granger_causality(
6868
req: CorrelateRequest,
69+
*,
6970
limit: int = Query(default=100, ge=1, le=2000),
7071
min_strength: float = Query(default=0.05, ge=0.0, le=1.0),
7172
max_series: int = Query(default=25, ge=2, le=200),

api/routes/common.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,4 @@ async def fetch_requested_metrics(
8484
req: _MetricRequestLike,
8585
) -> list[tuple[str, JSONDict]]:
8686
queries = list(dict.fromkeys((getattr(req, "metric_queries", None) or []) + DEFAULT_METRIC_QUERIES))
87-
return await safe_call(fetch_metrics(provider, queries, req.start, req.end, req.step))
87+
return await safe_call(fetch_metrics(provider, queries, req.start, req.end, step=req.step))

api/routes/correlation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ async def correlate_signals(req: CorrelateRequest) -> JSONDict:
4545
start=req.start * 1_000_000_000,
4646
end=req.end * 1_000_000_000,
4747
),
48-
fetch_metrics(provider, all_queries, req.start, req.end, req.step),
48+
fetch_metrics(provider, all_queries, req.start, req.end, step=req.step),
4949
return_exceptions=True,
5050
)
5151

api/routes/slo.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,15 @@ async def slo_burn(req: SloRequest) -> JSONDict:
7272
err_vals = err_vals[:n]
7373
tot_vals = tot_vals[:n]
7474
err_ts = err_ts[:n]
75-
alerts.extend(slo_evaluate(req.service, err_vals, tot_vals, err_ts, req.target_availability))
75+
alerts.extend(
76+
slo_evaluate(
77+
req.service,
78+
err_vals,
79+
tot_vals,
80+
err_ts,
81+
target_availability=req.target_availability,
82+
)
83+
)
7684
budget = remaining_minutes(req.service, err_vals, tot_vals, req.target_availability)
7785

7886
return {

connectors/common.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
from __future__ import annotations
66

7+
from dataclasses import dataclass
78
from typing import Protocol
89

910
import httpx
1011

11-
from datasources.helpers import fetch_json
12+
from datasources.helpers import FetchErrorMessages, FetchRequestOptions, fetch_json
1213
from datasources.types import JSONDict, QueryParams
1314

1415

@@ -20,29 +21,43 @@ class _BackendConnector(Protocol):
2021
def request_headers(self) -> dict[str, str]: ...
2122

2223

24+
@dataclass(frozen=True)
25+
class BackendErrorMessages:
26+
invalid: str
27+
timeout: str
28+
unavailable: str
29+
30+
2331
async def query_backend_json(
2432
connector: _BackendConnector,
25-
*,
2633
path: str,
2734
params: QueryParams,
28-
invalid_msg: str,
29-
timeout_msg: str,
30-
unavailable_msg: str,
35+
messages: BackendErrorMessages | None = None,
3136
) -> JSONDict:
37+
resolved_messages = messages or BackendErrorMessages(invalid="", timeout="", unavailable="")
38+
3239
headers_getter = getattr(connector, "request_headers", None)
3340
if callable(headers_getter):
3441
headers = headers_getter()
3542
else:
36-
legacy_headers_getter = getattr(connector, "_headers", None)
37-
headers = legacy_headers_getter() if callable(legacy_headers_getter) else {}
43+
fallback_headers_getter = getattr(connector, "_headers", None)
44+
headers = fallback_headers_getter() if callable(fallback_headers_getter) else {}
3845

3946
return await fetch_json(
4047
f"{connector.base_url}{path}",
41-
params=params,
42-
headers=headers,
43-
timeout=connector.timeout,
44-
client=connector.client,
45-
invalid_msg=invalid_msg,
46-
timeout_msg=timeout_msg,
47-
unavailable_msg=unavailable_msg,
48+
options=FetchRequestOptions(
49+
params=params,
50+
headers=headers,
51+
timeout=connector.timeout,
52+
client=connector.client,
53+
),
54+
messages=_to_fetch_messages(resolved_messages),
55+
)
56+
57+
58+
def _to_fetch_messages(messages: BackendErrorMessages) -> FetchErrorMessages:
59+
return FetchErrorMessages(
60+
invalid_msg=messages.invalid,
61+
timeout_msg=messages.timeout,
62+
unavailable_msg=messages.unavailable,
4863
)

connectors/loki.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import httpx
44

55
from config import DATASOURCE_TIMEOUT, HEALTH_PATH
6-
from connectors.common import query_backend_json
6+
from connectors.common import BackendErrorMessages, query_backend_json
77
from datasources.base import LogsConnector
88
from datasources.retry import retry
99
from datasources.types import JSONDict
@@ -16,10 +16,11 @@ def __init__(
1616
self,
1717
base_url: str,
1818
tenant_id: str,
19+
*,
1920
timeout: int = DATASOURCE_TIMEOUT,
2021
headers: dict[str, str] | None = None,
2122
) -> None:
22-
super().__init__(tenant_id, base_url, timeout, headers)
23+
super().__init__(tenant_id, base_url, timeout=timeout, headers=headers)
2324

2425
@staticmethod
2526
def _normalize_query(query: str) -> str:
@@ -36,6 +37,7 @@ async def query_range(
3637
query: str,
3738
start: int,
3839
end: int,
40+
*,
3941
limit: int | None = None,
4042
) -> JSONDict:
4143
params: dict[str, str | int | float | bool] = {
@@ -45,11 +47,14 @@ async def query_range(
4547
}
4648
if limit is not None:
4749
params["limit"] = limit
50+
messages = BackendErrorMessages(
51+
invalid="Loki query failed",
52+
timeout="Loki query timed out",
53+
unavailable="Cannot reach Loki at",
54+
)
4855
return await query_backend_json(
4956
self,
50-
path="/loki/api/v1/query_range",
51-
params=params,
52-
invalid_msg="Loki query failed",
53-
timeout_msg="Loki query timed out",
54-
unavailable_msg="Cannot reach Loki at",
57+
"/loki/api/v1/query_range",
58+
params,
59+
messages=messages,
5560
)

0 commit comments

Comments
 (0)