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
+34 -25
View File
@@ -69,7 +69,7 @@ class UnifiedTokenVerifier(TokenVerifier):
# JWT verification support
self.jwks_client: PyJWKClient | None = None
if hasattr(settings, "jwks_uri") and settings.jwks_uri:
logger.info(f"JWT verification enabled with JWKS URI: {settings.jwks_uri}")
logger.info("JWT verification enabled with JWKS URI: %s", settings.jwks_uri)
self.jwks_client = PyJWKClient(settings.jwks_uri, cache_keys=True)
# Introspection support (for opaque tokens)
@@ -81,7 +81,7 @@ class UnifiedTokenVerifier(TokenVerifier):
and settings.oidc_client_secret
):
self.introspection_uri = settings.introspection_uri
logger.info(f"Token introspection enabled: {self.introspection_uri}")
logger.info("Token introspection enabled: %s", self.introspection_uri)
# Build list of valid issuers (internal + public may differ in Docker)
# AS proxy obtains tokens via internal URL (e.g. http://app:80), while
@@ -114,14 +114,16 @@ class UnifiedTokenVerifier(TokenVerifier):
)
else:
logger.info(
f"Management API allowlist: {sorted(self._allowed_mgmt_clients)}"
"Management API allowlist: %s", sorted(self._allowed_mgmt_clients)
)
logger.info(
f"UnifiedTokenVerifier initialized in {self.mode} mode. "
f"MCP audience: {settings.oidc_client_id} or {settings.nextcloud_mcp_server_url}, "
f"Nextcloud resource URI: {settings.nextcloud_resource_uri}, "
f"Valid issuers: {self.valid_issuers}"
"UnifiedTokenVerifier initialized in %s mode. MCP audience: %s or %s, Nextcloud resource URI: %s, Valid issuers: %s",
self.mode,
settings.oidc_client_id,
settings.nextcloud_mcp_server_url,
settings.nextcloud_resource_uri,
self.valid_issuers,
)
async def verify_token(self, token: str) -> AccessToken | None:
@@ -281,9 +283,10 @@ class UnifiedTokenVerifier(TokenVerifier):
if not self._has_mcp_audience(payload):
audiences = payload.get("aud", [])
logger.error(
f"Token rejected: Missing MCP audience. "
f"Got {audiences}, need MCP ({self.settings.oidc_client_id} or "
f"{self.settings.nextcloud_mcp_server_url})"
"Token rejected: Missing MCP audience. Got %s, need MCP (%s or %s)",
audiences,
self.settings.oidc_client_id,
self.settings.nextcloud_mcp_server_url,
)
# Record as invalid due to audience mismatch
record_oauth_token_validation(validation_method, "invalid")
@@ -303,7 +306,7 @@ class UnifiedTokenVerifier(TokenVerifier):
return self._create_access_token(token, payload)
except Exception as e:
logger.error(f"Token verification failed: {e}")
logger.error("Token verification failed: %s", e)
record_oauth_token_validation(validation_method, "error")
return None
@@ -368,14 +371,15 @@ class UnifiedTokenVerifier(TokenVerifier):
# Skip audience validation - any valid Nextcloud token is accepted
logger.debug(
f"Management API token validated (no audience check) for user: {payload.get('sub')}"
"Management API token validated (no audience check) for user: %s",
payload.get("sub"),
)
# Cache and return the token
return self._create_access_token_with_cache_key(token, payload, cache_key)
except Exception as e:
logger.error(f"Management API token verification failed: {e}")
logger.error("Management API token verification failed: %s", e)
record_oauth_token_validation(validation_method, "error")
return None
@@ -478,20 +482,20 @@ class UnifiedTokenVerifier(TokenVerifier):
f"expected one of: {self.valid_issuers}"
)
logger.debug(f"JWT signature verified for user: {payload.get('sub')}")
logger.debug("JWT signature verified for user: %s", payload.get("sub"))
return payload
except jwt.ExpiredSignatureError:
logger.info("JWT token has expired")
return None
except jwt.InvalidIssuerError as e:
logger.warning(f"JWT issuer validation failed: {e}")
logger.warning("JWT issuer validation failed: %s", e)
return None
except jwt.InvalidTokenError as e:
logger.warning(f"JWT validation failed: {e}")
logger.warning("JWT validation failed: %s", e)
return None
except Exception as e:
logger.error(f"Unexpected error during JWT verification: {e}")
logger.error("Unexpected error during JWT verification: %s", e)
return None
async def _introspect_token(self, token: str) -> dict[str, Any] | None:
@@ -528,20 +532,23 @@ class UnifiedTokenVerifier(TokenVerifier):
return None
logger.debug(
f"Token introspected successfully for user: {introspection_data.get('sub')}"
"Token introspected successfully for user: %s",
introspection_data.get("sub"),
)
return introspection_data
elif response.status_code in (400, 401, 403):
logger.warning(
f"Token introspection failed: HTTP {response.status_code}. "
f"Response: {response.text[:200] if response.text else 'empty'}"
"Token introspection failed: HTTP %s. Response: %s",
response.status_code,
response.text[:200] if response.text else "empty",
)
return None
else:
logger.warning(
f"Unexpected response from introspection: {response.status_code}. "
f"Response: {response.text[:200] if response.text else 'empty'}"
"Unexpected response from introspection: %s. Response: %s",
response.status_code,
response.text[:200] if response.text else "empty",
)
return None
@@ -549,10 +556,10 @@ class UnifiedTokenVerifier(TokenVerifier):
logger.error("Timeout while introspecting token")
return None
except httpx.RequestError as e:
logger.error(f"Network error while introspecting token: {e}")
logger.error("Network error while introspecting token: %s", e)
return None
except Exception as e:
logger.error(f"Unexpected error during token introspection: {e}")
logger.error("Unexpected error during token introspection: %s", e)
return None
def _create_access_token(
@@ -598,7 +605,9 @@ class UnifiedTokenVerifier(TokenVerifier):
scope_string = payload.get("scope", "")
scopes = scope_string.split() if scope_string else []
logger.debug(
f"Extracted scopes from token - scope claim: '{scope_string}' -> scopes list: {scopes}"
"Extracted scopes from token - scope claim: '%s' -> scopes list: %s",
scope_string,
scopes,
)
# Extract expiration