Formatting and Logging#

Overview#

ROMPY provides a comprehensive framework for consistent formatting and logging across the codebase. This framework ensures that:

  1. Log messages are consistent and configurable

  2. String representations of objects are clear and hierarchical

  3. Output formatting is visually appealing and consistent

  4. Configuration is flexible and environment-aware

Core Components#

The framework consists of several key components:

  1. Centralized Logging System - Consistent log formatting and handling - Environment variable configuration - Multiple log levels and output formats

  2. Hierarchical String Representation - Clean, readable output of complex objects - Recursive handling of nested structures - Type-specific formatting

  3. Formatted Output - Boxes and visual elements - Consistent headers and footers - Progress indicators

Logging System#

ROMPY’s logging system is built on Python’s standard logging module but provides additional features and consistency.

Basic Usage#

from rompy.core.logging import get_logger

# Get a logger for your module
logger = get_logger(__name__)

# Log messages at different levels
logger.debug("Detailed debug information")
logger.info("Informational message")
logger.warning("Warning message")
logger.error("Error message")
logger.critical("Critical error")

Configuration#

Logging can be configured via environment variables:

Variable

Default

Description

ROMPY_LOG_LEVEL

INFO

Minimum log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)

ROMPY_LOG_FORMAT

detailed

Log format style (simple or detailed)

ROMPY_LOG_FILE

None

Optional file path for log output

Programmatic configuration is also available:

from rompy.core.logging import configure_logging

configure_logging(
    level="DEBUG",
    format="detailed",
    log_file="rompy.log"
)

Hierarchical String Representation#

All ROMPY models include a hierarchical string representation for better readability of complex objects.

Basic Usage#

class MyModel(RompyBaseModel):
    name: str
    value: float
    nested: dict

obj = MyModel(name="test", value=42.0, nested={"a": 1, "b": 2})
print(obj)

Output:

MyModel:
  name: test
  value: 42.0
  nested:
    a: 1
    b: 2

Custom Formatting#

Customize formatting by overriding the _format_value method:

class CustomModel(RompyBaseModel):
    timestamp: datetime

    def _format_value(self, obj: Any) -> Optional[str]:
        if isinstance(obj, datetime):
            return obj.strftime("%Y-%m-%d %H:%M")
        return None

Formatted Output#

ROMPY provides utilities for creating consistent, visually appealing output.

Boxes and Sections#

from rompy.core.formatting import box, section

# Create a simple box
print(box("Important Message"))

# Create a section with content
print(section("Processing Results", ["Item 1", "Item 2", "Item 3"]))

Progress Indicators#

from rompy.core.formatting import ProgressBar
import time

with ProgressBar("Processing", total=100) as pbar:
    for i in range(100):
        time.sleep(0.1)
        pbar.update(1)

Best Practices#

  1. Logging - Use appropriate log levels (DEBUG for detailed info, INFO for normal operations, etc.) - Include relevant context in log messages - Use structured logging for machine-readable output

  2. String Representation - Keep string representations concise but informative - Include all relevant attributes - Handle nested objects appropriately

  3. Formatting - Be consistent with formatting across the codebase - Use the provided utilities for common formatting needs - Consider readability in different output contexts (CLI, logs, etc.)

Example Integration#

Here’s how these components work together in a typical ROMPY module:

from rompy.core.logging import get_logger
from rompy.core.formatting import section
from rompy.core.types import RompyBaseModel

logger = get_logger(__name__)

class DataProcessor(RompyBaseModel):
    """Process data with logging and formatted output."""

    def process(self, data):
        logger.info("Starting data processing")

        with section("Processing Data"):
            # Process data here
            logger.debug(f"Processing {len(data)} items")

            # Log progress
            for i, item in enumerate(data, 1):
                self._process_item(item)
                logger.debug(f"Processed item {i}/{len(data)}")

        logger.info("Processing complete")

    def _process_item(self, item):
        # Process individual items
        pass