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
2 changes: 1 addition & 1 deletion tools/security-tracker-stats-dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ remaining charts still render.

## Prerequisites

- **Runtime:** Python 3.9+ (`python3`). Run each stage as `python3 fetch_events.py`, etc.
- **Runtime:** Python 3.11+ (`python3`). Run each stage as `python3 fetch_events.py`, etc.
Optional: `pyyaml` — when missing, `render.py` uses a bundled minimal YAML subset parser
sufficient for `default-config.yaml` and typical overlays; set `TRACKER_STATS_PY=uv-yaml`
to pin clean PyYAML invocations via `uv run --with pyyaml`.
Expand Down
6 changes: 3 additions & 3 deletions tools/security-tracker-stats-dashboard/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ name = "security-tracker-stats-dashboard"
version = "0.1.0"
description = "Rendering helpers for the security-tracker statistics dashboard."
readme = "README.md"
requires-python = ">=3.9"
requires-python = ">=3.11"
license = { text = "Apache-2.0" }
dependencies = []

Expand All @@ -33,15 +33,15 @@ packages = ["src/security_tracker_stats_dashboard"]

[tool.ruff]
line-length = 110
target-version = "py39"
target-version = "py311"
src = ["src", "tests"]

[tool.ruff.lint]
select = ["E", "W", "F", "I", "B", "UP", "SIM", "C4", "RUF"]
ignore = ["E501"]

[tool.mypy]
python_version = "3.10"
python_version = "3.11"
files = ["src", "tests"]
check_untyped_defs = true
no_implicit_optional = true
Expand Down
8 changes: 4 additions & 4 deletions tools/security-tracker-stats-dashboard/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ def _issue_label_names(issue):
with open(prs_path) as f:
prs_cache = json.load(f)

NOW = dt.datetime.now(dt.timezone.utc)
NOW = dt.datetime.now(dt.UTC)

if UPSTREAM_REPO:
# Match the original literal in the body-parse regex so an upstream
Expand Down Expand Up @@ -308,7 +308,7 @@ def _issue_label_names(issue):
# No / malformed date line — skip this entry defensively.
continue
try:
d = dt.datetime(int(dm.group(1)), int(dm.group(2)), int(dm.group(3)), tzinfo=dt.timezone.utc)
d = dt.datetime(int(dm.group(1)), int(dm.group(2)), int(dm.group(3)), tzinfo=dt.UTC)
except ValueError:
continue
rejections_dated.append(d)
Expand Down Expand Up @@ -501,7 +501,7 @@ def classify_per_config(labels, is_open, ts, n):
be = bucket_end(*b)
ts = NOW if be > NOW else be
cum_rejected.append(rejections_backfill_total + sum(1 for rd in rejections_dated if rd <= ts))
cum_reported = [o + r for o, r in zip(cum_opened, cum_rejected)]
cum_reported = [o + r for o, r in zip(cum_opened, cum_rejected, strict=True)]

# --- Opened-in-bucket vs untriaged-at-bucket-end ------------------

Expand All @@ -521,7 +521,7 @@ def classify_per_config(labels, is_open, ts, n):
# Per-bucket reported = trackers opened in the bucket + reports rejected in
# the same bucket (rejected_series). Equals opened_in_b when the rejections
# stat is disabled, so the trace is only drawn when the stat is active.
reported_in_b = [o + r for o, r in zip(opened_in_b, rejected_series)]
reported_in_b = [o + r for o, r in zip(opened_in_b, rejected_series, strict=True)]

# --- triage / response ---------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ def quarter_label(y: int, q: int) -> str:
def month_end(y: int, m: int) -> dt.datetime:
"""Return the last instant of the given calendar month (UTC)."""
last_day = calendar.monthrange(y, m)[1]
return dt.datetime(y, m, last_day, 23, 59, 59, tzinfo=dt.timezone.utc)
return dt.datetime(y, m, last_day, 23, 59, 59, tzinfo=dt.UTC)


def quarter_end(y: int, q: int) -> dt.datetime:
"""Return the last instant of the given calendar quarter (UTC)."""
last_month = q * 3
last_day = calendar.monthrange(y, last_month)[1]
return dt.datetime(y, last_month, last_day, 23, 59, 59, tzinfo=dt.timezone.utc)
return dt.datetime(y, last_month, last_day, 23, 59, 59, tzinfo=dt.UTC)


