Skip to content

pygx.formatting

String formatting primitives shared across PyGX.

formatting

String formatting primitives shared across PyGX.

pygx.formatting houses the Formattable mixin used by every symbolic value to produce its __str__ / __repr__ output, alongside helpers for bracket glyphs, key-value rendering, markdown-quoting, pluralization, and ANSI-coloring of terminal output. It sits below pygx.utils and pygx.symbolic in the dependency stack so those layers can rely on it without introducing cycles.

BracketType

Bases: IntEnum

Bracket types used for complex type formatting.

Formattable

Interface for classes whose instances can be pretty-formatted.

This interface overrides the default __repr__ and __str__ method, thus all Formattable objects can be printed nicely.

All symbolic types implement this interface.

format abstractmethod

format(
    compact: bool = False,
    verbose: bool = True,
    root_indent: int = 0,
    **kwargs: Any
) -> str

Formats this object into a string representation.

Parameters:

Name Type Description Default
compact bool

If True, this object will be formatted into a single line.

False
verbose bool

If True, this object will be formatted with verbosity. Subclasses should define verbosity on their own.

True
root_indent int

The start indent level for this object if the output is a multi-line string.

0
**kwargs Any

Subclass specific keyword arguments.

{}

Returns:

Type Description
str

A string of formatted object.

Source code in pygx/formatting/_formatting.py
@abc.abstractmethod
def format(
    self,
    compact: bool = False,
    verbose: bool = True,
    root_indent: int = 0,
    **kwargs: Any,
) -> str:
    """Formats this object into a string representation.

    Args:
      compact: If True, this object will be formatted into a single line.
      verbose: If True, this object will be formatted with verbosity.
        Subclasses should define `verbosity` on their own.
      root_indent: The start indent level for this object if the output is a
        multi-line string.
      **kwargs: Subclass specific keyword arguments.

    Returns:
      A string of formatted object.
    """

RawText

RawText(text: str)

Bases: Formattable

Raw text.

Source code in pygx/formatting/_formatting.py
def __init__(self, text: str):
    self.text = text

auto_plural

auto_plural(number: int, singular: str, plural: str | None = None) -> str

Use singular form if number is 1, otherwise use plural form.

Source code in pygx/formatting/_formatting.py
def auto_plural(number: int, singular: str, plural: str | None = None) -> str:
    """Use singular form if number is 1, otherwise use plural form."""
    if plural is None:
        plural = singular + 's'
    return singular if number == 1 else plural

bracket_chars

bracket_chars(bracket_type: BracketType) -> tuple[str, str]

Gets bracket character.

Source code in pygx/formatting/_formatting.py
def bracket_chars(bracket_type: BracketType) -> tuple[str, str]:
    """Gets bracket character."""
    return _BRACKET_CHARS[int(bracket_type)]

camel_to_snake

camel_to_snake(text: str, separator: str = '_') -> str

Returns the snake case version of a camel case string.

Source code in pygx/formatting/_formatting.py
def camel_to_snake(text: str, separator: str = '_') -> str:
    """Returns the snake case version of a camel case string."""
    chunks = []
    chunk_start = 0
    last_upper = 0
    length = len(text)
    for i, c in enumerate(text):
        if c.isupper():
            if last_upper < i - 1 or (i < length - 1 and text[i + 1].islower()):
                chunks.append(text[chunk_start:i])
                chunk_start = i
            last_upper = i
    chunks.append(text[chunk_start:])
    return (separator.join(c for c in chunks if c)).lower()

comma_delimited_str

comma_delimited_str(value_list: Sequence[Any]) -> str

Gets comma delimited string.

Source code in pygx/formatting/_formatting.py
def comma_delimited_str(value_list: Sequence[Any]) -> str:
    """Gets comma delimited string."""
    return ', '.join(str(quote_if_str(v)) for v in value_list)

format

