Formatters Reference

This document provides a complete reference for all formatters available in ViewText.

Overview

Formatters are functions that transform values into formatted strings for display. They are used in layout definitions to control how field values appear in the output. Unlike computed field operations (which transform data), formatters only affect the presentation.

All formatters are registered in the FormatterRegistry and can be accessed by name in TOML layout configurations.

Common Usage

Formatters are specified in layout line definitions:

[[layouts.my_layout.lines]]
field = "price"
index = 0
formatter = "number"

[layouts.my_layout.lines.formatter_params]
decimals = 2
thousands_sep = ","
prefix = "$"

Built-in Formatters

text

Basic text formatting with optional prefix and suffix.

Parameters:

  • prefix - String to prepend (optional, default: empty string)

  • suffix - String to append (optional, default: empty string)

Examples:

[[layouts.demo.lines]]
field = "name"
index = 0
formatter = "text"

[layouts.demo.lines.formatter_params]
prefix = "Name: "
suffix = "."
Input: "Alice"
Output: "Name: Alice."
[[layouts.demo.lines]]
field = "status"
index = 1
formatter = "text"

[layouts.demo.lines.formatter_params]
prefix = "["
suffix = "]"
Input: "active"
Output: "[active]"

text_uppercase

Converts text to uppercase.

Parameters:

None

Examples:

[[layouts.demo.lines]]
field = "city"
index = 0
formatter = "text_uppercase"
Input: "san francisco"
Output: "SAN FRANCISCO"
[[layouts.demo.lines]]
field = "code"
index = 1
formatter = "text_uppercase"
Input: "abc123"
Output: "ABC123"

number

Format numbers with precision, separators, and optional prefix/suffix.

Parameters:

  • decimals - Number of decimal places (optional, default: 0)

  • thousands_sep - Thousands separator character (optional, default: empty string)

  • decimal_sep - Decimal separator character (optional, default: “.”)

  • prefix - String to prepend (optional, default: empty string)

  • suffix - String to append (optional, default: empty string)

Examples:

# Basic number formatting
[[layouts.demo.lines]]
field = "population"
index = 0
formatter = "number"

[layouts.demo.lines.formatter_params]
thousands_sep = ","
Input: 1234567
Output: "1,234,567"
# Temperature with suffix
[[layouts.demo.lines]]
field = "temperature"
index = 1
formatter = "number"

[layouts.demo.lines.formatter_params]
decimals = 1
suffix = "°C"
Input: 23.456
Output: "23.5°C"
# European number format
[[layouts.demo.lines]]
field = "amount"
index = 2
formatter = "number"

[layouts.demo.lines.formatter_params]
decimals = 2
thousands_sep = "."
decimal_sep = ","
Input: 1234567.89
Output: "1.234.567,89"
# With prefix and suffix
[[layouts.demo.lines]]
field = "value"
index = 3
formatter = "number"

[layouts.demo.lines.formatter_params]
decimals = 2
thousands_sep = ","
prefix = "$"
suffix = " USD"
Input: 1234.567
Output: "$1,234.57 USD"

price

Specialized price formatting with currency symbol positioning.

Parameters:

  • symbol - Currency symbol (optional, default: empty string)

  • symbol_position - “prefix” or “suffix” (optional, default: “prefix”)

  • decimals - Number of decimal places (optional, default: 2)

  • thousands_sep - Thousands separator character (optional, default: empty string)

  • decimal_sep - Decimal separator character (optional, default: “.”)

Examples:

# US Dollar format
[[layouts.demo.lines]]
field = "price"
index = 0
formatter = "price"

[layouts.demo.lines.formatter_params]
symbol = "$"
decimals = 2
thousands_sep = ","
Input: 1234.50
Output: "$1,234.50"
# Euro with suffix
[[layouts.demo.lines]]
field = "price"
index = 1
formatter = "price"

[layouts.demo.lines.formatter_params]
symbol = "€"
symbol_position = "suffix"
decimals = 2
Input: 1234.56
Output: "1234.56€"
# European Euro format
[[layouts.demo.lines]]
field = "price"
index = 2
formatter = "price"

