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:
co-authored by
Claude Opus 4.7
parent
a4e6125d28
commit
665cb9b1eb
@@ -215,7 +215,7 @@ class BenchmarkMetrics:
|
||||
@asynccontextmanager
|
||||
async def create_mcp_session(url: str):
|
||||
"""Create an MCP client session with proper cleanup."""
|
||||
logger.info(f"Creating MCP client session for {url}")
|
||||
logger.info("Creating MCP client session for %s", url)
|
||||
streamable_context = streamablehttp_client(url)
|
||||
session_context = None
|
||||
|
||||
@@ -231,17 +231,17 @@ async def create_mcp_session(url: str):
|
||||
try:
|
||||
await session_context.__aexit__(None, None, None)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error closing session: {e}")
|
||||
logger.debug("Error closing session: %s", e)
|
||||
|
||||
try:
|
||||
await streamable_context.__aexit__(None, None, None)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error closing streamable context: {e}")
|
||||
logger.debug("Error closing streamable context: %s", e)
|
||||
|
||||
|
||||
async def wait_for_mcp_server(url: str, max_attempts: int = 10) -> bool:
|
||||
"""Wait for MCP server to be ready."""
|
||||
logger.info(f"Waiting for MCP server at {url}...")
|
||||
logger.info("Waiting for MCP server at %s...", url)
|
||||
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
@@ -252,10 +252,10 @@ async def wait_for_mcp_server(url: str, max_attempts: int = 10) -> bool:
|
||||
return True
|
||||
except Exception as e:
|
||||
if attempt < max_attempts:
|
||||
logger.debug(f"Attempt {attempt}/{max_attempts}: {e}")
|
||||
logger.debug("Attempt %s/%s: %s", attempt, max_attempts, e)
|
||||
await anyio.sleep(2)
|
||||
else:
|
||||
logger.error(f"MCP server not ready after {max_attempts} attempts")
|
||||
logger.error("MCP server not ready after %s attempts", max_attempts)
|
||||
return False
|
||||
|
||||
return False
|
||||
@@ -269,7 +269,7 @@ async def benchmark_worker(
|
||||
stop_event: anyio.Event,
|
||||
):
|
||||
"""Single worker that runs operations for the specified duration."""
|
||||
logger.info(f"Worker {worker_id} starting...")
|
||||
logger.info("Worker %s starting...", worker_id)
|
||||
|
||||
try:
|
||||
async with create_mcp_session(url) as session:
|
||||
@@ -297,10 +297,10 @@ async def benchmark_worker(
|
||||
# Cleanup
|
||||
await ops.cleanup()
|
||||
|
||||
logger.info(f"Worker {worker_id} completed {operation_count} operations")
|
||||
logger.info("Worker %s completed %s operations", worker_id, operation_count)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Worker {worker_id} error: {e}", exc_info=True)
|
||||
logger.error("Worker %s error: %s", worker_id, e, exc_info=True)
|
||||
|
||||
|
||||
async def run_benchmark(
|
||||
|
||||
@@ -74,7 +74,7 @@ class OAuthCallbackServer:
|
||||
|
||||
if code and state:
|
||||
self.auth_states[state] = code
|
||||
logger.info(f"Captured auth code for state {state[:16]}...")
|
||||
logger.info("Captured auth code for state %s...", state[:16])
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
@@ -94,7 +94,9 @@ class OAuthCallbackServer:
|
||||
self.server = HTTPServer((self.host, self.port), CallbackHandler)
|
||||
|
||||
def run():
|
||||
logger.info(f"OAuth callback server listening on {self.host}:{self.port}")
|
||||
logger.info(
|
||||
"OAuth callback server listening on %s:%s", self.host, self.port
|
||||
)
|
||||
self.server.serve_forever()
|
||||
|
||||
self.thread = threading.Thread(target=run, daemon=True)
|
||||
@@ -135,7 +137,7 @@ async def discover_oidc_endpoints(nextcloud_host: str) -> dict[str, str]:
|
||||
"token_endpoint": config["token_endpoint"],
|
||||
"registration_endpoint": config["registration_endpoint"],
|
||||
}
|
||||
logger.info(f"Discovered endpoints: {endpoints}")
|
||||
logger.info("Discovered endpoints: %s", endpoints)
|
||||
return endpoints
|
||||
|
||||
|
||||
@@ -168,7 +170,7 @@ async def setup_oauth_client(
|
||||
redirect_uris=[callback_url],
|
||||
)
|
||||
|
||||
logger.info(f"OAuth client setup complete (client_id: {client_info.client_id})")
|
||||
logger.info("OAuth client setup complete (client_id: %s)", client_info.client_id)
|
||||
return {
|
||||
"client_id": client_info.client_id,
|
||||
"client_secret": client_info.client_secret,
|
||||
@@ -197,7 +199,7 @@ async def create_and_authenticate_user(
|
||||
Returns:
|
||||
OAuth access token for the user
|
||||
"""
|
||||
logger.info(f"Creating and authenticating user: {username}")
|
||||
logger.info("Creating and authenticating user: %s", username)
|
||||
|
||||
# Create Nextcloud user
|
||||
await user_pool.create_nextcloud_user(
|
||||
@@ -218,7 +220,7 @@ async def create_and_authenticate_user(
|
||||
auth_states=auth_states,
|
||||
)
|
||||
|
||||
logger.info(f"Successfully authenticated user: {username}")
|
||||
logger.info("Successfully authenticated user: %s", username)
|
||||
return token
|
||||
|
||||
|
||||
@@ -239,7 +241,7 @@ async def oauth_benchmark_worker(
|
||||
metrics: Metrics collector
|
||||
stop_event: Event to signal stop
|
||||
"""
|
||||
logger.info(f"Worker for {user_wrapper.username} starting...")
|
||||
logger.info("Worker for %s starting...", user_wrapper.username)
|
||||
|
||||
start_time = time.time()
|
||||
operation_count = 0
|
||||
@@ -265,18 +267,21 @@ async def oauth_benchmark_worker(
|
||||
await anyio.sleep(0.05)
|
||||
|
||||
logger.info(
|
||||
f"Worker for {user_wrapper.username} completed {operation_count} operations"
|
||||
"Worker for %s completed %s operations",
|
||||
user_wrapper.username,
|
||||
operation_count,
|
||||
)
|
||||
|
||||
except anyio.get_cancelled_exc_class():
|
||||
# Handle task cancellation gracefully (e.g., during benchmark shutdown)
|
||||
logger.info(
|
||||
f"Worker for {user_wrapper.username} was cancelled "
|
||||
f"(completed {operation_count} operations)"
|
||||
"Worker for %s was cancelled (completed %s operations)",
|
||||
user_wrapper.username,
|
||||
operation_count,
|
||||
)
|
||||
raise # Re-raise to allow proper cleanup
|
||||
except Exception as e:
|
||||
logger.error(f"Worker {user_wrapper.username} error: {e}", exc_info=True)
|
||||
logger.error("Worker %s error: %s", user_wrapper.username, e, exc_info=True)
|
||||
|
||||
|
||||
async def show_progress(
|
||||
@@ -432,7 +437,9 @@ async def run_oauth_benchmark(
|
||||
return (username, password, token)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create/authenticate user {username}: {e}")
|
||||
logger.error(
|
||||
"Failed to create/authenticate user %s: %s", username, e
|
||||
)
|
||||
return None
|
||||
|
||||
async with async_playwright() as p:
|
||||
@@ -452,7 +459,7 @@ async def run_oauth_benchmark(
|
||||
)
|
||||
results.append(result)
|
||||
except Exception as e:
|
||||
logger.error(f"User creation task failed: {e}")
|
||||
logger.error("User creation task failed: %s", e)
|
||||
results.append(e)
|
||||
|
||||
async with anyio.create_task_group() as tg:
|
||||
@@ -462,7 +469,7 @@ async def run_oauth_benchmark(
|
||||
# Process results
|
||||
for result in results:
|
||||
if isinstance(result, Exception):
|
||||
logger.error(f"User creation task failed: {result}")
|
||||
logger.error("User creation task failed: %s", result)
|
||||
continue
|
||||
if result is None:
|
||||
continue
|
||||
@@ -496,7 +503,7 @@ async def run_oauth_benchmark(
|
||||
print(f" ✓ Session created for '{username}'")
|
||||
return wrapper
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create session for {username}: {e}")
|
||||
logger.error("Failed to create session for %s: %s", username, e)
|
||||
return None
|
||||
|
||||
# Create all sessions concurrently using anyio task groups
|
||||
@@ -508,7 +515,7 @@ async def run_oauth_benchmark(
|
||||
result = await create_session_task(username)
|
||||
session_results.append(result)
|
||||
except Exception as e:
|
||||
logger.error(f"Session creation task failed: {e}")
|
||||
logger.error("Session creation task failed: %s", e)
|
||||
session_results.append(e)
|
||||
|
||||
async with anyio.create_task_group() as tg:
|
||||
@@ -518,7 +525,7 @@ async def run_oauth_benchmark(
|
||||
# Process results
|
||||
for result in session_results:
|
||||
if isinstance(result, Exception):
|
||||
logger.error(f"Session creation task failed: {result}")
|
||||
logger.error("Session creation task failed: %s", result)
|
||||
continue
|
||||
if result is not None:
|
||||
user_wrappers.append(result)
|
||||
@@ -573,7 +580,7 @@ async def run_oauth_benchmark(
|
||||
print("✓ All sessions closed\n")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Benchmark error: {e}", exc_info=True)
|
||||
logger.error("Benchmark error: %s", e, exc_info=True)
|
||||
# Don't re-raise here - we want cleanup to run
|
||||
|
||||
finally:
|
||||
@@ -583,7 +590,7 @@ async def run_oauth_benchmark(
|
||||
callback_server.stop()
|
||||
logger.info("OAuth callback server stopped")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error stopping callback server: {e}")
|
||||
logger.warning("Error stopping callback server: %s", e)
|
||||
|
||||
# Cleanup test users
|
||||
if cleanup and created_users:
|
||||
@@ -596,10 +603,10 @@ async def run_oauth_benchmark(
|
||||
await cleanup_client.users.delete_user(userid=username)
|
||||
print(f" ✓ Deleted user '{username}'")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete user {username}: {e}")
|
||||
logger.warning("Failed to delete user %s: %s", username, e)
|
||||
print("✓ Cleanup complete\n")
|
||||
except Exception as e:
|
||||
logger.error(f"Error during user cleanup: {e}")
|
||||
logger.error("Error during user cleanup: %s", e)
|
||||
print(
|
||||
"⚠️ Failed to cleanup users. Please run cleanup script manually.\n"
|
||||
)
|
||||
|
||||
+21
-21
@@ -92,7 +92,7 @@ class OAuthUserPool:
|
||||
Returns:
|
||||
OAuth access token
|
||||
"""
|
||||
logger.info(f"Exchanging auth code for access token (user: {username})...")
|
||||
logger.info("Exchanging auth code for access token (user: %s)...", username)
|
||||
|
||||
if not self._http_client:
|
||||
raise RuntimeError(
|
||||
@@ -118,7 +118,7 @@ class OAuthUserPool:
|
||||
if not access_token:
|
||||
raise ValueError(f"No access token in response for {username}")
|
||||
|
||||
logger.info(f"Successfully acquired OAuth token for {username}")
|
||||
logger.info("Successfully acquired OAuth token for %s", username)
|
||||
return access_token
|
||||
|
||||
async def add_user(self, username: str, password: str, token: str) -> UserProfile:
|
||||
@@ -134,11 +134,11 @@ class OAuthUserPool:
|
||||
UserProfile for the added user
|
||||
"""
|
||||
if username in self.users:
|
||||
logger.warning(f"User {username} already in pool, updating token")
|
||||
logger.warning("User %s already in pool, updating token", username)
|
||||
|
||||
profile = UserProfile(username=username, password=password, token=token)
|
||||
self.users[username] = profile
|
||||
logger.info(f"Added user {username} to pool (total: {len(self.users)})")
|
||||
logger.info("Added user %s to pool (total: %s)", username, len(self.users))
|
||||
return profile
|
||||
|
||||
async def create_user_session(
|
||||
@@ -177,7 +177,7 @@ class OAuthUserPool:
|
||||
# Store both session and context for proper cleanup
|
||||
profile.session = session
|
||||
profile.streamable_context = streamable_context
|
||||
logger.info(f"Created MCP session for {username}")
|
||||
logger.info("Created MCP session for %s", username)
|
||||
return session
|
||||
|
||||
except Exception as e:
|
||||
@@ -185,7 +185,7 @@ class OAuthUserPool:
|
||||
try:
|
||||
await streamable_context.__aexit__(None, None, None)
|
||||
except Exception as cleanup_error:
|
||||
logger.debug(f"Error during cleanup: {cleanup_error}")
|
||||
logger.debug("Error during cleanup: %s", cleanup_error)
|
||||
raise e
|
||||
|
||||
async def close_user_session(self, username: str):
|
||||
@@ -200,7 +200,7 @@ class OAuthUserPool:
|
||||
try:
|
||||
await profile.session.__aexit__(None, None, None)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error closing session for {username}: {e}")
|
||||
logger.debug("Error closing session for %s: %s", username, e)
|
||||
profile.session = None
|
||||
|
||||
# Close streamable context
|
||||
@@ -208,7 +208,7 @@ class OAuthUserPool:
|
||||
try:
|
||||
await profile.streamable_context.__aexit__(None, None, None)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error closing streamable context for {username}: {e}")
|
||||
logger.debug("Error closing streamable context for %s: %s", username, e)
|
||||
profile.streamable_context = None
|
||||
|
||||
async def close_all_sessions(self):
|
||||
@@ -270,7 +270,7 @@ class OAuthUserPool:
|
||||
Raises:
|
||||
HTTPStatusError: If user creation fails
|
||||
"""
|
||||
logger.info(f"Creating Nextcloud user: {username}")
|
||||
logger.info("Creating Nextcloud user: %s", username)
|
||||
|
||||
await self.admin_client.users.create_user(
|
||||
userid=username,
|
||||
@@ -279,7 +279,7 @@ class OAuthUserPool:
|
||||
email=email or f"{username}@benchmark.local",
|
||||
)
|
||||
|
||||
logger.info(f"Successfully created Nextcloud user: {username}")
|
||||
logger.info("Successfully created Nextcloud user: %s", username)
|
||||
|
||||
return UserConfig(
|
||||
username=username,
|
||||
@@ -296,13 +296,13 @@ class OAuthUserPool:
|
||||
Args:
|
||||
username: Username to delete
|
||||
"""
|
||||
logger.info(f"Deleting Nextcloud user: {username}")
|
||||
logger.info("Deleting Nextcloud user: %s", username)
|
||||
|
||||
try:
|
||||
await self.admin_client.users.delete_user(userid=username)
|
||||
logger.info(f"Successfully deleted Nextcloud user: {username}")
|
||||
logger.info("Successfully deleted Nextcloud user: %s", username)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete user {username}: {e}")
|
||||
logger.warning("Failed to delete user %s: %s", username, e)
|
||||
|
||||
async def acquire_token_playwright(
|
||||
self,
|
||||
@@ -338,8 +338,8 @@ class OAuthUserPool:
|
||||
ValueError: If token exchange fails
|
||||
"""
|
||||
|
||||
logger.info(f"Starting Playwright OAuth flow for {username}...")
|
||||
logger.debug(f"Using state: {state[:16]}...")
|
||||
logger.info("Starting Playwright OAuth flow for %s...", username)
|
||||
logger.debug("Using state: %s...", state[:16])
|
||||
|
||||
# Construct authorization URL
|
||||
auth_url = (
|
||||
@@ -363,7 +363,7 @@ class OAuthUserPool:
|
||||
|
||||
# Login if needed
|
||||
if "/login" in current_url or "/index.php/login" in current_url:
|
||||
logger.info(f"Logging in as {username}...")
|
||||
logger.info("Logging in as %s...", username)
|
||||
await page.wait_for_selector('input[name="user"]', timeout=10000)
|
||||
await page.fill('input[name="user"]', username)
|
||||
await page.fill('input[name="password"]', password)
|
||||
@@ -382,7 +382,7 @@ class OAuthUserPool:
|
||||
await authorize_button.click()
|
||||
await page.wait_for_load_state("networkidle", timeout=10000)
|
||||
except Exception as e:
|
||||
logger.debug(f"No authorization needed: {e}")
|
||||
logger.debug("No authorization needed: %s", e)
|
||||
|
||||
# Wait for callback server to receive auth code
|
||||
logger.info("Waiting for OAuth callback...")
|
||||
@@ -392,20 +392,20 @@ class OAuthUserPool:
|
||||
if time.time() - start_time > timeout_seconds:
|
||||
screenshot_path = f"/tmp/oauth_timeout_{username}.png"
|
||||
await page.screenshot(path=screenshot_path)
|
||||
logger.error(f"Screenshot saved to {screenshot_path}")
|
||||
logger.error("Screenshot saved to %s", screenshot_path)
|
||||
raise TimeoutError(
|
||||
f"Timeout waiting for OAuth callback for {username}"
|
||||
)
|
||||
await anyio.sleep(0.5)
|
||||
|
||||
auth_code = auth_states[state]
|
||||
logger.info(f"Received auth code for {username}")
|
||||
logger.info("Received auth code for %s", username)
|
||||
|
||||
finally:
|
||||
await context.close()
|
||||
|
||||
# Exchange code for token
|
||||
logger.info(f"Exchanging auth code for access token ({username})...")
|
||||
logger.info("Exchanging auth code for access token (%s)...", username)
|
||||
token_response = await self._http_client.post(
|
||||
self.token_endpoint,
|
||||
data={
|
||||
@@ -424,7 +424,7 @@ class OAuthUserPool:
|
||||
if not access_token:
|
||||
raise ValueError(f"No access token for {username}: {token_data}")
|
||||
|
||||
logger.info(f"Successfully acquired OAuth token for {username}")
|
||||
logger.info("Successfully acquired OAuth token for %s", username)
|
||||
return access_token
|
||||
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class Workflow(ABC):
|
||||
return step_result
|
||||
except Exception as e:
|
||||
duration = time.time() - start
|
||||
logger.error(f"Step {step_name} failed for user {user.username}: {e}")
|
||||
logger.error("Step %s failed for user %s: %s", step_name, user.username, e)
|
||||
step_result = WorkflowStepResult(
|
||||
step_name=step_name,
|
||||
user=user.username,
|
||||
@@ -236,7 +236,7 @@ class NoteShareWorkflow(Workflow):
|
||||
return self._finish(success=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Note share workflow failed: {e}")
|
||||
logger.error("Note share workflow failed: %s", e)
|
||||
return self._finish(False, error=str(e))
|
||||
|
||||
|
||||
@@ -338,7 +338,7 @@ class CollaborativeEditWorkflow(Workflow):
|
||||
return self._finish(success=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Collaborative edit workflow failed: {e}")
|
||||
logger.error("Collaborative edit workflow failed: %s", e)
|
||||
return self._finish(False, error=str(e))
|
||||
|
||||
|
||||
@@ -424,7 +424,7 @@ class FileShareAndDownloadWorkflow(Workflow):
|
||||
return self._finish(success=True)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"File share workflow failed: {e}")
|
||||
logger.error("File share workflow failed: %s", e)
|
||||
return self._finish(False, error=str(e))
|
||||
|
||||
|
||||
|
||||
@@ -182,12 +182,12 @@ class WorkloadOperations:
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up any resources created during testing."""
|
||||
logger.info(f"Cleaning up {len(self._created_notes)} test notes...")
|
||||
logger.info("Cleaning up %s test notes...", len(self._created_notes))
|
||||
for note_id in self._created_notes[:]:
|
||||
try:
|
||||
await self.delete_note(note_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete note {note_id}: {e}")
|
||||
logger.warning("Failed to delete note %s: %s", note_id, e)
|
||||
|
||||
|
||||
class MixedWorkload:
|
||||
@@ -210,7 +210,7 @@ class MixedWorkload:
|
||||
|
||||
async def warmup(self, count: int = 10):
|
||||
"""Create initial notes for read/update operations."""
|
||||
logger.info(f"Warming up with {count} test notes...")
|
||||
logger.info("Warming up with %s test notes...", count)
|
||||
for _ in range(count):
|
||||
result = await self.ops.create_note()
|
||||
if result.success and self.ops._created_notes:
|
||||
@@ -225,7 +225,7 @@ class MixedWorkload:
|
||||
etag = note_data.get("etag", "")
|
||||
self._warmup_note_ids.append((note_id, etag))
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to get etag for note {note_id}: {e}")
|
||||
logger.warning("Failed to get etag for note %s: %s", note_id, e)
|
||||
|
||||
async def run_operation(self) -> OperationResult:
|
||||
"""Execute one random operation based on the workload distribution."""
|
||||
|
||||
Reference in New Issue
Block a user