format(
    value: Any,
    compact: bool = False,
    verbose: bool = True,
    root_indent: int = 0,
    list_wrap_threshold: int = 80,
    strip_object_id: bool = False,
    include_keys: set[str] | None = None,
    exclude_keys: set[str] | None = None,
    markdown: bool = False,
    max_str_len: int | None = None,
    max_bytes_len: int | None = None,
    *,
    custom_format: CustomFormatFn | None = None,
    memo: set[int] | None = None,
    **kwargs: Any
) -> str

Formats a (maybe) hierarchical value with flags.

Parameters:

Name Type Description Default
value Any

The value to format.

required
compact bool

If True, this object will be formatted into a single line.

False
verbose bool

If True, this object will be formatted with verbosity. Subclasses should define verbosity on their own.

True
root_indent int

The start indent level for this object if the output is a multi-line string.

0
list_wrap_threshold int

A threshold in number of characters for wrapping a list value in a single line.

80
strip_object_id bool

If True, format object as '(...)' other than 'object at

'.

False
include_keys set[str] | None

A set of keys to include from the top-level dict or object.

None
exclude_keys set[str] | None

A set of keys to exclude from the top-level dict or object. Applicable only when include_keys is set to None.

None
markdown bool

If True, use markdown notion to quote the formatted object.

False
max_str_len int | None

The max length of the string to be formatted. If the string is longer than this length, it will be truncated.

None
max_bytes_len int | None

The max length of the bytes to be formatted. If the bytes is longer than this length, it will be truncated.

None
custom_format CustomFormatFn | None

An optional custom format function, which will be applied to each value (and child values) in kvlist. If the function returns None, it will fall back to the default pg.format.

None
memo set[int] | None

A set of object ids that have been formatted. Used to avoid infinite recursion in the formatting process.

None
**kwargs Any

Keyword arguments that will be passed through unto child Formattable objects.

{}

Returns:

Type Description
str

A string representation for value.

