Skip to content

Commit fbc5bed

Browse files
Harden resolver helpers and router exports
1 parent 7fc1307 commit fbc5bed

5 files changed

Lines changed: 67 additions & 3 deletions

File tree

api/routes/__init__.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,51 @@
1111

1212
from fastapi import APIRouter
1313

14-
router = APIRouter()
14+
_router: APIRouter | None = None
15+
16+
17+
def _build_router() -> APIRouter:
18+
router = APIRouter()
19+
20+
from api.routes.analyze import router as analyze_router
21+
from api.routes.causal import router as causal_router
22+
from api.routes.correlation import router as correlation_router
23+
from api.routes.events import router as events_router
24+
from api.routes.forecast import router as forecast_router
25+
from api.routes.health import router as health_router
26+
from api.routes.jobs import router as jobs_router
27+
from api.routes.logs import router as logs_router
28+
from api.routes.metrics import router as metrics_router
29+
from api.routes.ml import router as ml_router
30+
from api.routes.slo import router as slo_router
31+
from api.routes.topology import router as topology_router
32+
from api.routes.traces import router as traces_router
33+
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+
48+
return router
49+
50+
51+
def __getattr__(name: str) -> APIRouter:
52+
if name != "router":
53+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
54+
55+
global _router
56+
if _router is None:
57+
_router = _build_router()
58+
globals()["router"] = _router
59+
return _router
1560

1661
__all__ = ["router"]

datasources/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _coerce_fetch_options(options: FetchRequestOptions | None) -> FetchRequestOp
5050
base = options or FetchRequestOptions()
5151
timeout = int(cast(int | str | bytes | bytearray, base.timeout))
5252
client_raw = base.client
53-
client = client_raw if hasattr(client_raw, "get") else base.client
53+
client = client_raw if callable(getattr(client_raw, "get", None)) else None
5454

5555
return FetchRequestOptions(
5656
params=base.params,

middleware/runtime_ssl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class RuntimeSSLOptions:
2121

2222
@classmethod
2323
def from_settings(cls, settings: object) -> RuntimeSSLOptions | None:
24-
if not getattr(settings, "ssl_enabled"):
24+
if not getattr(settings, "ssl_enabled", False):
2525
return None
2626

2727
certfile = str(getattr(settings, "ssl_certfile", "")).strip()

tests/test_api_route_surface_edges.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,3 +528,12 @@ def test_runtime_ssl_options_requires_paths():
528528
match="RESOLVER_SSL_ENABLED=true requires RESOLVER_SSL_CERTFILE and RESOLVER_SSL_KEYFILE to be set",
529529
):
530530
RuntimeSSLOptions.from_settings(types.SimpleNamespace(ssl_enabled=True, ssl_certfile="", ssl_keyfile=""))
531+
532+
533+
def test_aggregated_router_is_populated_and_ssl_defaults_disabled():
534+
from api.routes import router as aggregated_router
535+
536+
route_paths = {route.path for route in aggregated_router.routes}
537+
538+
assert "/health" in route_paths
539+
assert RuntimeSSLOptions.from_settings(types.SimpleNamespace()) is None

tests/test_helpers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,13 @@ async def test_fetch_text_success(monkeypatch):
7777
monkeypatch.setattr(httpx, "AsyncClient", lambda timeout: DummyClient(resp))
7878
got = await fetch_text("url")
7979
assert got == "hello"
80+
81+
82+
@pytest.mark.asyncio
83+
async def test_fetch_json_falls_back_to_owned_client_when_client_is_invalid(monkeypatch):
84+
resp = DummyResponse(status_code=200, json_data={"ok": True})
85+
monkeypatch.setattr(httpx, "AsyncClient", lambda timeout: DummyClient(resp))
86+
87+
got = await fetch_json("url", options=FetchRequestOptions(client=object()))
88+
89+
assert got == {"ok": True}

0 commit comments

Comments
 (0)