[layouts.demo.lines.formatter_params]
symbol = "€"
decimals = 2
thousands_sep = "."
decimal_sep = ","
Input: 1234567.89
Output: "€1.234.567,89"
# Swiss Franc format
[[layouts.demo.lines]]
field = "amount"
index = 3
formatter = "price"

[layouts.demo.lines.formatter_params]
symbol = "CHF"
symbol_position = "suffix"
decimals = 2
thousands_sep = "'"
Input: 1234567.89
Output: "1'234'567.89CHF"

datetime

Format timestamps and datetime objects using strftime format strings.

Parameters:

  • format - strftime format string (optional, default: “%Y-%m-%d %H:%M:%S”)

Supported Input Types:

  • Python datetime objects

  • Unix timestamps (int or float)

  • String values (returned as-is)

Examples:

# Full datetime
[[layouts.demo.lines]]
field = "timestamp"
index = 0
formatter = "datetime"

[layouts.demo.lines.formatter_params]
format = "%Y-%m-%d %H:%M:%S"
Input: 1234567890 (Unix timestamp)
Output: "2009-02-13 23:31:30"
# Date only
[[layouts.demo.lines]]
field = "date"
index = 1
formatter = "datetime"

[layouts.demo.lines.formatter_params]
format = "%Y-%m-%d"
Input: datetime(2023, 12, 25, 15, 30)
Output: "2023-12-25"
# Time only
[[layouts.demo.lines]]
field = "time"
index = 2
formatter = "datetime"

[layouts.demo.lines.formatter_params]
format = "%H:%M:%S"
Input: 1703516400
Output: "15:00:00"
# Custom format
[[layouts.demo.lines]]
field = "created_at"
index = 3
formatter = "datetime"

[layouts.demo.lines.formatter_params]
format = "%b %d, %Y at %I:%M %p"
Input: datetime(2023, 12, 25, 15, 30)
Output: "Dec 25, 2023 at 03:30 PM"

Common Format Codes:

  • %Y - Year with century (e.g., 2023)

  • %m - Month as zero-padded number (01-12)

  • %d - Day of month as zero-padded number (01-31)

  • %H - Hour (24-hour clock) as zero-padded number (00-23)

  • %M - Minute as zero-padded number (00-59)

  • %S - Second as zero-padded number (00-59)

  • %b - Abbreviated month name (Jan, Feb, etc.)

  • %B - Full month name (January, February, etc.)

  • %I - Hour (12-hour clock) as zero-padded number (01-12)

  • %p - AM or PM

relative_time

Format time differences in human-readable relative format (e.g., “5m ago”, “2d ago”).

Parameters:

  • format - “short” or “long” (optional, default: “short”)

Input:

Time value in seconds (typically the number of seconds elapsed)

Examples:

# Short format
[[layouts.demo.lines]]
field = "elapsed_seconds"
index = 0
formatter = "relative_time"

[layouts.demo.lines.formatter_params]
format = "short"
Input: 45 → Output: "45s ago"
Input: 300 → Output: "5m ago"
Input: 3600 → Output: "1h ago"
Input: 86400 → Output: "1d ago"
# Long format
[[layouts.demo.lines]]
field = "elapsed_seconds"
index = 1
formatter = "relative_time"

[layouts.demo.lines.formatter_params]
format = "long"
Input: 45 → Output: "45 seconds ago"
Input: 300 → Output: "5 minutes ago"
Input: 3600 → Output: "1 hours ago"
Input: 86400 → Output: "1 days ago"

Time Ranges:

  • Less than 60 seconds: shows seconds

  • 60-3599 seconds: shows minutes

  • 3600-86399 seconds: shows hours

  • 86400+ seconds: shows days

template

Combine multiple fields using a Python format string with field placeholders.

This is the most powerful formatter, allowing you to combine multiple fields with custom formatting specifications in a single template string.

Parameters:

  • template - Template string with {field} placeholders (required)

  • fields - List of field names/paths to extract from context (required)

Template Features:

  • Python format specifications (e.g., .2f, :>10, :,)

  • Multiple fields in one template

  • Nested field access via dot notation (e.g., current_price.usd)

  • All Python format mini-language features

