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
+68 -51
View File
@@ -184,12 +184,12 @@ class RefreshTokenStorage:
if has_schema:
logger.info(
f"Detected pre-Alembic database at {self.db_path}, "
"stamping with initial revision"
"Detected pre-Alembic database at %s, stamping with initial revision",
self.db_path,
)
else:
logger.info(
f"Initializing new database at {self.db_path} with migrations"
"Initializing new database at %s with migrations", self.db_path
)
# Run migrations in a worker thread using anyio.to_thread
@@ -215,7 +215,7 @@ class RefreshTokenStorage:
os.chmod(self.db_path, 0o600)
self._initialized = True
logger.info(f"Initialized refresh token storage at {self.db_path}")
logger.info("Initialized refresh token storage at %s", self.db_path)
async def store_refresh_token(
self,
@@ -333,7 +333,7 @@ class RefreshTokenStorage:
)
await db.commit()
logger.debug(f"Cached user profile for {user_id}")
logger.debug("Cached user profile for %s", user_id)
async def get_user_profile(self, user_id: str) -> dict[str, Any] | None:
"""
@@ -419,7 +419,7 @@ class RefreshTokenStorage:
row = await cursor.fetchone()
if not row:
logger.debug(f"No refresh token found for user {user_id}")
logger.debug("No refresh token found for user %s", user_id)
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "success")
return None
@@ -437,7 +437,9 @@ class RefreshTokenStorage:
# Check expiration
if expires_at is not None and expires_at < time.time():
logger.warning(
f"Refresh token for user {user_id} has expired (expired at {expires_at})"
"Refresh token for user %s has expired (expired at %s)",
user_id,
expires_at,
)
await self.delete_refresh_token(user_id)
duration = time.time() - start_time
@@ -448,7 +450,9 @@ class RefreshTokenStorage:
scopes = json.loads(scopes_json) if scopes_json else None
logger.debug(
f"Retrieved refresh token for user {user_id} (flow_type: {flow_type})"
"Retrieved refresh token for user %s (flow_type: %s)",
user_id,
flow_type,
)
duration = time.time() - start_time
@@ -467,7 +471,7 @@ class RefreshTokenStorage:
except Exception as e:
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "error")
logger.error(f"Failed to decrypt refresh token for user {user_id}: {e}")
logger.error("Failed to decrypt refresh token for user %s: %s", user_id, e)
return None
async def get_refresh_token_by_provisioning_client_id(
@@ -511,7 +515,8 @@ class RefreshTokenStorage:
if not row:
logger.debug(
f"No refresh token found for provisioning_client_id {provisioning_client_id[:16]}..."
"No refresh token found for provisioning_client_id %s...",
provisioning_client_id[:16],
)
return None
@@ -529,7 +534,8 @@ class RefreshTokenStorage:
# Check expiration
if expires_at is not None and expires_at < time.time():
logger.warning(
f"Refresh token for provisioning_client_id {provisioning_client_id[:16]}... has expired"
"Refresh token for provisioning_client_id %s... has expired",
provisioning_client_id[:16],
)
return None
@@ -538,7 +544,9 @@ class RefreshTokenStorage:
scopes = json.loads(scopes_json) if scopes_json else None
logger.debug(
f"Retrieved refresh token for provisioning_client_id {provisioning_client_id[:16]}... (user_id: {user_id})"
"Retrieved refresh token for provisioning_client_id %s... (user_id: %s)",
provisioning_client_id[:16],
user_id,
)
return {
@@ -553,7 +561,9 @@ class RefreshTokenStorage:
}
except Exception as e:
logger.error(
f"Failed to decrypt refresh token for provisioning_client_id {provisioning_client_id[:16]}...: {e}"
"Failed to decrypt refresh token for provisioning_client_id %s...: %s",
provisioning_client_id[:16],
e,
)
return None
@@ -584,14 +594,14 @@ class RefreshTokenStorage:
record_db_operation("sqlite", "delete", duration, "success")
if deleted:
logger.info(f"Deleted refresh token for user {user_id}")
logger.info("Deleted refresh token for user %s", user_id)
await self._audit_log(
event="delete_refresh_token",
user_id=user_id,
auth_method="offline_access",
)
else:
logger.debug(f"No refresh token to delete for user {user_id}")
logger.debug("No refresh token to delete for user %s", user_id)
return deleted
except Exception:
@@ -616,7 +626,7 @@ class RefreshTokenStorage:
rows = await cursor.fetchall()
user_ids = [row[0] for row in rows]
logger.debug(f"Found {len(user_ids)} users with refresh tokens")
logger.debug("Found %s users with refresh tokens", len(user_ids))
return user_ids
async def cleanup_expired_tokens(self) -> int:
@@ -640,7 +650,7 @@ class RefreshTokenStorage:
deleted = cursor.rowcount
if deleted > 0:
logger.info(f"Cleaned up {deleted} expired refresh token(s)")
logger.info("Cleaned up %s expired refresh token(s)", deleted)
return deleted
@@ -718,8 +728,9 @@ class RefreshTokenStorage:
await db.commit()
logger.info(
f"Stored OAuth client credentials (client_id: {client_id[:16]}..., "
f"expires at {client_secret_expires_at})"
"Stored OAuth client credentials (client_id: %s..., expires at %s)",
client_id[:16],
client_secret_expires_at,
)
# Audit log
@@ -785,7 +796,7 @@ class RefreshTokenStorage:
# Check expiration
if expires_at < time.time():
logger.warning(
f"OAuth client has expired (expired at {expires_at}), deleting"
"OAuth client has expired (expired at %s), deleting", expires_at
)
await self.delete_oauth_client()
return None
@@ -803,7 +814,7 @@ class RefreshTokenStorage:
redirect_uris = json.loads(redirect_uris_json)
logger.debug(
f"Retrieved OAuth client credentials (client_id: {client_id[:16]}...)"
"Retrieved OAuth client credentials (client_id: %s...)", client_id[:16]
)
return {
@@ -817,7 +828,7 @@ class RefreshTokenStorage:
}
except Exception as e:
logger.error(f"Failed to decrypt OAuth client credentials: {e}")
logger.error("Failed to decrypt OAuth client credentials: %s", e)
return None
async def delete_oauth_client(self) -> bool:
@@ -1017,7 +1028,9 @@ class RefreshTokenStorage:
)
await db.commit()
logger.debug(f"Stored OAuth session {session_id} (expires in {ttl_seconds}s)")
logger.debug(
"Stored OAuth session %s (expires in %ss)", session_id, ttl_seconds
)
async def get_oauth_session(self, session_id: str) -> dict | None:
"""
@@ -1043,7 +1056,7 @@ class RefreshTokenStorage:
# Check expiration
if session["expires_at"] < time.time():
logger.debug(f"OAuth session {session_id} has expired")
logger.debug("OAuth session %s has expired", session_id)
await self.delete_oauth_session(session_id)
return None
@@ -1077,7 +1090,8 @@ class RefreshTokenStorage:
# Check expiration
if session["expires_at"] < time.time():
logger.debug(
f"OAuth session with MCP code {mcp_authorization_code[:16]}... has expired"
"OAuth session with MCP code %s... has expired",
mcp_authorization_code[:16],
)
await self.delete_oauth_session(session["session_id"])
return None
@@ -1133,7 +1147,7 @@ class RefreshTokenStorage:
updated = cursor.rowcount > 0
if updated:
logger.debug(f"Updated OAuth session {session_id}")
logger.debug("Updated OAuth session %s", session_id)
return updated
@@ -1155,7 +1169,7 @@ class RefreshTokenStorage:
deleted = cursor.rowcount > 0
if deleted:
logger.debug(f"Deleted OAuth session {session_id}")
logger.debug("Deleted OAuth session %s", session_id)
return deleted
@@ -1179,7 +1193,7 @@ class RefreshTokenStorage:
deleted = cursor.rowcount
if deleted > 0:
logger.info(f"Cleaned up {deleted} expired OAuth session(s)")
logger.info("Cleaned up %s expired OAuth session(s)", deleted)
return deleted
@@ -1339,7 +1353,7 @@ class RefreshTokenStorage:
)
await db.commit()
logger.debug(f"Stored webhook {webhook_id} for preset '{preset_id}'")
logger.debug("Stored webhook %s for preset '%s'", webhook_id, preset_id)
async def get_webhooks_by_preset(self, preset_id: str) -> list[int]:
"""
@@ -1384,7 +1398,7 @@ class RefreshTokenStorage:
deleted = cursor.rowcount > 0
if deleted:
logger.debug(f"Deleted webhook {webhook_id} from tracking")
logger.debug("Deleted webhook %s from tracking", webhook_id)
return deleted
@@ -1430,7 +1444,7 @@ class RefreshTokenStorage:
deleted = cursor.rowcount
if deleted > 0:
logger.debug(f"Cleared {deleted} webhook(s) for preset '{preset_id}'")
logger.debug("Cleared %s webhook(s) for preset '%s'", deleted, preset_id)
return deleted
@@ -1482,7 +1496,7 @@ class RefreshTokenStorage:
duration = time.time() - start_time
record_db_operation("sqlite", "insert", duration, "success")
logger.info(f"Stored app password for user {user_id}")
logger.info("Stored app password for user %s", user_id)
except Exception:
duration = time.time() - start_time
@@ -1525,7 +1539,7 @@ class RefreshTokenStorage:
row = await cursor.fetchone()
if not row:
logger.debug(f"No app password found for user {user_id}")
logger.debug("No app password found for user %s", user_id)
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "success")
return None
@@ -1535,14 +1549,14 @@ class RefreshTokenStorage:
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "success")
logger.debug(f"Retrieved app password for user {user_id}")
logger.debug("Retrieved app password for user %s", user_id)
return decrypted_password
except Exception as e:
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "error")
logger.error(f"Failed to decrypt app password for user {user_id}: {e}")
logger.error("Failed to decrypt app password for user %s: %s", user_id, e)
return None
async def delete_app_password(self, user_id: str) -> bool:
@@ -1572,14 +1586,14 @@ class RefreshTokenStorage:
record_db_operation("sqlite", "delete", duration, "success")
if deleted:
logger.info(f"Deleted app password for user {user_id}")
logger.info("Deleted app password for user %s", user_id)
await self._audit_log(
event="delete_app_password",
user_id=user_id,
auth_method="app_password",
)
else:
logger.debug(f"No app password to delete for user {user_id}")
logger.debug("No app password to delete for user %s", user_id)
return deleted
@@ -1605,7 +1619,7 @@ class RefreshTokenStorage:
rows = await cursor.fetchall()
user_ids = [row[0] for row in rows]
logger.debug(f"Found {len(user_ids)} users with app passwords")
logger.debug("Found %s users with app passwords", len(user_ids))
return user_ids
async def cleanup_invalid_app_passwords(self, nextcloud_host: str) -> list[str]:
@@ -1651,19 +1665,21 @@ class RefreshTokenStorage:
if response.status_code in (401, 403):
logger.info(
f"App password for {user_id} is invalid "
f"(HTTP {response.status_code}), removing"
"App password for %s is invalid (HTTP %s), removing",
user_id,
response.status_code,
)
await self.delete_app_password(user_id)
removed.append(user_id)
else:
logger.debug(
f"App password for {user_id} validated "
f"(HTTP {response.status_code})"
"App password for %s validated (HTTP %s)",
user_id,
response.status_code,
)
except Exception as e:
logger.warning(f"Could not validate app password for {user_id}: {e}")
logger.warning("Could not validate app password for %s: %s", user_id, e)
async with anyio.create_task_group() as tg:
for user_id in user_ids:
@@ -1745,9 +1761,10 @@ class RefreshTokenStorage:
duration = time.time() - start_time
record_db_operation("sqlite", "insert", duration, "success")
logger.info(
f"Stored scoped app password for user {user_id} "
f"(scopes={'all' if scopes is None else len(scopes)}, "
f"username={username or 'N/A'})"
"Stored scoped app password for user %s (scopes=%s, username=%s)",
user_id,
"all" if scopes is None else len(scopes),
username or "N/A",
)
except Exception:
@@ -1793,7 +1810,7 @@ class RefreshTokenStorage:
row = await cursor.fetchone()
if not row:
logger.debug(f"No app password found for user {user_id}")
logger.debug("No app password found for user %s", user_id)
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "success")
return None
@@ -1917,7 +1934,7 @@ class RefreshTokenStorage:
duration = time.time() - start_time
record_db_operation("sqlite", "insert", duration, "success")
logger.info(f"Stored login flow session for user {user_id}")
logger.info("Stored login flow session for user %s", user_id)
except Exception:
duration = time.time() - start_time
@@ -1984,7 +2001,7 @@ class RefreshTokenStorage:
duration = time.time() - start_time
record_db_operation("sqlite", "select", duration, "error")
logger.error(
f"Failed to retrieve login flow session for user {user_id}: {e}"
"Failed to retrieve login flow session for user %s: %s", user_id, e
)
raise
@@ -2014,7 +2031,7 @@ class RefreshTokenStorage:
record_db_operation("sqlite", "delete", duration, "success")
if deleted:
logger.info(f"Deleted login flow session for user {user_id}")
logger.info("Deleted login flow session for user %s", user_id)
await self._audit_log(
event="delete_login_flow_session",
user_id=user_id,
@@ -2052,7 +2069,7 @@ class RefreshTokenStorage:
record_db_operation("sqlite", "delete", duration, "success")
if count > 0:
logger.info(f"Cleaned up {count} expired login flow sessions")
logger.info("Cleaned up %s expired login flow sessions", count)
await self._audit_log(
event="delete_expired_login_flow_sessions",
user_id="system",