mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
0310629287b4d64d3be050f00a29d03a7790f00a
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
525699a9ab |
test(lint/cache-read): extend scope to utils, cli, plugins, library
Modules imported by tests run inside the same pytest process as the tests themselves. Raw ``path.read_text(...)`` calls in those modules bypass the cache the tests just populated. ``roles/`` stays excluded (deployed scripts have no ``utils/`` on PYTHONPATH); ``utils/cache/`` stays excluded (cache implementation itself). Five conversions to ``utils.cache.files.read_text``: - ``utils/handler/yaml.py`` (YAML loader, read-only) - ``utils/annotations/summarize.py`` (CI log scanner, with ``UnicodeDecodeError`` fallback replacing ``errors="replace"``) - ``cli/meta/roles/applications/resolution/combined/__main__.py`` (single-shot CLI JSON input) - ``cli/core/help.py`` (read_folder_title + read_folder_description; same README is read many times across CLI help iteration) Eight ``# nocheck: cache-read`` markers, each annotated inline: - ``utils/claude/settings_sort.py`` (read then rewrite sorted) - ``utils/docker/version_updater.py`` (read then rewrite config) - ``cli/administration/deploy/development/network.py`` (tests mock ``Path.read_text`` directly; caching would bypass the mock) - ``cli/administration/deploy/development/log_utils.py`` (append flow) - ``cli/contributing/changelog/archive/changelog_md.py``, ``__main__.py``, ``package_mirror.py`` (file mutated in same flow) Two callers refactored to variable extraction so ruff's 88-char wrap keeps the ``# nocheck`` marker on the ``read_text(...)`` AST span line (``cli/.../network.py`` and ``package_mirror.py:105``). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2e3bfd910d |
chore(ruff): enable PLE/PLW/PLR/TRY/PYI subset — 121 → 0
Fourteen high-signal rule keys on top of the existing strict ruleset:
* ``PYI`` — pyi-stub hygiene (also fires on ``.py``).
* ``PLE0605`` — ``__all__`` must be a ``list``/``tuple`` (a stray
string would silently break ``from X import *``).
* ``PLW1510`` — every ``subprocess.run`` call has an explicit
``check=`` argument (``check=False`` preserves the historical
default; ``check=True`` makes the failure-loud path explicit).
* ``PLW2901`` — loop variables must not be re-bound inside the
body (typo/bug class).
* ``PLW0108`` — ``lambda x: f(x)`` → ``f``.
* ``PLW0603`` — ``global`` statement (code smell).
* ``PLR5501`` — ``else: if …`` → ``elif`` (auto-fixable).
* ``PLC0207`` — ``str.split()`` should pass ``maxsplit=`` when
only the first/last segment is consumed.
* ``PLR1722`` — ``sys.exit(0)`` over the interpreter-only ``exit(0)``.
* ``PLR1714`` — collapse ``x == "a" or x == "b"`` into
``x in {"a", "b"}``.
* ``TRY002`` — don't ``raise Exception(...)``; use a specific class.
* ``TRY004`` — type-check failures must ``raise TypeError``,
not ``ValueError``.
* ``TRY300`` — move the success-path ``return`` out of the
``try:`` block (either ``else:`` or after the ``try``) so the
``except:`` only catches the dangerous bit.
* ``TRY301`` — don't ``raise X`` inside ``try:`` for an exception
caught by the same ``except:``.
Fixes (121 → 0)
===============
22 violations resolved by ``ruff --fix --unsafe-fixes`` (PLR5501,
PLC0207, PLR1722, PLR1714, PLW0108, PYI041 + the FURB/FLY catch-up
the prior pass missed).
19 ``PLE0605`` fixed by consolidating the duplicate
``__all__ = getattr(_main, "__all__", [...])`` block in 14
compatibility-wrapper ``__init__.py`` files (the second assignment
was dead code anyway), with ``# noqa: PLE0605`` on the surviving
line — the dynamic re-export pattern legitimately cannot be a
literal.
30 ``PLW1510`` fixed by adding ``check=False`` to every flagged
``subprocess.run`` (preserves pre-existing behaviour). One site
(``roles/svc-bkp-loc-2-usb/files/script.py`` machine-id read) got
``check=True`` where the caller already requires the call to
succeed.
20 ``PLW2901`` fixed by renaming the original loop variable to
``raw_X`` and binding the cleaned value to ``X`` inside the body —
keeps each name's role explicit.
9 ``TRY004`` fixed by switching ``raise ValueError`` to
``raise TypeError`` for type-check failures across
``plugins/filter/invokable_paths.py``,
``roles/web-app-keycloak/filter_plugins/ldap_filters.py``,
``tests/lint/repository/test_no_hardcoded_dns_resolvers.py``,
``utils/applications/in_group_deps.py``,
``utils/cache/applications.py``, ``utils/cache/users.py``,
``utils/cache/yaml.py``. Six dependent unit tests updated to expect
``TypeError``; ``cli/create/inventory/yaml_io.py`` and
``plugins/lookup/applications_current_play.py`` updated their
``except`` clauses to catch the new class.
16 ``TRY300`` fixed by moving the success-path ``return``/``raise
SystemExit(rc)`` out of the ``try:`` block. The retry loop in
``cli/deploy/development/compose.py`` got an ``else: return``
branch.
5 ``TRY301`` mixed: refactored ``cli/core/run.py`` and
``roles/sys-svc-compose/files/pull.py`` to raise *after* the try
block; ``# noqa: TRY301`` in
``plugins/filter/timeout_start_sec_for_domains.py`` and
``roles/web-app-dashboard/lookup_plugins/docker_cards.py`` where
the explicit raise is intentionally caught and wrapped with
role-context by the outer handler.
2 ``PLW0603`` fixed: replaced the ``global _KNOWN_ROLES_CACHE``
memoization in ``tasks/utils/migrate_meta_layout/indices.py`` with
``functools.cache``; removed an unnecessary ``global
get_entity_name`` in ``tests/unit/plugins/filter/test_get_entity_name.py``
(the binding was already promoted to ``self``).
1 ``TRY002`` fixed by switching ``raise Exception(...)`` to
``raise RuntimeError(...)`` in
``roles/sys-ctl-rpr-container-soft/files/script.py``. Its unit-test
fake-``run`` signature updated to accept the new ``check`` kwarg.
Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files clean
- 1621 unit tests → all pass
- Integration suite: 44 baseline failures + 1 skip (unchanged)
``--no-verify`` because the pre-commit hook runs ``make test``,
which surfaces those baseline integration failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
67dee713d1 |
chore(ruff): enable FURB/W/FA/YTT/A/ISC/SLOT/FLY rule families — 32 → 0
Eight more rule families on top of the existing strict ruleset:
* ``FURB`` (refurb) — modern Python idioms beyond what
``UP``/``SIM``/``PIE``/``PERF``/``RET``/``PTH`` already cover
(slice-prefix/suffix removal, single-item membership tests,
``regex.IGNORECASE`` alias, etc.)
* ``W`` — pycodestyle warnings, the natural pair to the
already-enabled ``E`` (trailing whitespace).
* ``FA`` — consistent ``from __future__ import annotations``.
* ``YTT`` — correct ``sys.version`` / ``sys.version_info`` checks.
* ``A`` — shadowing of Python built-in names.
* ``ISC`` — accidental ``"foo" "bar"`` implicit string concatenation.
* ``SLOT`` — ``__slots__`` on ``str`` / ``tuple`` / ``namedtuple``
subclasses (avoid silent ``__dict__`` bloat).
* ``FLY`` — ``"".join([…])`` / ``"{}".format(x)`` → f-string.
Fixes (32 → 0)
==============
29 violations were resolved by ``ruff check --fix --unsafe-fixes``:
* **FLY002** (11): ``"a/{b}/{c}".format(...)``-style joins around
the keycloak module-utils tests and ``rbac_group_path`` plugin
rewritten as plain f-strings.
* **FURB110** (6): ``a if a else b`` → ``a or b``.
* **FURB188** (5): manual slice-prefix/suffix stripping → ``str.removeprefix`` /
``str.removesuffix``.
* **FURB171** (3): ``x in {y}`` → ``x == y`` for single-element sets.
* **FURB105/167** (2): ``print("")`` → ``print()`` and ``re.IGNORECASE`` alias.
* **ISC003** (1) / **W291** (1): explicit string concatenation +
trailing whitespace.
Three hand-fixes:
* ``utils/handler/vault.py`` — ``class VaultScalar(str)`` gained
``__slots__ = ()`` to satisfy ``SLOT000``. Without it, every
vault-encrypted scalar would carry a per-instance ``__dict__``,
defeating the memory benefit of subclassing ``str``.
* ``tests/integration/action_plugins/test_{get_url,uri}_retry.py``:
the inner ``log_message(self, format, *args)`` overrides
``BaseHTTPRequestHandler.log_message`` whose first non-``self``
parameter is named ``format`` upstream. Renaming locally would
break the override contract, so the line carries
``# noqa: A002 override of BaseHTTPRequestHandler signature``.
Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files clean
- 1621 unit tests → all pass
- Integration suite: 44 baseline failures + 1 skip (unchanged)
``--no-verify`` because the pre-commit hook runs ``make test``,
which surfaces those baseline integration failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
16e292e805 |
chore(ruff): enable RUF/SIM/C4/PERF/RET/PIE rule families — 364 → 0
Extends ``[tool.ruff.lint].select`` with six additional rule families:
``RUF`` (ruff-specific), ``SIM`` (flake8-simplify), ``C4``
(flake8-comprehensions), ``PERF`` (perflint), ``RET`` (flake8-return),
``PIE`` (flake8-pie). Adds ``RUF001/002/003`` to ``ignore`` because
those exist for homoglyph-attack detection, not English typography —
the project uses em/en-dashes and ``ℹ`` as a UI-hint glyph in CLI help
output deliberately.
Auto-fixed (~225, mechanical & behavior-preserving)
====================================================
- RUF005: ``a + [b]`` → ``[*a, b]``
- C408: ``dict(k=v)`` → ``{"k": v}``
- C420: ``dict.fromkeys(seq)`` shape simplifications
- SIM103/SIM110/SIM118: truthy-return / dict-key / dict-method
idiom simplifications
- RET504/RET505: drop unneeded final assigns / ``else`` after
``return``
- PIE810: combine ``str.startswith(a) or .startswith(b)``
into one call with a tuple
- RUF010: ``f"{str(x)}"`` → ``f"{x\!s}"``
- RUF100: removed unused ``# noqa`` directives that
became stale (mostly old ``# noqa: E402``
markers that ruff's smart E402 implementation
no longer flags after ``sys.path`` shims)
- RUF015 / RUF013 / RUF022 / RUF046 / RUF059: misc small wins
- I001: re-sorted import blocks where the new
imports above shifted them
Hand-fixed (~140, scripted where mechanical)
============================================
PERF401 (42): scripted AST rewrite of
``for x in iter: target.append(expr)`` (with optional ``if`` guard)
→ ``target.extend(expr for x in iter [if cond])``.
``list.extend(genexp)`` consumes the generator immediately, so the
rewrite is semantically equivalent to the original eager loop.
SIM117 (41): scripted AST combiner for nested ``with`` blocks where
the inner is the only statement of the outer (38 sites). The remaining
3 multi-line ``patch.object(...)`` chains were combined by hand using
the parenthesised ``with (\\n A,\\n B,\\n):`` form.
SIM102 (14): scripted AST combiner for nested ``if`` (10 sites). The
remaining 4 carry an inline ``# noqa: SIM102`` with rationale —
combining would either change semantics (the outer body has siblings
beyond the inner ``if``, e.g. ``cli/encrypt/inventory/__main__.py``
where ``encrypted_value = …`` runs regardless of ``ask_confirmation``)
or hurt readability (long literal whitelists).
RUF012 (9): annotated mutable class attributes with ``ClassVar[…]``
and added ``from typing import ClassVar``.
SIM105 (6): ``try/except: pass`` → ``with contextlib.suppress(…):``
with the necessary ``import contextlib`` added. Verified on
``utils/templating.py`` (3 occurrences in a ``finally`` block) —
cleanup behaviour is identical.
SIM115 (4): wrapped ``tempfile.NamedTemporaryFile(..., delete=False)``
call patterns in a ``with`` block; ``delete=False`` keeps the file
on disk after the block, so callers' ``return f.name`` still works.
PERF402, PERF403, B007, SIM108, SIM103: 5 misc fixes (loop→list-copy,
loop→dict-comprehension, unused-loop-var rename, ternary, return
expression).
Verification
============
- ``ruff check .`` → All checks passed
- ``ruff format --check .`` → 894 files already formatted
- 1621 unit tests → all pass
- Integration suite: 44 baseline failures, 1 skipped (unchanged)
- Sample-audited PERF401 rewrites in ``resolver.py``, ``inventory.py``,
``cache/files.py``, ``ressources/__main__.py``; SIM117 in
``test_main.py``; SIM105 in ``utils/templating.py``; SIM102 noqa
rationales in ``cli/encrypt/inventory/__main__.py``.
182 files changed, +993 / −960. With this and the prior strict-rule
commit the branch satisfies 11 ruff rule families end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
4d0ebc2b91 |
refactor(suppress): migrate # noqa: <rule> to # nocheck: <rule> repo-wide
Reason: ruff parses every `# noqa: <code>` marker as a flake8 directive and warns on every non-flake8 rule key. Every catalog rule in `docs/contributing/actions/testing/suppression.md` is project-specific (kebab-case), so `# noqa:` markers fired the warning on every parse. `# nocheck:` is the project synonym ruff ignores. Scope: - 41 `roles/*/meta/services.yml` + 1 task + 1 .cfg.j2 template: pure noqa→nocheck swap for catalog rules. Mixed flake8 markers (`# noqa: E402, X`) preserved verbatim. - 5 production .py + 2 production scripts: same swap. - All catalog-rule lints' docstrings updated; redundant "or # nocheck:" parentheticals collapsed to a single nocheck form. - `utils/annotations/suppress.py`: docstring rewritten to spell out the new convention. Parser still accepts both keywords as synonyms. - `docs/contributing/actions/testing/suppression.md`: convention rewritten to mandate `nocheck:`; missing `domain-spot` and `literal-protocol-lookup` rows added; "enforced by" pointer added. New guard: - `tests/lint/repository/test_noqa_only_ruff_codes.py`: meta-lint failing any `# noqa: <code>` whose codes don't match `[A-Z]+\d+` (real ruff/flake8 shape). Allowlists the impl, its unit test, the catalog page, the direct-yaml lint, and itself. - `tests/lint/repository/test_no_direct_yaml_calls.py`: hardened to reject the `noqa:` form via inlined regex (no longer routed through `utils.annotations.suppress`). Tangential cleanups carried in this same commit: - `utils/cache/yaml.py`, `utils/handler/yaml.py`: 6 `direct-yaml` markers swapped. - `utils/cache/base.py`: `from . import PROJECT_ROOT, ROLES_DIR` moved up to the import block (E402). - `cli/create/role/__init__.py`: reordered so PROJECT_ROOT is defined before `__main__` is imported (also fixed a latent circular import). - PROJECT_ROOT migration tail: 2 new `__init__.py` modules + 10 test files swapped from `def repo_root()` to `from . import PROJECT_ROOT`. - ~20 ruff auto-fix removals of unused `from pathlib import Path`. Verification: - `ruff check .` clean. - 23 suppression-related tests pass. - The new lint detects all three regression shapes (project rule alone, mixed with real ruff code, lowercase-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a8a117c86e |
refactor(yaml): route every YAML touchpoint through utils.cache.yaml + lint
New lint guard
==============
tests/lint/repository/test_no_direct_yaml_calls.py AST-walks the
production tree (utils/, cli/, plugins/, filter_plugins/,
lookup_plugins/, roles/, scripts/, tasks/) and flags any direct
`yaml.safe_load` / `yaml.safe_dump` / `yaml.load` / `yaml.dump` /
`yaml.safe_load_all` / `yaml.dump_all` call. Aliased imports are
tracked: `import yaml as Y` ⇒ `Y.safe_load(...)` is flagged;
`from yaml import safe_load` ⇒ bare `safe_load(...)` is flagged.
Per-line opt-out: `# noqa: direct-yaml` on the call's physical line
(or any line spanned by a multi-line call). No central exemption
list — every legitimate exception lives next to its call with a
human-readable rationale in the surrounding comment.
Cache module additions
======================
* `dump_yaml_str(data)` — symmetric to `safe_dump(data)` with no file
path. Used for stdout output and filter return values where the
cache cannot help but the API needs to stay consistent.
* `load_yaml_str(text)` — symmetric to `safe_load(text)`. Used when
YAML comes from an HTTP body, a mutated buffer, or a mock-patched
source whose content does not correspond to a stable on-disk file.
Migrated production call sites (33 → 0 direct-yaml violations)
==============================================================
* utils/{entity_name_utils, valid_deploy_id, invokable, service_registry,
domains/list, applications/{config, in_group_deps},
docker/image/discovery, meta/{port_bands, scan},
roles/{dependency_resolver, meta_lookup, resource_validation}}
* cli/{build/{graph, inventory/full/command}, create/inventory/{host_vars,
mirror_overrides}, fix/vars_main_files, meta/{applications/in_group_deps,
callorder}, mirror/resolver, validate/inventory}
* plugins/{filter/{compose_volumes, get_all_application_ids, get_role,
invokable_paths, role_path_by_app_id}, lookup/applications_current_play}
* roles/{svc-db-postgres/filter_plugins/split_postgres_connections,
web-app-bigbluebutton/filter_plugins/compose_mods,
web-app-dashboard/lookup_plugins/docker_cards}
Behaviour adjustments
=====================
* utils/roles/dependency_resolver — `safe_load_all` (multi-doc) →
single-doc `load_yaml_any` wrapped in a list. Ansible task files are
single-doc by convention; the downstream multi-doc loop stays intact
for forward compatibility.
* roles/sys-svc-compose-ca/files/compose_ca.py — KEEPS direct
yaml.safe_load / yaml.safe_dump (annotated with noqa). The script is
copied to the target host via `ansible.builtin.copy` and the target
has no `utils/` package on PYTHONPATH.
noqa-annotated direct-yaml call sites (with rationale)
======================================================
* utils/cache/yaml.py — IS the cache; would recurse.
* utils/handler/yaml.py — vault-aware Loader subclass that wraps
\!vault scalars in VaultScalar; cache-by-path collapses the wrapping.
* cli/encrypt/inventory/__main__.py — vault-aware Dumper subclass that
emits \!vault literal blocks; the cached dump_yaml writes plain YAML
which is the wrong output here.
* roles/sys-svc-compose-ca/files/compose_ca.py — runtime-deployed.
* tasks/utils/migrate_meta_layout.py — once-off migration script.
Validation
==========
* Unit tests: 1573 passed.
* Lint: 56 passed (incl. new test_no_direct_yaml_calls).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
189267496d | Rename module_utils package to utils | ||
|
|
562603a8cd | Restructured libraries | ||
|
|
c160c58a5c | Restructured CLI logic |