Examples:

# Basic field combination
[[layouts.demo.lines]]
field = "ticker"
index = 0
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "{symbol} - ${price:.2f}"
fields = ["symbol", "price"]
Input: {"symbol": "BTC", "price": 45234.567}
Output: "BTC - $45234.57"
# Multiple fields with formatting
[[layouts.demo.lines]]
field = "stock"
index = 1
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "{symbol} - ${price:.2f} - {volume}/$"
fields = ["symbol", "price", "volume"]
Input: {"symbol": "AAPL", "price": 172.43, "volume": "1.2M"}
Output: "AAPL - $172.43 - 1.2M/$"
# Nested field access
[[layouts.demo.lines]]
field = "crypto"
index = 2
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "{name}: ${current_price_usd:.2f}"
fields = ["name", "current_price.usd"]
Input: {"name": "Bitcoin", "current_price": {"usd": 45234.567}}
Output: "Bitcoin: $45234.57"
# Answer to user's question: format two floats
[[layouts.demo.lines]]
field = "coordinates"
index = 3
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "{a:.1f} {b:.1f}"
fields = ["a", "b"]
Input: {"a": 3.14159, "b": 2.71828}
Output: "3.1 2.7"
# Alignment and padding
[[layouts.demo.lines]]
field = "table_row"
index = 4
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "{name:<20} {value:>10.2f} {status:^10}"
fields = ["name", "value", "status"]
Input: {"name": "Temperature", "value": 23.456, "status": "OK"}
Output: "Temperature              23.46     OK    "
# Thousands separator
[[layouts.demo.lines]]
field = "stats"
index = 5
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "Population: {count:,}"
fields = ["count"]
Input: {"count": 1234567}
Output: "Population: 1,234,567"
# Percentage formatting
[[layouts.demo.lines]]
field = "progress"
index = 6
formatter = "template"

[layouts.demo.lines.formatter_params]
template = "Progress: {value:.1%}"
fields = ["value"]
Input: {"value": 0.756}
Output: "Progress: 75.6%"

Python Format Specification Mini-Language:

The template formatter supports all Python format specifications:

  • :f - Fixed-point notation

  • :.2f - Fixed-point with 2 decimal places

  • :, - Thousands separator

  • :.2% - Percentage with 2 decimals

  • :>10 - Right-align in 10 characters

  • :<10 - Left-align in 10 characters

  • :^10 - Center in 10 characters

  • :0>5 - Zero-pad to 5 characters

See Python’s Format Specification Mini-Language for complete details.

Error Handling:

  • Missing fields resolve to empty string

  • Invalid template syntax returns “Template error: <error message>”

  • Non-dict values are converted to string

Custom Formatters

You can register custom formatters using the FormatterRegistry:

Basic Custom Formatter

from viewtext import get_formatter_registry

def format_percentage(value, **kwargs):
    decimals = kwargs.get("decimals", 1)
    return f"{value:.{decimals}f}%"

formatter_registry = get_formatter_registry()
formatter_registry.register("percentage", format_percentage)

Then use it in your TOML:

[[layouts.demo.lines]]
field = "growth_rate"
index = 0
formatter = "percentage"

[layouts.demo.lines.formatter_params]
decimals = 2

Advanced Custom Formatter

from viewtext import get_formatter_registry

def format_bytes(value, **kwargs):
    """Format byte count as human-readable size."""
    units = ["B", "KB", "MB", "GB", "TB"]
    unit_index = 0
    size = float(value)

    while size >= 1024 and unit_index < len(units) - 1:
        size /= 1024
        unit_index += 1

    decimals = kwargs.get("decimals", 1)
    return f"{size:.{decimals}f} {units[unit_index]}"

formatter_registry = get_formatter_registry()
formatter_registry.register("bytes", format_bytes)

Usage:

[[layouts.demo.lines]]
field = "file_size"
index = 0
formatter = "bytes"

[layouts.demo.lines.formatter_params]
decimals = 2
Input: 1536 → Output: "1.50 KB"
Input: 1048576 → Output: "1.00 MB"

