NiWaRe's picture
refactor for concurrency: add context manager and single worker async architecture
1ec3391
"""
Unified API client management for W&B operations.
This module provides a consistent pattern for managing W&B API instances
with per-request API keys, following the same pattern as WeaveApiClient.
"""
import os
from typing import Optional, Dict, Any
from contextvars import ContextVar
import wandb
from wandb_mcp_server.utils import get_rich_logger
logger = get_rich_logger(__name__)
# Context variable for storing the current request's API key
api_key_context: ContextVar[Optional[str]] = ContextVar('wandb_api_key', default=None)
class WandBApiManager:
"""
Manages W&B API instances with per-request API keys.
This class follows the same pattern as WeaveApiClient, providing
a consistent interface for all W&B operations that need API access.
"""
@staticmethod
def get_api_key() -> Optional[str]:
"""
Get the API key for the current request context.
Returns:
The API key from context, environment, or None.
"""
# First try context variable (set by middleware for HTTP requests)
api_key = api_key_context.get()
# Fallback to environment variable (for STDIO or testing)
if not api_key:
api_key = os.environ.get("WANDB_API_KEY")
return api_key
@staticmethod
def get_api(api_key: Optional[str] = None) -> wandb.Api:
"""
Get a W&B API instance with the specified or current API key.
Args:
api_key: Optional API key to use. If not provided, uses context or environment.
Returns:
A configured wandb.Api instance.
Raises:
ValueError: If no API key is available.
"""
if api_key is None:
api_key = WandBApiManager.get_api_key()
if not api_key:
raise ValueError(
"No W&B API key available. Provide api_key parameter or "
"ensure WANDB_API_KEY is set in environment or request context."
)
# Create API instance with the specific key
# According to docs: https://docs.wandb.ai/ref/python/public-api/
return wandb.Api(api_key=api_key)
@staticmethod
def set_context_api_key(api_key: str) -> Any:
"""
Set the API key in the current context.
Args:
api_key: The API key to set.
Returns:
A token that can be used to reset the context.
"""
return api_key_context.set(api_key)
@staticmethod
def reset_context_api_key(token: Any) -> None:
"""
Reset the API key context.
Args:
token: The token returned from set_context_api_key.
"""
api_key_context.reset(token)
def get_wandb_api(api_key: Optional[str] = None) -> wandb.Api:
"""
Convenience function to get a W&B API instance.
This is the primary function that should be used throughout the codebase
to get a W&B API instance with proper API key handling.
Args:
api_key: Optional API key. If not provided, uses context or environment.
Returns:
A configured wandb.Api instance.
"""
return WandBApiManager.get_api(api_key)