mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-12 21:56:21 +00:00
Adds six more rule families to the strict ruleset: - ``T10`` — committed ``breakpoint()`` / ``pdb`` calls - ``PGH`` — pygrep-hooks (blanket ``# type: ignore`` without code) - ``EXE`` — shebang/executable-bit consistency - ``RSE`` — ``raise X()`` style cleanups - ``ICN`` — standard import aliases - ``DTZ`` — tz-naive ``datetime`` bugs Fixes (99 violations cleared) ============================= EXE001 (68): every file with a Python shebang now has the executable bit set. Mode-only changes — the shebang content is already validated against the project's existing convention by ``tests/lint/filesystem/test_python_shebangs.py`` (``#\!/usr/bin/env python3`` for portable scripts, ``#\!/usr/bin/python`` for Ansible custom modules in ``library/``). PGH003 (17): removed blanket ``# type: ignore`` markers. The project doesn't run mypy/pyright in CI, so these were decoration only. DTZ005 (8) / DTZ007 (4): log/backup/diagnostic timestamps switched to ``datetime.now(tz=timezone.utc)`` so cross-host log correlation doesn't depend on each host's local TZ. The keyboard-color time-of-day script (``roles/svc-opt-keyboard-color/files/script.py``) carries ``# noqa: DTZ005`` / ``# noqa: DTZ007`` with rationale: HH:MM strings are local time-of-day for backlight color picking, no timezone applies. The OpenSSL date parser in ``utils/cert_utils.py`` anchors the parsed naive datetime to UTC explicitly via ``.replace(tzinfo=UTC)`` (openssl always emits the literal "GMT"). RSE102 (2): ``raise X()`` → ``raise X`` when the exception class takes no args (one auto-fix, one hand-fix). Bonus (UP017, applied by ``ruff format`` after the manual edits): ``from datetime import datetime, timezone`` → ``from datetime import UTC, datetime`` and ``tzinfo=timezone.utc`` → ``tzinfo=UTC`` (Python 3.11+ shorthand). Verification ============ - ``ruff check .`` → All checks passed - ``ruff format --check .`` → 894 files formatted - 1621 unit tests → all pass - Integration suite: 44 baseline failures, 1 skipped (unchanged) 86 files changed, +55 / −37 content lines, plus 68 mode-only changes (``chmod +x``). Combined with the prior strict-rule commits the branch satisfies 17 ruff rule families end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
2.3 KiB
Python
Executable File
70 lines
2.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Local action plugin that extends ansible's uri action plugin with centralized
|
|
# retry defaults while preserving full uri feature compatibility.
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
from ansible.plugins.action.uri import ActionModule as UriActionModule
|
|
|
|
|
|
class ActionModule(UriActionModule):
|
|
# Default policy when no task keywords are provided.
|
|
DEFAULT_RETRIES = 30
|
|
DEFAULT_DELAY = 2
|
|
|
|
def run(self, tmp=None, task_vars=None):
|
|
if task_vars is None:
|
|
task_vars = {}
|
|
|
|
# Respect explicit Ansible until/retry logic to avoid nested loops.
|
|
if self._task_keyword_is_set("until"):
|
|
return super().run(tmp=tmp, task_vars=task_vars)
|
|
|
|
if self._task.async_val:
|
|
return super().run(tmp=tmp, task_vars=task_vars)
|
|
|
|
retries = self._task_keyword_int("retries", self.DEFAULT_RETRIES)
|
|
delay = self._task_keyword_int("delay", self.DEFAULT_DELAY)
|
|
|
|
attempts_total = max(1, retries + 1)
|
|
last_result = None
|
|
for attempt in range(1, attempts_total + 1):
|
|
last_result = super().run(tmp=tmp, task_vars=task_vars)
|
|
if not last_result.get("failed", False):
|
|
last_result["attempts"] = attempt
|
|
return last_result
|
|
|
|
if attempt < attempts_total:
|
|
time.sleep(delay)
|
|
|
|
last_result["attempts"] = attempts_total
|
|
return last_result
|
|
|
|
def _task_keyword_is_set(self, key):
|
|
ds = self._task.get_ds()
|
|
return isinstance(ds, dict) and key in ds
|
|
|
|
def _task_keyword_int(self, key, fallback):
|
|
# `retries` can be present on the parsed task object even if it is not
|
|
# visible in get_ds(); prefer the task attribute for reliable override.
|
|
if key == "retries":
|
|
retries_value = getattr(self._task, "retries", None)
|
|
if retries_value is None:
|
|
return fallback
|
|
return self._as_int(retries_value, fallback)
|
|
|
|
if not self._task_keyword_is_set(key):
|
|
return fallback
|
|
|
|
return self._as_int(getattr(self._task, key, None), fallback)
|
|
|
|
@staticmethod
|
|
def _as_int(value, fallback):
|
|
try:
|
|
return int(value)
|
|
except (TypeError, ValueError):
|
|
return fallback
|