Formatter vs Computed Field Operation

It’s important to understand the difference between formatters and computed field operations:

Formatters:

  • Applied at display time

  • Only affect presentation, not data

  • Defined in layout line definitions

  • Return formatted strings

  • Cannot be chained or used as input to other fields

  • Examples: number, price, datetime, template

Computed Field Operations:

  • Applied at data processing time

  • Transform the actual data

  • Defined in the fields section

  • Return processed values (can be any type)

  • Can be chained (output of one is input to another)

  • Examples: add, subtract, multiply, concat, conditional

When to Use Each:

Use formatters when you want to:

  • Format numbers with thousands separators for display

  • Add currency symbols to prices

  • Format dates/times for display

  • Combine multiple fields into one display string

Use computed field operations when you want to:

  • Calculate derived values (e.g., subtotal * tax_rate)

  • Perform unit conversions (e.g., Celsius to Fahrenheit)

  • Transform data (e.g., concatenate first and last name)

  • Create intermediate values for further processing

Example showing both:

# Computed field: calculate total price
[fields.total_price]
operation = "multiply"
sources = ["price", "quantity"]
default = 0.0

# Formatter: display total_price with currency symbol
[[layouts.product.lines]]
field = "total_price"
index = 0
formatter = "price"

[layouts.product.lines.formatter_params]
symbol = "$"
decimals = 2
thousands_sep = ","

Formatter Presets

You can define reusable formatter configurations (presets) in your TOML files to promote consistency and reduce duplication across your layouts.

Defining Presets

Formatter presets are defined in the [formatters] section. Each preset has a type field that specifies the built-in formatter type, along with any parameters for that formatter:

# Define formatter presets
[formatters.usd_price]
type = "price"
symbol = "$"
decimals = 2
thousands_sep = ","

[formatters.eur_price]
type = "price"
symbol = "€"
decimals = 2
thousands_sep = "."
decimal_sep = ","

[formatters.short_date]
type = "datetime"
format = "%Y-%m-%d"

[formatters.time_only]
type = "datetime"
format = "%H:%M"

Using Presets in Layouts

Once defined, presets can be referenced by name in two ways:

Method 1: Direct reference in layout line (recommended for simplicity):

[[layouts.product.lines]]
field = "price"
index = 0
formatter = "usd_price"  # References the preset by name

[[layouts.product.lines]]
field = "created_at"
index = 1
formatter = "short_date"  # References the preset by name

Method 2: Template field_formatters (useful for template formatter):

[[layouts.crypto.lines]]
field = "ticker"
index = 0
formatter = "template"

[layouts.crypto.lines.formatter_params]
template = "{symbol} - {price}"
fields = ["symbol", "price"]
field_formatters = { "price": "usd_price" }  # Apply preset to specific field

Benefits of Using Presets

  1. Consistency - Define formatting rules once, use everywhere

  2. Maintainability - Update formatting in one place, affects all usages

  3. Readability - Layout definitions become more concise and expressive

  4. Reusability - Share formatters across multiple layouts and files

Comparison: Inline vs Preset

Without presets (verbose, repeated configuration):

[[layouts.product.lines]]
field = "price"
index = 0
formatter = "price"

[layouts.product.lines.formatter_params]
symbol = "$"
decimals = 2
thousands_sep = ","

[[layouts.invoice.lines]]
field = "total"
index = 5
formatter = "price"

[layouts.invoice.lines.formatter_params]
symbol = "$"
decimals = 2
thousands_sep = ","

With presets (concise, reusable):

[formatters.usd_price]
type = "price"
symbol = "$"
decimals = 2
thousands_sep = ","

[[layouts.product.lines]]
field = "price"
index = 0
formatter = "usd_price"

[[layouts.invoice.lines]]
field = "total"
index = 5
formatter = "usd_price"

Preset Resolution

When ViewText encounters a formatter name in a layout, it follows this resolution order:

  1. Check if formatter_params is provided inline - use built-in formatter with those params

  2. Check if a preset exists with that name - use preset configuration

  3. Fall back to built-in formatter with default parameters

