"""
Context managers for the LogEverything library.
This module provides context managers that allow for temporarily modifying
logging configuration using Python's `with` statement.
"""
import copy
import logging
from typing import Any, Dict, List, Optional, TypeVar, Union, cast
from ..core import _config, configure, setup_logging
# TypeVar for better type hinting
ConfigValue = TypeVar("ConfigValue", int, str, bool, List[Union[str, logging.Handler]], None)
def _safe_setup_logging(**kwargs: Any) -> None:
"""
Safely call setup_logging with proper type conversion.
Args:
**kwargs: Configuration options that need type conversion
"""
# Filter out None values
filtered_kwargs = {k: v for k, v in kwargs.items() if v is not None}
# Call setup_logging with the filtered arguments
if filtered_kwargs:
setup_logging(_internal=True, **filtered_kwargs)
[docs]
class LoggingContext:
"""
A context manager for temporarily changing logging configuration.
This allows you to change logging settings within a specific block of code
and automatically restore the previous settings when exiting the block.
Example:
>>> with LoggingContext(level="DEBUG", log_entry_exit=True):
... # Code here will use DEBUG level and log function entry/exit
... my_function()
... # Original settings are restored after the with block
"""
[docs]
def __init__(
self,
level: Optional[Union[int, str]] = None,
log_entry_exit: Optional[bool] = None,
log_arguments: Optional[bool] = None,
log_return_values: Optional[bool] = None,
beautify: Optional[bool] = None,
indent_level: Optional[int] = None,
handlers: Optional[List[str]] = None,
logger_name: Optional[str] = None,
capture_print: Optional[bool] = None,
print_logger_name: Optional[str] = None,
print_level: Optional[int] = None,
print_prefix: Optional[str] = None,
**kwargs: Any,
) -> None:
"""
Initialize a logging context with temporary configuration.
Args:
level: Temporarily change the logging level
log_entry_exit: Whether to log function entry and exit
log_arguments: Whether to log function arguments
log_return_values: Whether to log function return values
beautify: Whether to beautify logs with visual elements
indent_level: Indentation level for beautified logs
handlers: List of handlers to use temporarily
logger_name: Name of the logger to configure
capture_print: Whether to capture print statements
print_logger_name: Logger name for captured print statements
print_level: Logging level for captured print statements
print_prefix: Prefix for captured print statements
**kwargs: Additional configuration options
"""
self.new_config = {
"level": level,
"log_entry_exit": log_entry_exit,
"log_arguments": log_arguments,
"log_return_values": log_return_values,
"beautify": beautify,
"indent_level": indent_level,
"handlers": handlers,
"logger_name": logger_name,
"capture_print": capture_print,
"print_logger_name": print_logger_name,
"print_level": print_level,
"print_prefix": print_prefix,
}
# Add any additional kwargs
for key, value in kwargs.items():
self.new_config[key] = value
# Remove None values to only apply specified changes
self.new_config = {k: v for k, v in self.new_config.items() if v is not None}
# Will store the original configuration
self.original_config: Dict[str, Any] = {}
[docs]
def __enter__(self) -> Dict[str, Any]:
"""
Enter the context, save original settings and apply new ones.
Returns:
Dict[str, Any]: The current configuration
"""
# Save original values for all keys we're going to change
for key in self.new_config:
if key in _config:
self.original_config[key] = copy.deepcopy(_config[key])
# Apply new configuration - we need to pass parameters individually
# rather than with **self.new_config to satisfy mypy
if "level" in self.new_config:
configure(_internal=True, level=cast(Union[int, str, None], self.new_config["level"]))
if "log_entry_exit" in self.new_config:
configure(_internal=True, log_entry_exit=cast(bool, self.new_config["log_entry_exit"]))
if "log_arguments" in self.new_config:
configure(_internal=True, log_arguments=cast(bool, self.new_config["log_arguments"]))
if "log_return_values" in self.new_config:
configure(
_internal=True, log_return_values=cast(bool, self.new_config["log_return_values"])
)
if "beautify" in self.new_config:
configure(_internal=True, beautify=cast(bool, self.new_config["beautify"]))
if "indent_level" in self.new_config:
configure(_internal=True, indent_level=cast(int, self.new_config["indent_level"]))
if "handlers" in self.new_config:
configure(
_internal=True,
handlers=cast(List[Union[str, logging.Handler]], self.new_config["handlers"]),
)
if "logger_name" in self.new_config:
configure(_internal=True, logger_name=cast(str, self.new_config["logger_name"]))
if "capture_print" in self.new_config:
configure(_internal=True, capture_print=cast(bool, self.new_config["capture_print"]))
if "print_logger_name" in self.new_config:
configure(
_internal=True, print_logger_name=cast(str, self.new_config["print_logger_name"])
)
if "print_level" in self.new_config:
configure(_internal=True, print_level=cast(int, self.new_config["print_level"]))
if "print_prefix" in self.new_config:
configure(_internal=True, print_prefix=cast(str, self.new_config["print_prefix"]))
return _config
[docs]
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
"""
Exit the context and restore original settings.
Args:
exc_type: Exception type if an exception was raised
exc_val: Exception value if an exception was raised
exc_tb: Exception traceback if an exception was raised
"""
# Restore original configuration - handle each parameter individually
if "level" in self.original_config:
configure(_internal=True, level=self.original_config["level"])
if "log_entry_exit" in self.original_config:
configure(
_internal=True, log_entry_exit=cast(bool, self.original_config["log_entry_exit"])
)
if "log_arguments" in self.original_config:
configure(
_internal=True, log_arguments=cast(bool, self.original_config["log_arguments"])
)
if "log_return_values" in self.original_config:
configure(
_internal=True,
log_return_values=cast(bool, self.original_config["log_return_values"]),
)
if "beautify" in self.original_config:
configure(_internal=True, beautify=cast(bool, self.original_config["beautify"]))
if "indent_level" in self.original_config:
configure(_internal=True, indent_level=cast(int, self.original_config["indent_level"]))
if "handlers" in self.original_config:
configure(
_internal=True,
handlers=cast(List[Union[str, logging.Handler]], self.original_config["handlers"]),
)
if "logger_name" in self.original_config:
configure(_internal=True, logger_name=cast(str, self.original_config["logger_name"]))
if "capture_print" in self.original_config:
configure(
_internal=True, capture_print=cast(bool, self.original_config["capture_print"])
)
if "print_logger_name" in self.original_config:
configure(
_internal=True,
print_logger_name=cast(str, self.original_config["print_logger_name"]),
)
if "print_level" in self.original_config:
configure(_internal=True, print_level=cast(int, self.original_config["print_level"]))
if "print_prefix" in self.original_config:
configure(_internal=True, print_prefix=cast(str, self.original_config["print_prefix"]))
[docs]
class VisualLoggingContext(LoggingContext):
"""
A context manager for temporarily enabling or configuring visual logging.
This context manager is specialized for visual enhancements to logging output,
allowing you to enable colors, symbols, indentation, and other visual elements
for a specific block of code.
Visual settings are managed separately from standard logging settings because they
may require reconfiguration of handlers and formatters. For this reason, they are
handled through setup_logging() rather than configure().
Example:
>>> with VisualLoggingContext(use_colors=True, use_symbols=True):
... # Code here will use visual enhancements
... log_complex_data()
... # Original visual settings are restored after the with block
"""
[docs]
def __init__(
self,
visual_mode: Optional[bool] = True,
use_symbols: Optional[bool] = None,
use_indent: Optional[bool] = None,
align_columns: Optional[bool] = None,
auto_detect_platform: Optional[bool] = None,
force_ascii: Optional[bool] = None,
disable_colors: Optional[bool] = None,
color_theme: Optional[str] = None,
**kwargs: Any,
) -> None:
"""
Initialize a visual logging context with temporary configuration.
Args:
visual_mode: Master switch for visual enhancements
use_symbols: Whether to use Unicode symbols for log levels
use_indent: Whether to use visual indentation with box drawing characters
align_columns: Whether to align columns in log output
auto_detect_platform: Whether to detect platform capabilities automatically
force_ascii: Whether to force ASCII-only output (no Unicode)
disable_colors: Whether to disable ANSI color codes
color_theme: Color theme to use for console output
**kwargs: Additional configuration options
"""
# Initialize base class with standard settings
super().__init__(**kwargs)
# Store visual settings separately
self.visual_settings = {
"visual_mode": visual_mode,
"use_symbols": use_symbols,
"use_indent": use_indent,
"align_columns": align_columns,
"auto_detect_platform": auto_detect_platform,
"force_ascii": force_ascii,
"disable_colors": disable_colors,
"color_theme": color_theme,
}
self.visual_settings = {k: v for k, v in self.visual_settings.items() if v is not None}
[docs]
def __enter__(self) -> Dict[str, Any]:
"""
Enter the context, save original settings and apply new ones.
Returns:
Dict[str, Any]: The current configuration
"""
# First save all current visual settings
visual_keys = [
"visual_mode",
"use_symbols",
"use_indent",
"align_columns",
"auto_detect_platform",
"force_ascii",
"disable_colors",
"color_theme",
]
self.original_visual = {k: _config.get(k) for k in visual_keys}
# Apply the new visual settings
if self.visual_settings:
_safe_setup_logging(**self.visual_settings)
# Then apply standard settings using the parent's __enter__
super().__enter__()
return _config
[docs]
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
"""
Exit the context and restore original settings.
Args:
exc_type: Exception type if an exception was raised
exc_val: Exception value if an exception was raised
exc_tb: Exception traceback if an exception was raised
"""
# First restore visual settings
if hasattr(self, "original_visual"):
# Directly update config to handle None values properly
for key, value in self.original_visual.items():
_config[key] = value
# Then restore standard settings through parent
super().__exit__(exc_type, exc_val, exc_tb)
[docs]
class QuietLoggingContext(LoggingContext):
"""
A context manager for temporarily suppressing or minimizing logging output.
This context manager is specialized for reducing log verbosity for a specific
block of code, useful when you want to silence noisy operations temporarily.
Example:
>>> with QuietLoggingContext():
... # This code will produce minimal logs
... noisy_operation()
... # Original logging verbosity is restored after the with block
"""
[docs]
def __init__(
self,
level: Union[int, str] = logging.WARNING,
log_entry_exit: bool = False,
log_arguments: bool = False,
log_return_values: bool = False,
capture_print: bool = False,
**kwargs: Any,
) -> None:
"""
Initialize a quiet logging context with minimized verbosity.
Args:
level: The logging level to use (defaults to WARNING)
log_entry_exit: Whether to log function entry and exit (defaults to False)
log_arguments: Whether to log function arguments (defaults to False)
log_return_values: Whether to log function return values (defaults to False)
capture_print: Whether to capture print statements (defaults to False)
**kwargs: Additional configuration options
"""
super().__init__(
level=level,
log_entry_exit=log_entry_exit,
log_arguments=log_arguments,
log_return_values=log_return_values,
capture_print=capture_print,
**kwargs,
)
[docs]
class VerboseLoggingContext(LoggingContext):
"""
A context manager for temporarily increasing logging verbosity.
This context manager is specialized for maximizing log information for a specific
block of code, useful for debugging or detailed analysis of specific operations.
Example:
>>> with VerboseLoggingContext():
... # This code will produce detailed logs
... complex_operation()
... # Original logging verbosity is restored after the with block
"""
[docs]
def __init__(
self,
level: Union[int, str] = logging.DEBUG,
log_entry_exit: bool = True,
log_arguments: bool = True,
log_return_values: bool = True,
capture_print: bool = True,
**kwargs: Any,
) -> None:
"""
Initialize a verbose logging context with maximum verbosity.
Args:
level: The logging level to use (defaults to DEBUG)
log_entry_exit: Whether to log function entry and exit (defaults to True)
log_arguments: Whether to log function arguments (defaults to True)
log_return_values: Whether to log function return values (defaults to True)
capture_print: Whether to capture print statements (defaults to True)
**kwargs: Additional configuration options
"""
super().__init__(
level=level,
log_entry_exit=log_entry_exit,
log_arguments=log_arguments,
log_return_values=log_return_values,
capture_print=capture_print,
**kwargs,
)
[docs]
class TemporaryHandlerContext:
"""
A context manager for temporarily using specific log handlers.
This context manager allows you to temporarily switch to a different set of
handlers for a specific block of code, and then automatically restore the
original handlers when exiting the block.
Example:
>>> with TemporaryHandlerContext(["file", "json"]):
... # This code will log to file and JSON, ignoring console
... important_operation()
... # Original handlers are restored after the with block
"""
[docs]
def __init__(self, handlers: List[Union[str, logging.Handler]]) -> None:
"""
Initialize a temporary handler context.
Args:
handlers: A list of handler names or actual handler objects to use temporarily
"""
self.handlers = handlers
self.original_handlers: Optional[List[Union[str, logging.Handler]]] = None
[docs]
def __enter__(self) -> Dict[str, Any]:
"""
Enter the context, save original handlers and apply new ones.
Returns:
Dict[str, Any]: The current configuration
"""
# Save original handlers
if "handlers" in _config:
self.original_handlers = cast(
List[Union[str, logging.Handler]], copy.deepcopy(_config["handlers"])
)
# Apply new handlers using setup_logging which handles proper handler initialization
setup_logging(handlers=self.handlers, _internal=True)
return _config
[docs]
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
"""
Exit the context and restore original handlers.
Args:
exc_type: Exception type if an exception was raised
exc_val: Exception value if an exception was raised
exc_tb: Exception traceback if an exception was raised
"""
# Restore original handlers
if self.original_handlers is not None:
setup_logging(handlers=self.original_handlers, _internal=True)