Files
computer-playbook/utils/annotations/message.py
T
kevinveenbirkenbachandClaude Opus 4.7 26ef3ff504 chore(ruff): enable strict ruleset (E/F/I/B/UP, py311) — 2819 → 0
Adds ``[tool.ruff.lint]`` with ``select = ["E", "F", "I", "B", "UP"]``
and ``target-version = "py311"``. ``E501`` is ignored because
``ruff format`` already reflows what it can; the remaining long lines
are unsplittable string literals / URLs.

Auto-fixed (2753 issues, behavior-preserving):
- UP006/UP035: ``List[X]`` → ``list[X]``, ``Dict[X]`` → ``dict[X]``,
  ``from typing import …`` redirected where superseded by builtins.
- UP045: ``Optional[X]`` → ``X | None``.
- UP010/UP009/UP015: dropped Python-2-era pragmas (``from __future__
  import absolute_import, division, print_function``,
  ``# -*- coding: utf-8 -*-``), redundant ``open(..., "r")`` mode.
- UP004: dropped implicit ``class X(object):``.
- UP022/UP008/UP032: stdlib idiom modernization (``capture_output``,
  super-call simplification, etc.).
- B905: ``zip(...)`` → ``zip(..., strict=False)`` (preserves prior
  behavior; switching to ``strict=True`` is a separate semantic
  choice not made here).
- I001: import-block ordering.

Hand-fixed (66 issues):
- B904 × 54: appended ``from <bound>`` (when the enclosing ``except``
  binds a name) or ``from None`` (when it doesn't) so chained
  exceptions stay distinguishable from errors-during-exception-
  handling. Applied via a small AST-aware script that walked back to
  the enclosing ``except`` clause and matched the binding.
- B017 × 4: narrowed broad ``assertRaises(Exception)`` to the actual
  ``AnsibleError`` / ``AnsibleFilterError`` raised by the code under
  test.
- B023 × 2: bound captured loop variables via default args
  (``def _record(name, *, _path_real=path_real, ...)``) so each
  closure gets its own snapshot.
- B007 × 2: unused loop control variables renamed to ``_`` /
  rewritten to ``.values()``.
- UP007 × 2: ``Union[X, Y]`` → ``X | Y`` (plus dropping the now-unused
  ``from typing import Union``).
- B018 × 1: Odoo addon manifests are bare dict expressions at module
  level by design — suppressed with an inline ``# noqa: B018`` and
  rationale comment.
- UP031 × 1: printf-style format → f-string.

The 65 ``# noqa: …`` markers introduced by the auto-fixer all carry
real ruff codes (``B018``, ``E402``, ``F401``, etc.), so
``test_noqa_only_ruff_codes`` stays green.

Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files already formatted
- ``tests.lint.repository.{test_project_root_import,
  test_no_direct_yaml_calls, test_noqa_only_ruff_codes}`` and
  ``tests.unit.utils.annotations.test_suppress`` → 20 tests OK
- 1621 unit tests → all pass
- Integration suite: 44 failures (pre-existing baseline, none from
  this change), 1 skipped.

Going forward, the new rules block regressions on E (style), F
(pyflakes), I (import order), B (likely-bug patterns), and UP
(modernization to py311) for any new code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:57:20 +02:00

101 lines
2.5 KiB
Python

"""GitHub Actions workflow command annotations.
Outputs structured log messages that GitHub Actions renders as annotations
(warning triangles, error markers, etc.) in the job summary and log view.
See: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-a-warning-message
"""
from __future__ import annotations
import os
from collections.abc import Iterable
def in_github_actions() -> bool:
return os.environ.get("GITHUB_ACTIONS") == "true"
def _emit(
level: str,
message: str,
*,
title: str | None = None,
file: str | None = None,
line: int | None = None,
col: int | None = None,
) -> None:
if in_github_actions():
parts = []
if title:
parts.append(f"title={title}")
if file:
parts.append(f"file={file}")
if line is not None:
parts.append(f"line={line}")
if col is not None:
parts.append(f"col={col}")
props = ",".join(parts)
prefix = f"::{level} {props}::" if props else f"::{level}::"
print(f"{prefix}{message}", flush=True)
else:
label = f"[{level.upper()}]"
if title:
label += f" {title}"
print(f"{label}: {message}", flush=True)
def warning(
message: str,
*,
title: str | None = None,
file: str | None = None,
line: int | None = None,
col: int | None = None,
) -> None:
_emit("warning", message, title=title, file=file, line=line, col=col)
def error(
message: str,
*,
title: str | None = None,
file: str | None = None,
line: int | None = None,
col: int | None = None,
) -> None:
_emit("error", message, title=title, file=file, line=line, col=col)
def notice(
message: str,
*,
title: str | None = None,
file: str | None = None,
line: int | None = None,
col: int | None = None,
) -> None:
_emit("notice", message, title=title, file=file, line=line, col=col)
def warning_each(
items: Iterable[str],
*,
title: str | None = None,
file: str | None = None,
) -> None:
"""Emit one warning annotation per item in *items*."""
for item in items:
warning(item, title=title, file=file)
def error_each(
items: Iterable[str],
*,
title: str | None = None,
file: str | None = None,
) -> None:
"""Emit one error annotation per item in *items*."""
for item in items:
error(item, title=title, file=file)