- Declare `__all__` in every package `__init__.py` whose `PROJECT_ROOT`
is imported by its modules (163 packages), so the constant counts as
exported; list every public name where the package defines more.
- Empty the 15 package `__init__.py` files whose `PROJECT_ROOT` no module
imports.
- Declare the public API of `utils.github.variant.axes` in `__all__`,
re-exports included, instead of silencing them with `noqa: F401`; the
list matches the one `feature/store-i18n` already carries.
- Use `import unittest.mock as mock` in `test_applications.py`, as the
rest of the suite does.
- Spell `_NON_PRINTABLE` in `utils/yaml_bootstrap.py` as the equivalent
BMP-only positive class; it matches exactly the same 2111 code points
as the old negated class across U+0000..U+10FFFF.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Four tests were guarding two rules. Both duplicates are gone and what they
were meant to check now lives with the rule it belongs to.
The handler name/length pair: a 120-character limit sat over the same files
as an 80-character one, so it could never fire first, and outside that overlap
it matched `name:` keys that were no task at all -- a volume name, a var. It
found nothing in either half. Deleted; the surviving lint instead grows the
scope it was missing, `roles/*/handlers/**` and `playbooks/**`, because a
handler is a task, and the dispatch input descriptions, because a form line is
read like a job name.
The templated-handler-name pair is worse: the second one skipped every
document with `if not isinstance(doc, dict)`, and a handlers file is a list.
It has been vacuously green for its whole life. Its intent -- every
`handlers/*.yml`, not just `main.yml` -- is now what the surviving test does.
The notify/listen contract itself gains the rule that made the rename in
a8dd7f1 safe: a notify must land on a `listen:` topic, never on a display
name, and both spell out as [a-z_-]+. Following `import_tasks` chains by hand
is gone with it; scanning every project YAML sees the same handlers, plus the
ones a role hands to a generic renderer through a data structure.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The ruff toolchain was unpinned, so host venvs, the lint container and CI
installed different releases over time. That drift is why lint-python only
failed on CI: locally ruff 0.15.x preview-gated ISC004/FURB192 and (falsely)
fired S310 on urllib.request.Request, while CI's 0.16.0 has ISC004/FURB192
stable and drops the S310-on-Request false positive.
Pin ruff==0.16.0 in pyproject.toml's dev extras (Dependabot keeps it current)
and have utils.install.lint.ruff read that pin and reinstall whenever the
found version drifts, so every environment resolves the same ruff. Then fix
what 0.16.0 flags: parenthesise the implicit string concatenations (ISC004),
use min() over sorted()[0] (FURB192), drop the now-unused S310 noqa on the
Request() builders (RUF100), and delete the narration comments that the
comment lint surfaces in the touched files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the mode-specific handlers from a single main.yml with per-task
`when: DEPLOYMENT_MODE ==/\!= 'swarm'` conditions into two sibling files
(compose.yml, swarm.yml) imported via `import_tasks` with the mode guard
on the wrapper. The mode condition is set once at the import site
instead of being repeated on every handler.
main.yml keeps the shared prune handler (storage-constrained, fires for
both compose-up and compose-build listens regardless of mode).
Also: the handler-invocation lint at
tests/integration/ansible/handlers/test_invoked.py now follows
import_tasks references inside handlers/main.yml so it picks up handler
definitions in sibling files. Without this, the new layout fails the
"every notify maps to a handler" check because the scanner previously
only looked at main.yml.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Widens the cache-read lint guard from ``tests/lint/`` to every ``.py``
under ``tests/``. Integration tests show the same compounding cost
pattern (one role's compose template / nginx conf / scrape fragment
read across many shape tests) that motivated the original lint for
lint/.
Converts 91 violations across 28 test files:
- **Cache (utils.cache.files.read_text)** for project-tracked reads
and single-read tempdir fixtures, e.g. test_prometheus.py (32x),
test_keycloak_import_flow.py (11x), test_dashboard_oidc.py (3x),
test_changelog_md.py (8x), test_package_mirror.py (8x), plus
test_logging.py / test_summarize.py / test_version_updater.py /
test_get_url_retry.py / test_pull_specific_host.py.
- **Cache + UnicodeDecodeError handler** for project-walk scanners
that previously used ``errors="replace"`` / ``errors="ignore"`` to
defend against non-UTF-8 files (compose volumes, password quoting,
template image, mailu dependency, URL reachability). Wraps each
read in ``try/except (OSError, UnicodeDecodeError): continue``,
which has the same observable effect (skip undecodable files).
- **Refactor away the binary heuristic** in test_no_raw_docker.py:
``read_bytes() + b"\x00" not in raw`` becomes
``read_text() / except UnicodeDecodeError``. Same files skipped,
with cache benefit.
- **Variable extraction** in test_changelog_md.py / test_package_mirror.py
for cases where ``self.assertEqual(path.read_text(...), expected)``
exceeded ruff's 88-char limit. Ruff was wrapping the marker onto a
line outside the AST span of the read_text call.
- **Compute-from-setup** for two test methods that previously read
``self.changelog`` twice (before/after) to verify a no-op:
``before`` is now derived from the known input, dropping the first
read.
Five ``# nocheck: cache-read`` markers remain, each annotated inline
with the reason: idempotency tests where the second read must hit
disk to catch non-idempotent regressions, and tests with intermediate
``write_text`` whose subsequent read would return stale cache data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>