def iter_months(y0: int, m0: int, y1: int, m1: int) -> Iterator[tuple[int, int]]:
Expand Down Expand Up @@ -119,7 +119,7 @@ def week_label(y: int, w: int) -> str:
def week_end(y: int, w: int) -> dt.datetime:
# ISO weeks run Monday (day 1) .. Sunday (day 7); end at Sunday 23:59:59.
sunday = dt.date.fromisocalendar(y, w, 7)
return dt.datetime(sunday.year, sunday.month, sunday.day, 23, 59, 59, tzinfo=dt.timezone.utc)
return dt.datetime(sunday.year, sunday.month, sunday.day, 23, 59, 59, tzinfo=dt.UTC)


def iter_weeks(y0: int, w0: int, y1: int, w1: int) -> Iterator[tuple[int, int]]:
Expand Down
28 changes: 14 additions & 14 deletions tools/security-tracker-stats-dashboard/tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ def test_empty_returns_none(self):

def test_z_suffix(self):
result = parse_dt("2024-03-15T10:30:00Z")
assert result == dt.datetime(2024, 3, 15, 10, 30, 0, tzinfo=dt.timezone.utc)
assert result == dt.datetime(2024, 3, 15, 10, 30, 0, tzinfo=dt.UTC)

def test_plus_offset(self):
result = parse_dt("2024-03-15T10:30:00+00:00")
assert result == dt.datetime(2024, 3, 15, 10, 30, 0, tzinfo=dt.timezone.utc)
assert result == dt.datetime(2024, 3, 15, 10, 30, 0, tzinfo=dt.UTC)

def test_aware(self):
result = parse_dt("2025-01-01T00:00:00Z")
Expand All @@ -77,41 +77,41 @@ def test_aware(self):

class TestMonthOf:
def test_basic(self):
d = dt.datetime(2024, 6, 15, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 6, 15, tzinfo=dt.UTC)
assert month_of(d) == (2024, 6)

def test_january(self):
d = dt.datetime(2025, 1, 1, tzinfo=dt.timezone.utc)
d = dt.datetime(2025, 1, 1, tzinfo=dt.UTC)
assert month_of(d) == (2025, 1)

def test_december(self):
d = dt.datetime(2024, 12, 31, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 12, 31, tzinfo=dt.UTC)
assert month_of(d) == (2024, 12)


class TestQuarterOf:
def test_q1(self):
d = dt.datetime(2024, 2, 15, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 2, 15, tzinfo=dt.UTC)
assert quarter_of(d) == (2024, 1)

def test_q2(self):
d = dt.datetime(2024, 4, 1, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 4, 1, tzinfo=dt.UTC)
assert quarter_of(d) == (2024, 2)

def test_q3(self):
d = dt.datetime(2024, 9, 30, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 9, 30, tzinfo=dt.UTC)
assert quarter_of(d) == (2024, 3)

def test_q4(self):
d = dt.datetime(2024, 12, 1, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 12, 1, tzinfo=dt.UTC)
assert quarter_of(d) == (2024, 4)

def test_boundary_month_3_is_q1(self):
d = dt.datetime(2024, 3, 31, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 3, 31, tzinfo=dt.UTC)
assert quarter_of(d) == (2024, 1)

def test_boundary_month_4_is_q2(self):
d = dt.datetime(2024, 4, 1, tzinfo=dt.timezone.utc)
d = dt.datetime(2024, 4, 1, tzinfo=dt.UTC)
assert quarter_of(d) == (2024, 2)


Expand Down Expand Up @@ -140,7 +140,7 @@ def test_january(self):
assert result.day == 31
assert result.hour == 23
assert result.minute == 59
assert result.tzinfo == dt.timezone.utc
assert result.tzinfo == dt.UTC

def test_february_leap(self):
result = month_end(2024, 2)
Expand Down Expand Up @@ -177,7 +177,7 @@ def test_q4(self):
assert result.day == 31

def test_aware(self):
assert quarter_end(2024, 1).tzinfo == dt.timezone.utc
assert quarter_end(2024, 1).tzinfo == dt.UTC


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -254,7 +254,7 @@ def test_sunday_end_of_week(self):
assert (result.hour, result.minute, result.second) == (23, 59, 59)

def test_is_utc(self):
assert week_end(2025, 52).tzinfo == dt.timezone.utc
assert week_end(2025, 52).tzinfo == dt.UTC

def test_week_52(self):
# ISO 2025-W52 ends Sun 2025-12-28.
Expand Down
5 changes: 2 additions & 3 deletions tools/security-tracker-stats-dashboard/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.