Source code in pygx/formatting/_formatting.py
def format(  # pylint: disable=redefined-builtin
    value: Any,
    compact: bool = False,
    verbose: bool = True,
    root_indent: int = 0,
    list_wrap_threshold: int = 80,
    strip_object_id: bool = False,
    include_keys: set[str] | None = None,
    exclude_keys: set[str] | None = None,
    markdown: bool = False,
    max_str_len: int | None = None,
    max_bytes_len: int | None = None,
    *,
    custom_format: CustomFormatFn | None = None,
    memo: set[int] | None = None,
    **kwargs: Any,
) -> str:
    """Formats a (maybe) hierarchical value with flags.

    Args:
      value: The value to format.
      compact: If True, this object will be formatted into a single line.
      verbose: If True, this object will be formatted with verbosity. Subclasses
        should define `verbosity` on their own.
      root_indent: The start indent level for this object if the output is a
        multi-line string.
      list_wrap_threshold: A threshold in number of characters for wrapping a list
        value in a single line.
      strip_object_id: If True, format object as '<class-name>(...)' other than
        'object at <address>'.
      include_keys: A set of keys to include from the top-level dict or object.
      exclude_keys: A set of keys to exclude from the top-level dict or object.
        Applicable only when `include_keys` is set to None.
      markdown: If True, use markdown notion to quote the formatted object.
      max_str_len: The max length of the string to be formatted. If the string is
        longer than this length, it will be truncated.
      max_bytes_len: The max length of the bytes to be formatted. If the bytes is
        longer than this length, it will be truncated.
      custom_format: An optional custom format function, which will be applied to
        each value (and child values) in kvlist. If the function returns None, it
        will fall back to the default `pg.format`.
      memo: A set of object ids that have been formatted. Used to avoid
        infinite recursion in the formatting process.
      **kwargs: Keyword arguments that will be passed through unto child
        ``Formattable`` objects.

    Returns:
      A string representation for `value`.
    """
    # 1) Custom format hook may intercept any value entirely.
    if custom_format is not None:
        result = custom_format(value, root_indent)
        if result is not None:
            return maybe_markdown_quote(result, markdown)

    # 2) Fast path: primitives can't recurse, so we skip memo / closures / TLS.
    #    Using `type(v) is X` instead of isinstance is intentional: it's faster
    #    and subclasses (e.g. `bool` of `int`) are handled by their own branch.
    value_type = type(value)
    if value_type is int or value_type is float or value_type is bool:
        return maybe_markdown_quote(repr(value), markdown)
    if value is None:
        return maybe_markdown_quote('None', markdown)
    if value_type is str:
        if max_str_len is not None and len(value) > max_str_len:
            value = value[:max_str_len] + '...'
        return maybe_markdown_quote(repr(value), markdown)
    if value_type is bytes:
        if max_bytes_len is not None and len(value) > max_bytes_len:
            value = value[:max_bytes_len] + b'...'
        return maybe_markdown_quote(repr(value), markdown)

    # 3) Cycle protection — only complex values need this.
    if memo is None:
        memo = set()
    id_ = id(value)
    if id_ in memo:
        return f'<recursive {value.__class__.__name__} at 0x{id_:x}>'
    memo.add(id_)

    # 4) Dispatch on container/Formattable type.
    is_formattable = _is_formattable(value_type)

    # Downstream `Formattable.format()` implementations expect `exclude_keys`
    # to be a non-None set when they pop it from kwargs and mutate it
    # (e.g. to add their own field names). Preserve that invariant.
    if exclude_keys is None:
        exclude_keys = set()

    # `markdown` is only applied at the outer-most level, so we suppress it for
    # any nested `__str__`/`__repr__` invocations. The TLS push is only needed
    # for branches that may call those: the Formattable branch (calls into
    # `value.format()` which often uses `str()`/`repr()` internally) and the
    # generic "other" branch (calls `str_ext`/`repr_ext` directly). List, tuple,
    # and dict branches recurse via `format()` which uses `markdown=False` by
    # default, so the push there would be pure overhead.
    try:
        # Hot path: Formattable (covers pg.Object/pg.Dict/pg.List etc.) —
        # delegates to `value.format()` which handles its own children. No
        # `child_kwargs` dict needed; the central kwargs are passed through
        # directly. Downstream tolerates `exclude_keys=None`, so we don't
        # allocate an empty set here.
        if is_formattable:
            if _outer_markdown_active():
                with str_format(markdown=False), repr_format(markdown=False):
                    s = value.format(
                        compact=compact,
                        verbose=verbose,
                        root_indent=root_indent,
                        list_wrap_threshold=list_wrap_threshold,
                        strip_object_id=strip_object_id,
                        include_keys=include_keys,
                        exclude_keys=exclude_keys,
                        max_str_len=max_str_len,
                        max_bytes_len=max_bytes_len,
                        custom_format=custom_format,
                        memo=memo,
                        **kwargs,
                    )
            else:
                s = value.format(
                    compact=compact,
                    verbose=verbose,
                    root_indent=root_indent,
                    list_wrap_threshold=list_wrap_threshold,
                    strip_object_id=strip_object_id,
                    include_keys=include_keys,
                    exclude_keys=exclude_keys,
                    max_str_len=max_str_len,
                    max_bytes_len=max_bytes_len,
                    custom_format=custom_format,
                    memo=memo,
                    **kwargs,
                )
        else:
            is_list = value_type is list or isinstance(value, list)
            is_tuple = not is_list and (
                value_type is tuple or isinstance(value, tuple)
            )
            is_dict = (
                not is_list
                and not is_tuple
                and (value_type is dict or isinstance(value, dict))
            )

            if is_list or is_tuple or is_dict:
                # Shared kwargs for recursive child formatting. `markdown` is
                # intentionally not propagated — it only applies at the
                # outer-most level.
                child_kwargs = dict(
                    compact=compact,
                    verbose=verbose,
                    root_indent=root_indent + 1,
                    list_wrap_threshold=list_wrap_threshold,
                    strip_object_id=strip_object_id,
                    max_str_len=max_str_len,
                    max_bytes_len=max_bytes_len,
                    custom_format=custom_format,
                    memo=memo,
                    **kwargs,
                )
                if is_list:
                    s = _format_sequence(
                        value,
                        '[',
                        ']',
                        compact,
                        list_wrap_threshold,
                        root_indent,
                        child_kwargs,
                    )
                elif is_tuple:
                    s = _format_sequence(
                        value,
                        '(',
                        ')',
                        compact,
                        list_wrap_threshold,
                        root_indent,
                        child_kwargs,
                    )
                else:
                    s = _format_dict(
                        value,
                        compact,
                        root_indent,
                        include_keys,
                        exclude_keys,
                        child_kwargs,
                    )
            else:
                # Generic "other" path — may call user `__str__`/`__repr__`.
                if _outer_markdown_active():
                    with (
                        str_format(markdown=False),
                        repr_format(markdown=False),
                    ):
                        s = _format_other(
                            value,
                            compact,
                            strip_object_id,
                            custom_format,
                            root_indent,
                        )
                else:
                    s = _format_other(
                        value,
                        compact,
                        strip_object_id,
                        custom_format,
                        root_indent,
                    )
    finally:
        memo.discard(id_)
    return maybe_markdown_quote(s, markdown)

