utils.annotations package

Submodules

utils.annotations.message module

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

utils.annotations.message.error(message: str, *, title: str | None = None, file: str | None = None, line: int | None = None, col: int | None = None) → None
utils.annotations.message.error_each(items: Iterable[str], *, title: str | None = None, file: str | None = None) → None

Emit one error annotation per item in items.

utils.annotations.message.in_github_actions() → bool
utils.annotations.message.notice(message: str, *, title: str | None = None, file: str | None = None, line: int | None = None, col: int | None = None) → None
utils.annotations.message.warning(message: str, *, title: str | None = None, file: str | None = None, line: int | None = None, col: int | None = None) → None
utils.annotations.message.warning_each(items: Iterable[str], *, title: str | None = None, file: str | None = None) → None

Emit one warning annotation per item in items.

utils.annotations.summarize module

Parse GHA annotation lines from a log file and write a Markdown summary to $GITHUB_STEP_SUMMARY.

Usage:

python3 -m utils.annotations.summarize <logfile> [<title>]

Annotation format emitted by utils.annotations.message:

::warning title=<t>,file=<f>,line=<l>,col=<c>::<message> ::error title=<t>,…::<message> ::notice title=<t>,…::<message>

class utils.annotations.summarize.Annotation(level: 'str', message: 'str', title: 'str | None' = None, file: 'str | None' = None, line: 'int | None' = None, col: 'int | None' = None)

Bases: object

col: int | None = None
file: str | None = None
level: str
line: int | None = None
message: str
title: str | None = None
utils.annotations.summarize.main() → None
utils.annotations.summarize.parse_log(path: Path) → list[Annotation]
utils.annotations.summarize.parse_props(props_str: str) → dict[str, str]
utils.annotations.summarize.render_markdown(annotations: list[Annotation], title: str) → str

utils.annotations.suppress module

Unified suppression markers for infinito-nexus tests.

A single grammar covers every per-line, per-block, and per-file opt-out across the test suite. See docs/contributing/actions/testing/suppression.md for the catalog of valid rule keys, their placement, and the test that consumes each.

Grammar

<comment-prefix> (noqa|nocheck): <rule>(s*,s*<rule>)*

Accepted comment prefixes:

  • # (Python, YAML, shell, conf, INI, …)

  • // (JS, JSONC, …)

  • {# … #} (Jinja2)

  • <!-- … --> (HTML, Markdown)

noqa and nocheck are accepted as synonyms (case-insensitive) by this parser. By repo convention every project rule from docs/contributing/actions/testing/suppression.md MUST use nocheck: because # noqa: <code> is also parsed by ruff as a flake8 directive and triggers invalid-noqa-code warnings on every project-specific rule key. noqa: is reserved for real flake8 / ruff codes (E402, F401, …). The direct-yaml lint enforces the convention by hard-rejecting the noqa: form.

Multiple rules may be combined on one comment, comma-separated:

# nocheck: shared, email # nocheck: url, docker-version

Position semantics are per-rule and listed in the catalog page. This module exposes three resolvers:

utils.annotations.suppress.is_suppressed_anywhere(lines: Sequence[str], rule: str) → bool

Return True iff rule appears on any line of lines.

Used for whole-file opt-outs whose semantics legitimately apply to the entire file regardless of where the marker sits (e.g. run-once on roles that are intentionally executed every play).

utils.annotations.suppress.is_suppressed_at(lines: Sequence[str], line_no: int, rule: str, *, mode: str = 'same-or-above') → bool

Check whether the construct at 1-based line_no is suppressed.

mode selects placement semantics:

  • "same-line": marker must be on the construct’s line.

  • "line-above": marker must be on the immediately preceding non-empty line. Blank lines between marker and construct break the association.

  • "same-or-above" (default): either of the above.

utils.annotations.suppress.is_suppressed_for_key(lines: Sequence[str], key: str, rule: str) → bool

Whether the mapping line declaring key carries a marker for rule.

Parameters:
  • lines – the file’s lines.

  • key – the YAML key, matched as <key>: after stripping indentation.

  • rule – the rule to look for.

utils.annotations.suppress.is_suppressed_in_head(lines: Sequence[str], rule: str, *, scan_lines: int = 30) → bool

Return True iff rule appears in the first scan_lines lines.

File-level opt-outs (e.g. file-size) MUST live near the top of the file so the cost of carrying the exemption stays visible.

utils.annotations.suppress.line_has_rule(line: str, rule: str) → bool

Return True iff line carries a marker for rule.

utils.annotations.suppress.suppressed_line_numbers(lines: Sequence[str], rule: str) → set[int]

Return the set of 1-based line numbers carrying a marker for rule.

utils.annotations.task_gate module

Detect Ansible task-level when: gates that exempt compose-only shell calls from swarm-compat lints.

A task whose when: evaluates compose-only at deploy time (e.g. when: DEPLOYMENT_MODE != ‘swarm’ or when: DEPLOYMENT_MODE == ‘compose’) never reaches a swarm host, so compose <verb> / chdir: directories.instance inside that task body is legitimate. This module gives the lints a heuristic to recognise that gate without parsing YAML.

The detection is line-based: the task block is the span between the nearest - name: (at indent N) above the offending line and the next top-level structural marker (- name: at indent <= N, or end of file). Within that span we look for any when: clause whose textual value matches a compose-only DEPLOYMENT_MODE expression.

utils.annotations.task_gate.is_file_compose_only_by_header(lines: Sequence[str]) → bool

True iff the file declares a top-of-file marker

# include-gated: when: DEPLOYMENT_MODE != “swarm”

that documents the entire file is meant to be include_tasks: from a parent that already carries the compose-only when: guard. The marker MUST sit in the first 5 lines.

utils.annotations.task_gate.is_task_compose_only_gated(lines: Sequence[str], idx: int) → bool

True iff the task body containing idx — or any enclosing parent - block: / - name: block — carries a when: clause whose textual expression evaluates compose-only at deploy time. Walks upwards to recognise block-level gates: a single when: DEPLOYMENT_MODE != ‘swarm’ on a parent - block: covers every child task underneath.

Module contents