This means you can mix inline parameters and presets in the same layout:

[formatters.standard_date]
type = "datetime"
format = "%Y-%m-%d"

[[layouts.mixed.lines]]
field = "created_at"
index = 0
formatter = "standard_date"  # Uses preset

[[layouts.mixed.lines]]
field = "price"
index = 1
formatter = "price"

[layouts.mixed.lines.formatter_params]
symbol = "$"
decimals = 2  # Inline parameters

Organizing Presets in Separate Files

For better organization, you can define formatter presets in a separate file:

formatters.toml:

[formatters.usd_price]
type = "price"
symbol = "$"
decimals = 2
thousands_sep = ","

[formatters.eur_price]
type = "price"
symbol = "€"
decimals = 2
thousands_sep = "."
decimal_sep = ","

layouts.toml:

[layouts.product]
name = "Product Display"

[[layouts.product.lines]]
field = "price"
index = 0
formatter = "usd_price"

Load both files together:

viewtext --config layouts.toml --formatters formatters.toml list
from viewtext import LayoutLoader

loader = LayoutLoader(
    config_path="layouts.toml",
    formatters_path="formatters.toml"
)

See User Guide for more information on split configuration files.

Best Practices

  1. Use appropriate formatters - Choose the formatter that best matches your data type (price for currency, datetime for timestamps, etc.)

  2. Define global formatter configurations - Create reusable formatter presets for consistency across layouts

  3. Use template formatter for complex formatting - When you need to combine multiple fields or use advanced Python format specifications

  4. Consider localization - Use thousands_sep and decimal_sep parameters to format numbers according to regional conventions

  5. Test with edge cases - Verify formatter behavior with None values, very large/small numbers, and edge cases

  6. Document custom formatters - Add docstrings and examples when creating custom formatters

  7. Keep formatters simple - Formatters should focus on presentation. Use computed field operations for data transformation.

Examples by Use Case

Financial Data

# US stock display
[[layouts.stock.lines]]
field = "ticker"
index = 0
formatter = "template"

[layouts.stock.lines.formatter_params]
template = "{symbol} ${price:.2f} {change:+.2f} ({change_pct:+.1f}%)"
fields = ["symbol", "price", "change", "change_pct"]

# European price formatting
[[layouts.product_eu.lines]]
field = "price"
index = 1
formatter = "price"

[layouts.product_eu.lines.formatter_params]
symbol = "€"
decimals = 2
thousands_sep = "."
decimal_sep = ","

Temperature Display

# Celsius with one decimal
[[layouts.weather.lines]]
field = "temp_celsius"
index = 0
formatter = "number"

[layouts.weather.lines.formatter_params]
decimals = 1
suffix = "°C"

# Both Celsius and Fahrenheit
[[layouts.weather.lines]]
field = "temperature"
index = 1
formatter = "template"

[layouts.weather.lines.formatter_params]
template = "{temp_c:.1f}°C / {temp_f:.1f}°F"
fields = ["temp_c", "temp_f"]

Activity Timestamps

# Last seen time
[[layouts.user.lines]]
field = "last_seen_seconds"
index = 0
formatter = "relative_time"

[layouts.user.lines.formatter_params]
format = "short"

# Created date
[[layouts.user.lines]]
field = "created_at"
index = 1
formatter = "datetime"

[layouts.user.lines.formatter_params]
format = "%b %d, %Y"

Dashboard Layout

# System metrics dashboard
[[layouts.dashboard.lines]]
field = "cpu"
index = 0
formatter = "template"

[layouts.dashboard.lines.formatter_params]
template = "CPU: {usage:>5.1f}% [{cores} cores]"
fields = ["usage", "cores"]

[[layouts.dashboard.lines]]
field = "memory"
index = 1
formatter = "template"

[layouts.dashboard.lines.formatter_params]
template = "MEM: {used:>6.1f}/{total:>6.1f} GB ({pct:>5.1f}%)"
fields = ["used", "total", "pct"]

[[layouts.dashboard.lines]]
field = "uptime"
index = 2
formatter = "relative_time"

[layouts.dashboard.lines.formatter_params]
format = "short"

See Also