kvlist_str

kvlist_str(
    kvlist: list[tuple[str, Any, Any]],
    compact: bool = True,
    verbose: bool = False,
    root_indent: int = 0,
    *,
    label: str | None = None,
    bracket_type: BracketType = ROUND,
    custom_format: CustomFormatFn | None = None,
    memo: set[int] | None = None,
    **kwargs: Any
) -> str

Formats a list key/value pairs into a comma delimited string.

Parameters:

Name Type Description Default
kvlist list[tuple[str, Any, Any]]

List of tuples in format of (key, value, default_value or a tuple of default values)

required
compact bool

If True, format value in kvlist in compact form.

True
verbose bool

If True, format value in kvlist in verbose.

False
root_indent int

The indent should be applied for values in kvlist if they are multi-line.

0
label str | None

(Optional) If not None, add label to brace all kv pairs.

None
bracket_type BracketType

Bracket type used for embracing the kv pairs. Applicable only when name is not None.

ROUND
custom_format CustomFormatFn | None

An optional custom format function, which will be applied to each value (and child values) in kvlist. If the function returns None, it will fall back to the default pg.format.

None
memo set[int] | None

A set of object ids that have been formatted. Used to avoid infinite recursion in the formatting process.

None
**kwargs Any

Keyword arguments that will be passed through unto child Formattable objects.

{}

Returns: A formatted string from a list of key/value pairs delimited by comma.

