refactor: convert f-string logging to lazy %-style format (G004)

Sweep all 1676 G004 violations across 112 files, converting
`logger.<level>(f"…{x}…")` to `logger.<level>("…%s…", x)`.

Why: ruff rule G004 was added to pyproject.toml to enforce lazy
%-style logging — defers formatting until the log level is enabled
and lets structured log tooling match the unformatted template.

Conversion preserves rendered output byte-for-byte:
- `{x}` → `%s` + `x`
- `{x!r}` / `{x!s}` / `{x!a}` → `%r` / `%s` / `%a`
- Format specs (`{x:.2f}`, `{x:>10}`) → `%s` + `format(x, 'spec')`
  (printf-style specs aren't 1:1 with Python format specs, so we
  delegate to `format()` to keep identical output)
- Literal `%` → `%%`
- Concatenated f-strings (`f"a {x} " "b"`) flattened
- Trailing kwargs (`exc_info=True`) preserved

Verified:
- `uv run ruff check --select G004` → 0 violations
- `uv run ty check -- nextcloud_mcp_server` → passes
- `uv run pytest tests/unit/` → 1010 passed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-13 01:12:17 +02:00
co-authored by Claude Opus 4.7
parent a4e6125d28
commit 665cb9b1eb
112 changed files with 2534 additions and 1859 deletions
+16 -13
View File
@@ -90,7 +90,7 @@ async def _poll_and_store(provision_id: str) -> None:
result = await flow_client.poll(poll_endpoint, poll_token)
except Exception as e:
logger.warning(
f"Login Flow v2 poll error for provision {provision_id}: {e}"
"Login Flow v2 poll error for provision %s: %s", provision_id, e
)
await anyio.sleep(2)
continue
@@ -106,7 +106,8 @@ async def _poll_and_store(provision_id: str) -> None:
if session:
session["status"] = "error"
logger.error(
f"Login Flow v2 completed but no app_password (provision_id={provision_id})"
"Login Flow v2 completed but no app_password (provision_id=%s)",
provision_id,
)
return
await storage.store_app_password_with_scopes(
@@ -121,8 +122,9 @@ async def _poll_and_store(provision_id: str) -> None:
session["status"] = "completed"
session["username"] = result.login_name
logger.info(
f"Login Flow v2 web provision completed for user {effective_user_id} "
f"(provision_id={provision_id})"
"Login Flow v2 web provision completed for user %s (provision_id=%s)",
effective_user_id,
provision_id,
)
return
@@ -131,7 +133,7 @@ async def _poll_and_store(provision_id: str) -> None:
if session:
session["status"] = "expired"
logger.warning(
f"Login Flow v2 web provision expired (provision_id={provision_id})"
"Login Flow v2 web provision expired (provision_id=%s)", provision_id
)
return
@@ -142,7 +144,7 @@ async def _poll_and_store(provision_id: str) -> None:
if session:
session["status"] = "expired"
logger.warning(
f"Login Flow v2 web provision timed out (provision_id={provision_id})"
"Login Flow v2 web provision timed out (provision_id=%s)", provision_id
)
@@ -166,7 +168,7 @@ async def provision_page(
try:
user_id, _token_data = await validate_token_and_get_user(request)
except (ValueError, KeyError, AttributeError) as e:
logger.warning(f"Provision request rejected: {e}")
logger.warning("Provision request rejected: %s", e)
return JSONResponse({"error": "Authentication required"}, status_code=401)
_cleanup_expired_sessions()
@@ -180,14 +182,14 @@ async def provision_page(
)
if urlparse(redirect_uri).scheme == "http":
logger.warning(f"Provision redirect_uri uses insecure HTTP: {redirect_uri}")
logger.warning("Provision redirect_uri uses insecure HTTP: %s", redirect_uri)
# Check if user already has an app password — skip straight to redirect
if user_id:
storage = await get_shared_storage()
existing = await storage.get_app_password_with_scopes(user_id)
if existing:
logger.info(f"User {user_id} already has app password, skipping provision")
logger.info("User %s already has app password, skipping provision", user_id)
return RedirectResponse(redirect_uri)
# Initiate Login Flow v2
@@ -206,7 +208,7 @@ async def provision_page(
)
init_response = await flow_client.initiate()
except Exception as e:
logger.error(f"Failed to initiate Login Flow v2 for web provision: {e}")
logger.error("Failed to initiate Login Flow v2 for web provision: %s", e)
return HTMLResponse(
content=_render_error(
"Failed to start login flow. Please try again later."
@@ -241,8 +243,9 @@ async def provision_page(
poll_tg.start_soon(_poll_and_store, provision_id)
logger.info(
f"Login Flow v2 web provision initiated (provision_id={provision_id}, "
f"user_id={user_id or 'unknown'}), redirecting to NC login"
"Login Flow v2 web provision initiated (provision_id=%s, user_id=%s), redirecting to NC login",
provision_id,
user_id or "unknown",
)
# Redirect to Nextcloud's Login Flow v2 login page.
@@ -277,7 +280,7 @@ async def provision_status(request: Request) -> JSONResponse:
try:
_user_id, _token_data = await validate_token_and_get_user(request)
except (ValueError, KeyError, AttributeError) as e:
logger.warning(f"Provision status request rejected: {e}")
logger.warning("Provision status request rejected: %s", e)
return JSONResponse({"error": "Authentication required"}, status_code=401)
provision_id = request.query_params.get("id", "")