new site
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
"""Utility modules for Odoo Directory."""
|
||||
|
||||
from .logger import get_logger, setup_logging
|
||||
from .retry import retry_with_backoff
|
||||
|
||||
__all__ = ["get_logger", "setup_logging", "retry_with_backoff"]
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Logging configuration for Odoo Directory CLI.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
|
||||
# Global console for rich output
|
||||
console = Console()
|
||||
|
||||
|
||||
def setup_logging(level: str = "INFO") -> None:
|
||||
"""
|
||||
Configure logging with rich handler for pretty console output.
|
||||
|
||||
Args:
|
||||
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||
"""
|
||||
logging.basicConfig(
|
||||
level=level.upper(),
|
||||
format="%(message)s",
|
||||
datefmt="[%X]",
|
||||
handlers=[
|
||||
RichHandler(
|
||||
console=console,
|
||||
show_time=True,
|
||||
show_path=False,
|
||||
markup=True,
|
||||
rich_tracebacks=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def get_logger(name: str, level: Optional[str] = None) -> logging.Logger:
|
||||
"""
|
||||
Get a logger instance with optional level override.
|
||||
|
||||
Args:
|
||||
name: Logger name (usually __name__)
|
||||
level: Optional level override
|
||||
|
||||
Returns:
|
||||
Configured logger instance
|
||||
"""
|
||||
logger = logging.getLogger(name)
|
||||
if level:
|
||||
logger.setLevel(level.upper())
|
||||
return logger
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Retry utilities with exponential backoff.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from functools import wraps
|
||||
from typing import Callable, TypeVar, Any, Tuple, Type
|
||||
import logging
|
||||
|
||||
from tenacity import (
|
||||
retry,
|
||||
stop_after_attempt,
|
||||
wait_exponential,
|
||||
retry_if_exception_type,
|
||||
before_sleep_log,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def retry_with_backoff(
|
||||
max_attempts: int = 3,
|
||||
min_wait: float = 1.0,
|
||||
max_wait: float = 60.0,
|
||||
exceptions: Tuple[Type[Exception], ...] = (Exception,),
|
||||
) -> Callable:
|
||||
"""
|
||||
Decorator for retrying functions with exponential backoff.
|
||||
|
||||
Args:
|
||||
max_attempts: Maximum number of retry attempts
|
||||
min_wait: Minimum wait time between retries (seconds)
|
||||
max_wait: Maximum wait time between retries (seconds)
|
||||
exceptions: Tuple of exception types to retry on
|
||||
|
||||
Returns:
|
||||
Decorated function with retry logic
|
||||
"""
|
||||
return retry(
|
||||
stop=stop_after_attempt(max_attempts),
|
||||
wait=wait_exponential(multiplier=1, min=min_wait, max=max_wait),
|
||||
retry=retry_if_exception_type(exceptions),
|
||||
before_sleep=before_sleep_log(logger, logging.WARNING),
|
||||
reraise=True,
|
||||
)
|
||||
|
||||
|
||||
async def async_retry_with_backoff(
|
||||
func: Callable[..., T],
|
||||
*args,
|
||||
max_attempts: int = 3,
|
||||
min_wait: float = 1.0,
|
||||
max_wait: float = 60.0,
|
||||
exceptions: Tuple[Type[Exception], ...] = (Exception,),
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""
|
||||
Async function with retry logic and exponential backoff.
|
||||
|
||||
Args:
|
||||
func: Async function to call
|
||||
*args: Positional arguments for func
|
||||
max_attempts: Maximum number of retry attempts
|
||||
min_wait: Minimum wait time between retries (seconds)
|
||||
max_wait: Maximum wait time between retries (seconds)
|
||||
exceptions: Tuple of exception types to retry on
|
||||
**kwargs: Keyword arguments for func
|
||||
|
||||
Returns:
|
||||
Result of the function call
|
||||
"""
|
||||
last_exception = None
|
||||
wait_time = min_wait
|
||||
|
||||
for attempt in range(1, max_attempts + 1):
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except exceptions as e:
|
||||
last_exception = e
|
||||
if attempt < max_attempts:
|
||||
logger.warning(
|
||||
f"Attempt {attempt}/{max_attempts} failed: {e}. "
|
||||
f"Retrying in {wait_time:.1f}s..."
|
||||
)
|
||||
await asyncio.sleep(wait_time)
|
||||
wait_time = min(wait_time * 2, max_wait)
|
||||
else:
|
||||
logger.error(f"All {max_attempts} attempts failed: {e}")
|
||||
|
||||
raise last_exception
|
||||
Reference in New Issue
Block a user