Source code in pygx/formatting/_formatting.py
def kvlist_str(
    kvlist: list[tuple[str, Any, Any]],
    compact: bool = True,
    verbose: bool = False,
    root_indent: int = 0,
    *,
    label: str | None = None,
    bracket_type: BracketType = BracketType.ROUND,
    custom_format: CustomFormatFn | None = None,
    memo: set[int] | None = None,
    **kwargs: Any,
) -> str:
    """Formats a list key/value pairs into a comma delimited string.

    Args:
      kvlist: List of tuples in format of
        (key, value, default_value or a tuple of default values)
      compact: If True, format value in kvlist in compact form.
      verbose: If True, format value in kvlist in verbose.
      root_indent: The indent should be applied for values in kvlist if they are
        multi-line.
      label: (Optional) If not None, add label to brace all kv pairs.
      bracket_type: Bracket type used for embracing the kv pairs. Applicable only
        when `name` is not None.
      custom_format: An optional custom format function, which will be applied to
        each value (and child values) in kvlist. If the function returns None, it
        will fall back to the default `pg.format`.
      memo: A set of object ids that have been formatted. Used to avoid
        infinite recursion in the formatting process.
      **kwargs: Keyword arguments that will be passed through unto child
        ``Formattable`` objects.
    Returns:
      A formatted string from a list of key/value pairs delimited by comma.
    """
    # Build the body as a list of fragments and join once at the end —
    # significantly faster than io.StringIO writes for the small-N case that
    # dominates `Formattable.format()` callers.
    body: list[str] = []
    child_indent = (root_indent + 1) if label else root_indent
    child_indent_str = '  ' * child_indent if not compact else ''
    separator = ', ' if compact else ',\n'
    has_content = False

    for k, v, d in kvlist:
        if isinstance(d, tuple):
            include_pair = True
            for sd in d:
                if sd == v:
                    include_pair = False
                    break
        else:
            include_pair = v != d
        if not include_pair:
            continue
        if has_content:
            body.append(separator)
        if not compact:
            body.append(child_indent_str)
        formatted = format(
            v,
            compact=compact,
            verbose=verbose,
            root_indent=child_indent,
            custom_format=custom_format,
            memo=memo,
            **kwargs,
        )
        if k:
            body.append(k)
            body.append('=')
        body.append(str_ext(formatted, custom_format, child_indent))
        has_content = True

    if label is None:
        return ''.join(body)
    bracket_start, bracket_end = bracket_chars(bracket_type)
    if not has_content:
        # Empty: "Label()".
        return f'{label}{bracket_start}{bracket_end}'
    if compact:
        return f'{label}{bracket_start}{"".join(body)}{bracket_end}'
    # Multi-line with label: "Label(\n  ...\n)".
    return (
        f'{label}{bracket_start}\n{"".join(body)}\n'
        f'{"  " * root_indent}{bracket_end}'
    )

maybe_markdown_quote

maybe_markdown_quote(s: str, markdown: bool = True) -> str

Maybe quote the formatted string with markdown.

Source code in pygx/formatting/_formatting.py
def maybe_markdown_quote(s: str, markdown: bool = True) -> str:
    """Maybe quote the formatted string with markdown."""
    if not markdown:
        return s
    if '\n' not in s:
        return f'`{s}`'
    else:
        return f'```\n{s}\n```'

printv

printv(*args, **kwargs)

Prints formatted value.

Source code in pygx/formatting/_formatting.py
def printv(*args, **kwargs):
    """Prints formatted value."""
    fs = kwargs.pop('file', sys.stdout)
    print(*[format(v, **kwargs) for v in args], file=fs)

quote_if_str

quote_if_str(value: Any) -> Any

Quotes the value if it is a str.

Source code in pygx/formatting/_formatting.py
def quote_if_str(value: Any) -> Any:
    """Quotes the value if it is a str."""
    if isinstance(value, str):
        return repr(value)
    return value

repr_format

repr_format(**kwargs) -> ContextManager[dict[str, Any]]

Context manager for setting the default format kwargs for repr.

Source code in pygx/formatting/_formatting.py
def repr_format(**kwargs) -> ContextManager[dict[str, Any]]:
    """Context manager for setting the default format kwargs for __repr__."""
    return thread_local.thread_local_arg_scope(
        _TLS_REPR_FORMAT_KWARGS, **kwargs
    )

str_format

str_format(**kwargs) -> ContextManager[dict[str, Any]]

Context manager for setting the default format kwargs for str.

Source code in pygx/formatting/_formatting.py
def str_format(**kwargs) -> ContextManager[dict[str, Any]]:
    """Context manager for setting the default format kwargs for __str__."""
    return thread_local.thread_local_arg_scope(_TLS_STR_FORMAT_KWARGS, **kwargs)

colored

colored(
    text: str,
    color: str | None = None,
    background: str | None = None,
    styles: list[str] | None = None,
) -> str

Returns the colored text with ANSI color characters.

Parameters:

Name Type Description Default
text str

A string that may or may not already has ANSI color characters.

required
color str | None

A string for text colors. Applicable values are: 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.

None
background str | None

