diff --git a/prek.toml b/prek.toml
index bb05f33..9792eed 100644
--- a/prek.toml
+++ b/prek.toml
@@ -33,7 +33,7 @@ hooks = [{ id = "gitleaks" }]
[[repos]]
repo = "https://github.com/astral-sh/ruff-pre-commit"
-rev = "01a675ea018f2fb714478a5ffb83fcea8374bb06" # frozen: v0.15.21
+rev = "cb8c523fd4835aba42af70f4cad5568db4df0b6c" # frozen: v0.16.0
hooks = [
{ id = "ruff-check", args = ["--fix"] },
{ id = "ruff-format" }
diff --git a/tests/test_any_llm_provider.py b/tests/test_any_llm_provider.py
index 6ba8900..efb4c69 100644
--- a/tests/test_any_llm_provider.py
+++ b/tests/test_any_llm_provider.py
@@ -299,6 +299,7 @@ async def test_any_llm_provider_assesses_notification_value_with_a_narrow_schema
)
async def test_factory_normalizes_provider_native_request_errors( # noqa: PLR0913
monkeypatch,
+ *,
provider_name: str,
error_factory: Callable[[httpx.Response], Exception],
operation: str,
diff --git a/tests/test_geocoding.py b/tests/test_geocoding.py
index 472e3d2..994d6ee 100644
--- a/tests/test_geocoding.py
+++ b/tests/test_geocoding.py
@@ -501,9 +501,11 @@ def handler(request: httpx.Request) -> httpx.Response:
[
'"invalid"',
'{"id":"example","name":"Example"}',
- '{"id":"example","name":"Example","latitude":1,"longitude":2,'
- '"country_code":null,"administrative_area":null,"timezone":null,'
- '"is_mainland_china":false,"summary_language":"english"}',
+ (
+ '{"id":"example","name":"Example","latitude":1,"longitude":2,'
+ '"country_code":null,"administrative_area":null,"timezone":null,'
+ '"is_mainland_china":false,"summary_language":"english"}'
+ ),
],
)
async def test_resolver_rejects_invalid_cached_reverse_record(tmp_path: Path, cached: str) -> None:
@@ -527,9 +529,11 @@ async def test_resolver_rejects_invalid_cached_reverse_record(tmp_path: Path, ca
"cached",
[
'{"id":"example","name":"Example"}',
- '{"id":"example","name":"Example","latitude":1,"longitude":2,'
- '"country_code":null,"administrative_area":null,"timezone":null,'
- '"is_mainland_china":false,"summary_language":"english"}',
+ (
+ '{"id":"example","name":"Example","latitude":1,"longitude":2,'
+ '"country_code":null,"administrative_area":null,"timezone":null,'
+ '"is_mainland_china":false,"summary_language":"english"}'
+ ),
],
)
async def test_resolver_rejects_obsolete_cache_record(tmp_path: Path, cached: str) -> None:
diff --git a/tests/test_service.py b/tests/test_service.py
index c726bc4..beb3036 100644
--- a/tests/test_service.py
+++ b/tests/test_service.py
@@ -238,7 +238,8 @@ def notification_decisions(self) -> NotificationDecisionProvider:
...
-def _briefing_service( # noqa: PLR0913
+# Test factory mirroring BriefingService; kept positional across 37 call sites.
+def _briefing_service( # noqa: PLR0913, PLR0917
settings: _TestSettings,
location: ResolvedLocation,
state: SQLiteStateStore,
@@ -252,15 +253,15 @@ def _briefing_service( # noqa: PLR0913
llm.notification_decisions if isinstance(llm, _NotificationDecisionOwner) else RecordingNotificationDecisions()
)
return _BriefingService(
- settings,
- location,
- state,
- rss_source,
- llm,
- notification_decisions,
- delivery,
- ops_delivery,
- weather_context_provider,
+ settings=settings,
+ location=location,
+ state=state,
+ rss_source=rss_source,
+ llm=llm,
+ notification_decisions=notification_decisions,
+ delivery=delivery,
+ ops_delivery=ops_delivery,
+ weather_context_provider=weather_context_provider,
)
@@ -1541,15 +1542,15 @@ async def test_forced_audible_briefing_does_not_depend_on_notification_decision(
with SQLiteStateStore(tmp_path / "forced-audible.sqlite3") as state:
service = _BriefingService(
- settings,
- _location(),
- state,
- EmptyRSSSource(),
- llm,
- decision_provider,
- delivery,
- delivery,
- StaticWeatherContextProvider(),
+ settings=settings,
+ location=_location(),
+ state=state,
+ rss_source=EmptyRSSSource(),
+ llm=llm,
+ notification_decisions=decision_provider,
+ delivery=delivery,
+ ops_delivery=delivery,
+ weather_context_provider=StaticWeatherContextProvider(),
)
body = await service.run(
"briefing",
diff --git a/tests/test_service_status.py b/tests/test_service_status.py
index e2585f8..cf64853 100644
--- a/tests/test_service_status.py
+++ b/tests/test_service_status.py
@@ -574,7 +574,7 @@ async def test_mismatched_official_language_is_translated(tmp_path: Path) -> Non
)
with SQLiteStateStore(tmp_path / "state.sqlite3") as state:
await ServiceStatusMonitor(
- (provider,), state.service_status, (("test", delivery),), decision, translator, "zh-CN"
+ (provider,), state.service_status, (("test", delivery),), decision, translator, language="zh-CN"
).run(pendulum.now("UTC"))
translator.translate_service_status.assert_awaited_once_with(
@@ -597,7 +597,7 @@ async def test_translation_failure_falls_back_to_official_text(tmp_path: Path, c
caplog.at_level("WARNING", logger="weather_briefing.service_status"),
):
await ServiceStatusMonitor(
- (provider,), state.service_status, (("test", delivery),), decision, translator, "zh-CN"
+ (provider,), state.service_status, (("test", delivery),), decision, translator, language="zh-CN"
).run(pendulum.now("UTC"))
delivery.publish_alert.assert_awaited_once_with(
@@ -634,22 +634,12 @@ def test_state_rejects_handling_a_changed_observation(tmp_path: Path) -> None:
with SQLiteStateStore(tmp_path / "state.sqlite3") as state:
state.service_status.observe_service_status_message(
"source",
- "incident",
- "new",
- "Title",
- "monitoring",
- "Body",
- now,
+ _message(incident_id="incident", revision_id="new", title="Title", status="monitoring", body="Body"),
)
with pytest.raises(RuntimeError, match="changed before handling"):
state.service_status.mark_service_status_message_handled(
"source",
- "incident",
- "old",
- "Title",
- "monitoring",
- "Body",
- (ServiceSurface.API,),
+ _message(incident_id="incident", revision_id="old", title="Title", status="monitoring", body="Body"),
now,
)
@@ -658,25 +648,15 @@ def test_state_rejects_invalid_stored_service_status_surfaces(tmp_path: Path) ->
now = pendulum.now("UTC")
state_path = tmp_path / "state.sqlite3"
with SQLiteStateStore(state_path) as state:
- state.service_status.observe_service_status_message(
- "source",
- "incident",
- "revision",
- "Title",
- "monitoring",
- "Body",
- now,
- )
- state.service_status.mark_service_status_message_handled(
- "source",
- "incident",
- "revision",
- "Title",
- "monitoring",
- "Body",
- (ServiceSurface.API,),
- now,
+ message = _message(
+ incident_id="incident",
+ revision_id="revision",
+ title="Title",
+ status="monitoring",
+ body="Body",
)
+ state.service_status.observe_service_status_message("source", message)
+ state.service_status.mark_service_status_message_handled("source", message, now)
for stored_value, message in (
("{}", "must be a list"),
("[1]", "must contain strings"),
@@ -698,12 +678,7 @@ def test_state_rejects_deciding_a_changed_observation(tmp_path: Path) -> None:
with SQLiteStateStore(tmp_path / "state.sqlite3") as state:
state.service_status.observe_service_status_message(
"source",
- "incident",
- "new",
- "Title",
- "monitoring",
- "Body",
- pendulum.now("UTC"),
+ _message(incident_id="incident", revision_id="new", title="Title", status="monitoring", body="Body"),
)
with pytest.raises(RuntimeError, match="changed before its decision"):
state.service_status.mark_service_status_message_decided(
diff --git a/weather_briefing/cli.py b/weather_briefing/cli.py
index fbb9a1a..f695591 100644
--- a/weather_briefing/cli.py
+++ b/weather_briefing/cli.py
@@ -158,20 +158,20 @@ async def _run_unlocked(
briefing_sent_today=briefing_sent_today,
)
service = BriefingService(
- settings,
- location,
- state,
- RSSSource(
+ settings=settings,
+ location=location,
+ state=state,
+ rss_source=RSSSource(
client,
max_attempts=settings.rss_max_attempts,
retry_min_seconds=settings.rss_retry_min_seconds,
retry_max_seconds=settings.rss_retry_max_seconds,
),
- llm_provider,
- notification_decisions,
- delivery,
- delivery,
- _weather_context_provider(settings, client, location),
+ llm=llm_provider,
+ notification_decisions=notification_decisions,
+ delivery=delivery,
+ ops_delivery=delivery,
+ weather_context_provider=_weather_context_provider(settings, client, location),
)
body = await service.run(
kind,
@@ -220,7 +220,7 @@ async def run_service_status() -> None:
deliveries,
notification_decisions,
service_status_llm,
- settings.service_status_language,
+ language=settings.service_status_language,
)
published = await monitor.run(pendulum.now(settings.timezone))
_LOGGER.info("Service-status run published %d notification(s)", published)
diff --git a/weather_briefing/delivery/base.py b/weather_briefing/delivery/base.py
index ddc508f..53409d6 100644
--- a/weather_briefing/delivery/base.py
+++ b/weather_briefing/delivery/base.py
@@ -189,7 +189,7 @@ def rendered_text_logging_enabled(diagnostics: RenderedTextDiagnostics | None) -
return False
try:
enabled = diagnostics.rendered_text_logging_enabled()
- except Exception: # noqa: BLE001
+ except Exception:
_LOGGER.warning("Rendered text diagnostic state check failed", exc_info=True)
return False
return enabled and _LOGGER.isEnabledFor(logging.DEBUG)
diff --git a/weather_briefing/delivery/telegram_renderer.py b/weather_briefing/delivery/telegram_renderer.py
index bb16139..01d1d09 100644
--- a/weather_briefing/delivery/telegram_renderer.py
+++ b/weather_briefing/delivery/telegram_renderer.py
@@ -31,8 +31,10 @@ def render_briefing(
}
source_links.update({document.id: _html_link(document.url, document.name) for document in context})
lines = [
- f"{_html_text(result.headline)} "
- f"{_html_attribution(result.headline_source_ids, source_links, labels)}",
+ (
+ f"{_html_text(result.headline)} "
+ f"{_html_attribution(result.headline_source_ids, source_links, labels)}"
+ ),
"",
]
lines.extend(_html_items(labels["weather"], result.conclusions, source_links, labels))
diff --git a/weather_briefing/llm/any_llm.py b/weather_briefing/llm/any_llm.py
index 1a5a0dd..1d31d5e 100644
--- a/weather_briefing/llm/any_llm.py
+++ b/weather_briefing/llm/any_llm.py
@@ -239,7 +239,7 @@ def _sensitive_llm_diagnostics_enabled(diagnostics: SensitiveLLMDiagnostics | No
return False
try:
return diagnostics.rendered_text_logging_enabled()
- except Exception: # noqa: BLE001
+ except Exception:
_LOGGER.warning("Sensitive LLM diagnostic state check failed", exc_info=True)
return False
diff --git a/weather_briefing/persistence/service_status.py b/weather_briefing/persistence/service_status.py
index a29f7ea..94f273b 100644
--- a/weather_briefing/persistence/service_status.py
+++ b/weather_briefing/persistence/service_status.py
@@ -15,6 +15,8 @@
import pendulum
+ from weather_briefing.service_status.models import ServiceStatusMessage
+
@dataclass(frozen=True, slots=True)
class ServiceStatusMessageState:
@@ -86,15 +88,10 @@ def service_status_message_state(
handled_surfaces=_stored_surfaces(row["handled_surfaces"]),
)
- def observe_service_status_message( # noqa: PLR0913
+ def observe_service_status_message(
self,
source_id: str,
- incident_id: str,
- revision_id: str,
- title: str,
- status: str,
- body: str,
- observed_at: pendulum.DateTime,
+ message: ServiceStatusMessage,
) -> None:
"""Persist an official message without claiming handling succeeded."""
self._connection.execute(
@@ -108,7 +105,15 @@ def observe_service_status_message( # noqa: PLR0913
observed_status = excluded.observed_status,
observed_body = excluded.observed_body,
observed_at = excluded.observed_at""",
- (source_id, incident_id, revision_id, title, status, body, storage_time(observed_at)),
+ (
+ source_id,
+ message.incident_id,
+ message.revision_id,
+ message.title,
+ message.status,
+ message.body,
+ storage_time(message.published_at),
+ ),
)
self._connection.commit()
@@ -165,15 +170,10 @@ def mark_service_status_message_delivered(
)
self._connection.commit()
- def mark_service_status_message_handled( # noqa: PLR0913
+ def mark_service_status_message_handled(
self,
source_id: str,
- incident_id: str,
- revision_id: str,
- title: str,
- status: str,
- body: str,
- surfaces: tuple[ServiceSurface, ...],
+ message: ServiceStatusMessage,
handled_at: pendulum.DateTime,
) -> None:
"""Mark one observed message as delivered or intentionally skipped."""
@@ -187,15 +187,15 @@ def mark_service_status_message_handled( # noqa: PLR0913
handled_at = ?
WHERE source_id = ? AND incident_id = ? AND observed_revision_id = ?""",
(
- revision_id,
- title,
- status,
- body,
- json.dumps([surface.value for surface in surfaces], ensure_ascii=False, separators=(",", ":")),
+ message.revision_id,
+ message.title,
+ message.status,
+ message.body,
+ json.dumps([surface.value for surface in message.surfaces], ensure_ascii=False, separators=(",", ":")),
storage_time(handled_at),
source_id,
- incident_id,
- revision_id,
+ message.incident_id,
+ message.revision_id,
),
)
if cursor.rowcount != 1:
diff --git a/weather_briefing/service.py b/weather_briefing/service.py
index 9f6bdae..94c6fab 100644
--- a/weather_briefing/service.py
+++ b/weather_briefing/service.py
@@ -47,6 +47,7 @@ class BriefingService:
def __init__( # noqa: PLR0913
self,
+ *,
settings: BriefingSettings,
location: ResolvedLocation,
state: SQLiteStateStore,
diff --git a/weather_briefing/service_status/monitor.py b/weather_briefing/service_status/monitor.py
index 910fd95..a50a655 100644
--- a/weather_briefing/service_status/monitor.py
+++ b/weather_briefing/service_status/monitor.py
@@ -17,7 +17,7 @@
from weather_briefing.notification_decision import NotificationDecisionProvider
from weather_briefing.persistence.service_status import ServiceStatusMessageState
- from .models import ServiceStatusMessage, ServiceStatusSnapshot, ServiceSurface
+ from .models import ServiceStatusMessage, ServiceStatusSnapshot
from .statuspage import ServiceStatusProvider
_LOGGER = logging.getLogger("weather_briefing.service_status")
@@ -38,28 +38,18 @@ def service_status_message_state(
"""Return the durable state for one incident."""
...
- def observe_service_status_message( # noqa: PLR0913
+ def observe_service_status_message(
self,
source_id: str,
- incident_id: str,
- revision_id: str,
- title: str,
- status: str,
- body: str,
- observed_at: pendulum.DateTime,
+ message: ServiceStatusMessage,
) -> None:
"""Record an official message before evaluating or delivering it."""
...
- def mark_service_status_message_handled( # noqa: PLR0913
+ def mark_service_status_message_handled(
self,
source_id: str,
- incident_id: str,
- revision_id: str,
- title: str,
- status: str,
- body: str,
- surfaces: tuple[ServiceSurface, ...],
+ message: ServiceStatusMessage,
handled_at: pendulum.DateTime,
) -> None:
"""Record successful delivery or an intentional skip."""
@@ -128,6 +118,7 @@ def __init__( # noqa: PLR0913
deliveries: tuple[tuple[str, ServiceStatusDelivery], ...],
decision_provider: NotificationDecisionProvider,
translator: ServiceStatusTranslator | None = None,
+ *,
language: str = "en",
) -> None:
"""Configure providers, durable state, notification policy, and delivery."""
@@ -164,15 +155,7 @@ async def _process_message(
previous = self._state.service_status_message_state(snapshot.source_id, message.incident_id)
if previous is not None and previous.handled_revision_id == message.revision_id:
return False
- self._state.observe_service_status_message(
- snapshot.source_id,
- message.incident_id,
- message.revision_id,
- message.title,
- message.status,
- message.body,
- message.published_at,
- )
+ self._state.observe_service_status_message(snapshot.source_id, message)
if previous is None and message.status == "resolved":
self._mark_handled(snapshot, message, now)
return False
@@ -223,16 +206,7 @@ def _mark_handled(
message: ServiceStatusMessage,
now: pendulum.DateTime,
) -> None:
- self._state.mark_service_status_message_handled(
- snapshot.source_id,
- message.incident_id,
- message.revision_id,
- message.title,
- message.status,
- message.body,
- message.surfaces,
- now,
- )
+ self._state.mark_service_status_message_handled(snapshot.source_id, message, now)
async def _localized_message(self, message: ServiceStatusMessage) -> tuple[str, str]:
if self._translator is None or official_message_matches(message, self._language):