Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/populace-data/src/populace/data/publish_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

from populace.data.release import publish_release
from populace.data.slack import notify_release


def main(argv: list[str] | None = None) -> int:
Expand Down Expand Up @@ -65,6 +66,15 @@ def main(argv: list[str] | None = None) -> int:
updated_at=args.updated_at,
)
print(json.dumps(pointer, indent=2))

# Real-time release alert, fired the moment latest.json is live. No-op
# unless the country's SLACK_WEBHOOK_POPULACE_* env var is set, and never
# fatal — a Slack failure must not fail an otherwise-successful publish.
notify_release(
args.repo_id,
str(pointer.get("release_id", "")),
pointer.get("updated_at"),
)
return 0


Expand Down
90 changes: 90 additions & 0 deletions packages/populace-data/src/populace/data/slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Best-effort Slack notification when a Populace release is published.

Sent inline from :func:`populace.data.publish_cli.main` right after
``latest.json`` is uploaded, so the alert is real-time (no polling) and fires
wherever the publish runs. The webhook URL comes from a per-country env var, so
this is a no-op unless it is configured, and a failed post is logged rather than
raised — a Slack hiccup must never fail a release.
"""

from __future__ import annotations

import json
import os
import urllib.request
from collections.abc import Callable
from typing import Any

CHANNEL_ENV: dict[str, str] = {
"us": "SLACK_WEBHOOK_POPULACE_US",
"uk": "SLACK_WEBHOOK_POPULACE_UK",
}

DASHBOARD_URL = "https://calibration-diagnostics.vercel.app/populace"


def country_for_repo(repo_id: str) -> str:
return "uk" if "uk" in repo_id.lower() else "us"


def _default_post(url: str, payload: dict[str, Any]) -> None:
request = urllib.request.Request(
url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=15) as response:
response.read()


def notify_release(
repo_id: str,
release_id: str,
updated_at: str | None = None,
*,
webhook: str | None = None,
post: Callable[[str, dict[str, Any]], None] | None = None,
) -> bool:
"""Announce a published release to the country's Slack channel.

Returns True if a message was sent. Returns False (no-op) when the webhook
env var is unset. Never raises — a failed post is logged, not fatal.
"""
country = country_for_repo(repo_id)
url = webhook or os.environ.get(CHANNEL_ENV[country])
if not url:
return False

label = "🇬🇧 UK" if country == "uk" else "🇺🇸 US"
context = " · ".join(
part
for part in (
repo_id,
f"published {updated_at}" if updated_at else "",
f"<{DASHBOARD_URL}|calibration diagnostics>",
)
if part
)
payload = {
"text": f"New Populace {country.upper()} release: {release_id}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f":rocket: *New Populace {label} release*\n`{release_id}`",
},
},
{
"type": "context",
"elements": [{"type": "mrkdwn", "text": context}],
},
],
}
try:
(post or _default_post)(url, payload)
return True
except Exception as exc:
print(f"::warning:: Populace Slack notification failed: {exc}")
return False
48 changes: 48 additions & 0 deletions packages/populace-data/tests/test_slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from populace.data.slack import country_for_repo, notify_release


def test_country_inferred_from_repo_id() -> None:
assert country_for_repo("policyengine/populace-us") == "us"
assert country_for_repo("policyengine/populace-uk-private") == "uk"


def test_notify_is_noop_without_webhook(monkeypatch) -> None:
monkeypatch.delenv("SLACK_WEBHOOK_POPULACE_US", raising=False)
sent: list = []
result = notify_release(
"policyengine/populace-us",
"populace-us-2024-abc-20260620T000000Z",
post=lambda url, payload: sent.append((url, payload)),
)
assert result is False
assert sent == []


def test_notify_posts_to_country_webhook(monkeypatch) -> None:
monkeypatch.setenv("SLACK_WEBHOOK_POPULACE_UK", "https://hooks.slack.test/uk")
sent: list = []
result = notify_release(
"policyengine/populace-uk-private",
"populace-uk-2023-def-20260620T000000Z",
"2026-06-20T00:00:00+00:00",
post=lambda url, payload: sent.append((url, payload)),
)
assert result is True
assert len(sent) == 1
url, payload = sent[0]
assert url == "https://hooks.slack.test/uk"
assert "populace-uk-2023-def" in payload["text"]
assert "UK" in payload["blocks"][0]["text"]["text"]


def test_notify_never_raises_on_post_failure() -> None:
def boom(url, payload):
raise RuntimeError("slack down")

result = notify_release(
"policyengine/populace-us",
"populace-us-2024-abc-20260620T000000Z",
webhook="https://hooks.slack.test/us",
post=boom,
)
assert result is False
Loading