A string for background colors. Applicable values are: 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.

None
styles list[str] | None

A list of strings for applying styles on the text. Applicable values are: 'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.

None

Returns:

Type Description
str

A string with ANSI color characters embracing the entire text.

Source code in pygx/formatting/_text_color.py
def colored(
    text: str,
    color: str | None = None,
    background: str | None = None,
    styles: list[str] | None = None,
) -> str:
    """Returns the colored text with ANSI color characters.

    Args:
      text: A string that may or may not already has ANSI color characters.
      color: A string for text colors. Applicable values are:
        'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
      background: A string for background colors. Applicable values are:
        'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
      styles: A list of strings for applying styles on the text.
        Applicable values are:
        'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.

    Returns:
      A string with ANSI color characters embracing the entire text.
    """
    if not _termcolor_loaded:
        _load_termcolor()
    if not termcolor:
        return text
    return termcolor.colored(
        text,
        color=color,
        on_color=('on_' + background) if background else None,
        attrs=styles,
    )

colored_block

colored_block(
    text: str,
    block_start: str,
    block_end: str,
    color: str | None = None,
    background: str | None = None,
    styles: list[str] | None = None,
) -> str

Apply colors to text blocks.

Parameters:

Name Type Description Default
text str

A string that may or may not already has ANSI color characters.

required
block_start str

A string that signals the start of a block. E.g. '{{'

required
block_end str

A string that signals the end of a block. E.g. '}}'.

required
color str | None

A string for text colors. Applicable values are: 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.

None
background str | None

A string for background colors. Applicable values are: 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.

None
styles list[str] | None

A list of strings for applying styles on the text. Applicable values are: 'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.

None

Returns:

Type Description
str

A string with ANSI color characters embracing the matched text blocks.

Source code in pygx/formatting/_text_color.py
def colored_block(
    text: str,
    block_start: str,
    block_end: str,
    color: str | None = None,
    background: str | None = None,
    styles: list[str] | None = None,
) -> str:
    """Apply colors to text blocks.

    Args:
      text: A string that may or may not already has ANSI color characters.
      block_start: A string that signals the start of a block. E.g. '{{'
      block_end: A string that signals the end of a block. E.g. '}}'.
      color: A string for text colors. Applicable values are:
        'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
      background: A string for background colors. Applicable values are:
        'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'.
      styles: A list of strings for applying styles on the text.
        Applicable values are:
        'bold', 'dark', 'underline', 'blink', 'reverse', 'concealed'.

    Returns:
      A string with ANSI color characters embracing the matched text blocks.
    """
    if not color and not background and not styles:
        return text

    s = []
    end_index = 0
    previous_color = None

    def write_nonblock_text(text: str, previous_color: str | None):
        if previous_color:
            s.append(previous_color)
        s.append(text)

    while True:
        start_index = text.find(block_start, end_index)
        if start_index == -1:
            write_nonblock_text(text[end_index:], previous_color)
            break

        # Deal with text since last block.
        since_last_block = text[end_index:start_index]
        write_nonblock_text(since_last_block, previous_color)
        colors = re.findall(_ANSI_COLOR_REGEX, since_last_block)
        if colors:
            previous_color = colors[-1]

        # Match block.
        end_index = text.find(block_end, start_index + len(block_start))
        if end_index == -1:
            write_nonblock_text(text[start_index:], previous_color)
            break
        end_index += len(block_end)

        # Write block text.
        block = text[start_index:end_index]
        block = colored(
            block, color=color, background=background, styles=styles
        )
        s.append(block)
    return ''.join(s)

decolor

decolor(text: str) -> str

De-colors a string that may contains ANSI color characters.

Source code in pygx/formatting/_text_color.py
def decolor(text: str) -> str:
    """De-colors a string that may contains ANSI color characters."""
    return re.sub(_ANSI_COLOR_REGEX, '', text)

options: show_root_heading: false show_root_toc_entry: false members_order: source heading_level: 2