Skip to content

Display

coola.display

Provide helpers to display objects and to write to the terminal.

The package has three kinds of content:

  • Mixins: InlineDisplayMixin, MultilineDisplayMixin and BaseDisplayMixin give a class a consistent __repr__ and __str__. A class only overrides _display_fields.
  • Pydantic formatting: str_pydantic_model and repr_pydantic_model format a pydantic model without exposing its secrets. They need pydantic, which is imported the first time they are used.
  • Terminal output: coola.display.rich and coola.display.colorlog configure logging and print with rich and colorlog. A function raises MissingPackageError when its library is not installed.

coola.display.BaseDisplayMixin

Bases: ABC

Abstract base for display mixins.

__repr__ and __str__ are defined here: they get the fields from _display_fields and delegate the layout to _render_fields. Subclasses must implement _render_fields to choose the layout. Classes that use a layout mixin override _display_fields to return the values to show; by default no field is shown.

coola.display.InlineDisplayMixin

Bases: BaseDisplayMixin

Mixin that renders __repr__ and __str__ on a single line.

All fields are displayed inline. Best suited for simple objects with few, short fields where a compact single-line format aids readability. Override _display_fields to choose the fields to show; by default none is shown, which suits stateless classes.

Example
>>> from coola.display import InlineDisplayMixin
>>> from typing import Any
>>> class MyClass(InlineDisplayMixin):
...     def __init__(self, key1: str, key2: str) -> None:
...         self.key1 = key1
...         self.key2 = key2
...     def _display_fields(self) -> dict[str, Any]:
...         return {"key1": self.key1, "key2": self.key2}
...
>>> obj = MyClass(key1="value1", key2="value2")
>>> print(repr(obj))
MyClass(key1='value1', key2='value2')
>>> print(str(obj))
MyClass(key1=value1, key2=value2)
>>> class NoFields(InlineDisplayMixin):
...     pass
...
>>> print(repr(NoFields()))
NoFields()

coola.display.MultilineDisplayMixin

Bases: BaseDisplayMixin

Mixin that renders __repr__ and __str__ in multiline dict format.

Each field is displayed on its own indented line. Best suited for objects with several fields or deeply nested values where a single-line format would be hard to read. Override _display_fields to choose the fields to show; by default none is shown.

Example
>>> from coola.display import MultilineDisplayMixin
>>> from typing import Any
>>> class MyClass(MultilineDisplayMixin):
...     def __init__(self, key1: str, key2: str) -> None:
...         self.key1 = key1
...         self.key2 = key2
...     def _display_fields(self) -> dict[str, Any]:
...         return {"key1": self.key1, "key2": self.key2}
...
>>> obj = MyClass(key1="value1", key2="value2")
>>> print(repr(obj))
MyClass(
  (key1): value1
  (key2): value2
)
>>> print(str(obj))
MyClass(
  (key1): value1
  (key2): value2
)

coola.display.repr_pydantic_model

repr_pydantic_model(
    model: BaseModel,
    *,
    sort: bool = True,
    exclude_none: bool = False,
    exclude_secret: bool = True,
    exclude_fields: list[str] | None = None,
) -> str

Return a formatted, single-line repr-style representation of a pydantic model.

It has the same options and behaviour as :func:str_pydantic_model, but it uses repr for each value.

Parameters:

Name Type Description Default
model BaseModel

The pydantic model to format.

required
sort bool

See :func:str_pydantic_model.

True
exclude_none bool

See :func:str_pydantic_model.

False
exclude_secret bool

See :func:str_pydantic_model.

True
exclude_fields list[str] | None

See :func:str_pydantic_model.

None

Returns:

Type Description
str

A string like "ClassName(field1=value1, field2=value2)" using repr for each value.

Example
>>> from pydantic import BaseModel
>>> from coola.display.pydantic import repr_pydantic_model
>>> class Config(BaseModel):
...     name: str
...     count: int
...
>>> repr_pydantic_model(Config(name="my-app", count=3))
"Config(count=3, name='my-app')"

coola.display.str_pydantic_model

str_pydantic_model(
    model: BaseModel,
    *,
    sort: bool = True,
    exclude_none: bool = False,
    exclude_secret: bool = True,
    exclude_fields: list[str] | None = None,
) -> str

Return a formatted, single-line string representation of a pydantic model.

The fields are read from the model: no serializer runs, a field declared with Field(repr=False) is hidden (as in the repr of pydantic), the values of extra="allow" are shown, and a computed_field is not shown because it is not a field. A nested model keeps its name and is formatted with sort, exclude_none and exclude_secret (also inside lists, tuples, sets, frozensets and dicts). The nested secrets are never shown: they are omitted, or masked by pydantic if exclude_secret is False.

Parameters:

Name Type Description Default
model BaseModel

The pydantic model to format.

required
sort bool

If True, sort fields by name.

True
exclude_none bool

If True, omit fields whose value is None.

False
exclude_secret bool

If True, omit fields typed as a secret type (SecretStr, SecretBytes, Secret) entirely, rather than showing the masked value.

True
exclude_fields list[str] | None

Optional list of field names to omit. A RuntimeWarning is emitted for names that are not fields of the model; a field already omitted by exclude_none or exclude_secret is not reported. The names apply to the root model only.

None

Returns:

Type Description
str

A string like "ClassName(field1=value1, field2=value2)".

Example
>>> from pydantic import BaseModel
>>> from coola.display.pydantic import str_pydantic_model
>>> class Child(BaseModel):
...     name: str
...
>>> class Config(BaseModel):
...     name: str
...     count: int
...     child: Child
...
>>> str_pydantic_model(Config(name="my-app", count=3, child=Child(name="a")))
'Config(child=Child(name=a), count=3, name=my-app)'

coola.display.pydantic

Contain utilities to generate formatted strings of pydantic objects.

coola.display.pydantic.is_secret_annotation

is_secret_annotation(annotation: object) -> bool

Indicate if an annotation is, or contains, a secret type.

The secret types are SecretStr, SecretBytes and Secret. The arguments of generic aliases (Optional, tuple, Annotated, ...) are inspected recursively.

Parameters:

Name Type Description Default
annotation object

The type annotation to inspect.

required

Returns:

Type Description
bool

True if the annotation is or contains a secret type.

Example
>>> from typing import Optional
>>> from pydantic import SecretStr
>>> from coola.display.pydantic import is_secret_annotation
>>> is_secret_annotation(Optional[SecretStr])
True
>>> is_secret_annotation(int)
False

coola.display.pydantic.repr_pydantic_model

repr_pydantic_model(
    model: BaseModel,
    *,
    sort: bool = True,
    exclude_none: bool = False,
    exclude_secret: bool = True,
    exclude_fields: list[str] | None = None,
) -> str

Return a formatted, single-line repr-style representation of a pydantic model.

It has the same options and behaviour as :func:str_pydantic_model, but it uses repr for each value.

Parameters:

Name Type Description Default
model BaseModel

The pydantic model to format.

required
sort bool

See :func:str_pydantic_model.

True
exclude_none bool

See :func:str_pydantic_model.

False
exclude_secret bool

See :func:str_pydantic_model.

True
exclude_fields list[str] | None

See :func:str_pydantic_model.

None

Returns:

Type Description
str

A string like "ClassName(field1=value1, field2=value2)" using repr for each value.

Example
>>> from pydantic import BaseModel
>>> from coola.display.pydantic import repr_pydantic_model
>>> class Config(BaseModel):
...     name: str
...     count: int
...
>>> repr_pydantic_model(Config(name="my-app", count=3))
"Config(count=3, name='my-app')"

coola.display.pydantic.secret_field_names

secret_field_names(model: BaseModel) -> set[str]

Return the names of fields annotated as a secret type.

A field is secret if its annotation is SecretStr, SecretBytes or Secret, or contains one of them (for example Optional[SecretStr] or tuple[SecretStr, int]). The whole field is then considered secret.

Parameters:

Name Type Description Default
model BaseModel

The pydantic model to inspect.

required

Returns:

Type Description
set[str]

The set of field names whose annotation is (or includes) a secret type.

Example
>>> from pydantic import BaseModel, SecretStr
>>> from coola.display.pydantic import secret_field_names
>>> class Config(BaseModel):
...     name: str
...     token: SecretStr
...
>>> secret_field_names(Config(name="my-app", token="s3cr3t"))
{'token'}

coola.display.pydantic.str_pydantic_model

str_pydantic_model(
    model: BaseModel,
    *,
    sort: bool = True,
    exclude_none: bool = False,
    exclude_secret: bool = True,
    exclude_fields: list[str] | None = None,
) -> str

Return a formatted, single-line string representation of a pydantic model.

The fields are read from the model: no serializer runs, a field declared with Field(repr=False) is hidden (as in the repr of pydantic), the values of extra="allow" are shown, and a computed_field is not shown because it is not a field. A nested model keeps its name and is formatted with sort, exclude_none and exclude_secret (also inside lists, tuples, sets, frozensets and dicts). The nested secrets are never shown: they are omitted, or masked by pydantic if exclude_secret is False.

Parameters:

Name Type Description Default
model BaseModel

The pydantic model to format.

required
sort bool

If True, sort fields by name.

True
exclude_none bool

If True, omit fields whose value is None.

False
exclude_secret bool

If True, omit fields typed as a secret type (SecretStr, SecretBytes, Secret) entirely, rather than showing the masked value.

True
exclude_fields list[str] | None

Optional list of field names to omit. A RuntimeWarning is emitted for names that are not fields of the model; a field already omitted by exclude_none or exclude_secret is not reported. The names apply to the root model only.

None

Returns:

Type Description
str

A string like "ClassName(field1=value1, field2=value2)".

Example
>>> from pydantic import BaseModel
>>> from coola.display.pydantic import str_pydantic_model
>>> class Child(BaseModel):
...     name: str
...
>>> class Config(BaseModel):
...     name: str
...     count: int
...     child: Child
...
>>> str_pydantic_model(Config(name="my-app", count=3, child=Child(name="a")))
'Config(child=Child(name=a), count=3, name=my-app)'

coola.display.colorlog

Provide utilities for configuring Python's logging output.

coola.display.colorlog.configure_colorlog_logging

configure_colorlog_logging(
    level: int = INFO, *, force: bool = False, **kwargs: Any
) -> None

Configure the root logger, using a coloured formatter when available.

If the colorlog package is installed and sys.stderr is attached to a terminal, attaches a colorlog.StreamHandler with per-level colours for both the log metadata (level, logger name, line number) and the message itself. If colorlog is not installed, or output is not a terminal (e.g. redirected to a file or running in CI), falls back to plain logging.basicConfig with no formatting, to avoid emitting raw ANSI escape codes into non-interactive output.

The coloured handler writes to stream if given, and to sys.stderr otherwise; the terminal check applies to this stream. If handlers, filename, filemode, format or style is given, the caller controls the output and the call is forwarded to logging.basicConfig without a coloured handler. datefmt is used by the coloured formatter.

colorlog is optional, unlike rich for :func:~coola.display.rich.configure_rich_logging: without it the output has no colour, which is a correct result for a logging helper, so no error is raised.

Note

logging.basicConfig is a no-op if the root logger already has handlers configured. Pass force=True to remove existing handlers and reconfigure unconditionally.

Parameters:

Name Type Description Default
level int

Minimum log level for the root logger. Accepts any constant from logging (e.g. logging.DEBUG, logging.WARNING). Defaults to logging.INFO.

INFO
force bool

When True, removes any existing handlers before applying the new configuration, ensuring this call always takes effect. Defaults to False.

False
**kwargs Any

Additional keyword arguments passed to logging.basicConfig (e.g. stream, filename, datefmt).

{}

Raises:

Type Description
TypeError

If kwargs contains an option that logging.basicConfig does not accept.

ValueError

If kwargs contains incompatible options (for example handlers with stream), or if level is not a valid level.

Example
>>> import logging
>>> from coola.display.colorlog import configure_colorlog_logging
>>> configure_colorlog_logging(level=logging.DEBUG, force=True)
>>> logging.getLogger().level
10

coola.display.rich

Common display utilities for rich.

coola.display.rich.configure_rich_logging

configure_rich_logging(
    level: int = INFO,
    *,
    fmt: str = "%(message)s",
    datefmt: str = "[%Y-%m-%d %H:%M:%S]",
    rich_tracebacks: bool = True,
    markup: bool = True,
    force: bool = False,
    console: Console | None = None,
    **kwargs: Any,
) -> None

Configure the root logger to use :class:rich.logging.RichHandler.

Sets up Python's standard :mod:logging module with a single :class:~rich.logging.RichHandler so that all log output is rendered through Rich's console, with coloured levels, timestamps, and optional pretty-printed tracebacks.

After calling this function, obtain a logger for the current module with the standard idiom::

logger = logging.getLogger(__name__)

Using :func:logging.getLogger with __name__ gives the logger a dotted name matching its import path (e.g. my_package.my_module), which slots it correctly into the logging hierarchy and allows callers to tune or silence it by name.

Note

:func:logging.basicConfig is a no-op if the root logger already has handlers configured (e.g. if another library called basicConfig first, or if you call this function more than once). Pass force=True to remove any existing handlers and reconfigure unconditionally.

Parameters:

Name Type Description Default
level int

Minimum log level for the root logger. Accepts any constant from :mod:logging (e.g. logging.DEBUG, logging.WARNING). Defaults to logging.INFO.

INFO
fmt str

Log record format string passed to :class:logging.Formatter. Because Rich renders level, timestamp, and logger name itself, the default "%(message)s" avoids duplicating those fields. Change this if you need additional fields such as %(name)s in the formatted output.

'%(message)s'
datefmt str

Date/time format string for the %(asctime)s field, following :func:time.strftime conventions. Defaults to "[%Y-%m-%d %H:%M:%S]".

'[%Y-%m-%d %H:%M:%S]'
rich_tracebacks bool

When True, exceptions are rendered by Rich as syntax-highlighted, multi-frame tracebacks instead of the standard Python traceback format. Defaults to True.

True
markup bool

When True, Rich markup tags (e.g. [bold red]) in log messages are interpreted and rendered. Set to False if log messages may contain literal square brackets that should not be treated as markup. Defaults to True.

True
force bool

When True, any existing handlers on the root logger are removed before applying the new configuration, ensuring this call always takes effect. Equivalent to passing force=True to :func:logging.basicConfig (requires Python 3.8+). Defaults to False.

False
console Console | None

The :class:~rich.console.Console that renders the records. None (the default) uses the shared console of Rich (:func:rich.get_console).

None
**kwargs Any

Additional keyword arguments forwarded verbatim to :class:~rich.logging.RichHandler. For example::

configure_rich_logging(show_path=False, tracebacks_show_locals=True)

See the Rich documentation <https://rich.readthedocs.io/en/latest/logging.html>_ for the full list of supported parameters.

{}

Raises:

Type Description
MissingPackageError

If rich is not installed.

TypeError

If an unrecognised keyword argument is passed that :class:~rich.logging.RichHandler does not accept.

Example
>>> import logging
>>> from coola.display.rich import configure_rich_logging
>>> configure_rich_logging()
>>> logger = logging.getLogger(__name__)
>>> logger.info("Rich logging is active")

coola.display.rich.make_progressbar

make_progressbar(
    *,
    transient: bool = False,
    console: Console | None = None,
) -> Progress

Create a Rich progress bar with a standard column layout.

Builds a Progress instance with a consistent column layout: - A spinner indicating the task is active. - Description text. - A bar showing percentage complete. - The percentage as a number (e.g. '42%'). - An M-of-N counter (e.g. '42/100'). - Elapsed time since the task started. - Estimated time remaining.

Use the result as a context manager to start and stop rendering.

Parameters:

Name Type Description Default
transient bool

If True, the progress bar is cleared from the terminal when the context manager exits. Useful for short-lived tasks where the bar would clutter the output. Defaults to False.

False
console Console | None

The :class:~rich.console.Console to render to. None (the default) uses the shared console of Rich (:func:rich.get_console).

None

Returns:

Type Description
Progress

A configured Progress instance, ready to use as a context manager.

Raises:

Type Description
MissingPackageError

If rich is not installed.

Example
>>> from coola.display.rich import make_progressbar
>>> rows = list(range(10))
>>> with make_progressbar() as progress:
...     task = progress.add_task("Processing rows", total=len(rows))
...     for row in rows:
...         print(row)
...         progress.advance(task)
...

coola.display.rich.make_spinner

make_spinner(
    *,
    transient: bool = True,
    console: Console | None = None,
) -> Progress

Create a Rich spinner for tasks where the total number of items is unknown.

Builds a :class:~rich.progress.Progress instance with a column layout suited for indeterminate tasks:

  • A spinner indicating the task is active.
  • Description text.
  • An M-of-N counter showing items processed so far (e.g. 42/?).
  • Processing speed (e.g. 12.3/s).
  • Elapsed time since the task started.

Parameters:

Name Type Description Default
transient bool

If True, the spinner is cleared from the terminal when the context manager exits. Useful for short-lived tasks where the spinner would clutter the output. Defaults to True.

True
console Console | None

The :class:~rich.console.Console to render to. None (the default) uses the shared console of Rich (:func:rich.get_console).

None

Returns:

Type Description
Progress

A configured :class:~rich.progress.Progress instance ready to

Progress

use as a context manager.

Raises:

Type Description
MissingPackageError

If rich is not installed.

Example
>>> from coola.display.rich import make_spinner
>>> items = list(range(10))
>>> with make_spinner() as progress:
...     task = progress.add_task("Processing items...", total=None)
...     for item in items:
...         progress.advance(task)
...

coola.display.rich.print_markdown

print_markdown(
    msg: str,
    *,
    title: str | None = None,
    title_align: AlignMethod = "left",
    box: bool = True,
    panel: bool = True,
    max_length: int | None = None,
    console: Console | None = None,
) -> None

Render a Markdown string to the console, optionally inside a Rich panel.

Prints msg as rendered Markdown, wrapped in a :class:~rich.panel.Panel unless panel is False. Uses the provided console if given, otherwise falls back to the shared instance.

Parameters:

Name Type Description Default
msg str

The Markdown content to render. May contain any Markdown syntax supported by :class:~rich.markdown.Markdown. An empty string renders a blank panel.

required
title str | None

Optional title displayed on the panel border. Pass None (the default) to render a panel with no title. Ignored if panel is False.

None
title_align AlignMethod

Horizontal alignment of title along the panel border ("left", "center", or "right"). Defaults to "left". Ignored if panel is False.

'left'
box bool

If True (the default), render the panel with a visible rounded border. If False, render with a minimal (near-invisible) border. Ignored if panel is False.

True
panel bool

If True (the default), wrap the rendered Markdown in a :class:~rich.panel.Panel. If False, print it directly and ignore title, title_align, and box.

True
max_length int | None

Maximum number of characters of msg to render before truncating. The cut prefers a paragraph break, then a line break, then a space, closes a code fence left open, and appends a notice with the number of omitted characters. An inline marker (**) can be left unbalanced when the cut falls inside a paragraph. Pass None (the default) to render msg in full.

None
console Console | None

Optional :class:~rich.console.Console instance to use for this call. Overrides the shared default for this invocation only. Pass None (the default) to use the shared instance.

None

Raises:

Type Description
MissingPackageError

If rich is not installed.

Raises:

Type Description
MissingPackageError

If rich is not installed.

ValueError

If max_length is lower than 1.

Example
>>> from io import StringIO
>>> from rich.console import Console
>>> from coola.display.rich import print_markdown
>>> console = Console(file=StringIO(), width=20)
>>> print_markdown("**hello**", panel=False, console=console)
>>> console.file.getvalue().strip()
'hello'
>>> print_markdown("**hello**", title="Demo")
>>> print_markdown("**hello**", title="Demo", box=False)
>>> print_markdown("**hello**", panel=False)

coola.display.rich.print_pretty

print_pretty(
    data: Any,
    *,
    title: str | None = None,
    title_align: AlignMethod = "left",
    box: bool = True,
    panel: bool = True,
    console: Console | None = None,
) -> None

Render an arbitrary object to the console in a pretty format, optionally inside a Rich panel.

Prints data using :class:~rich.pretty.Pretty, wrapped in a :class:~rich.panel.Panel unless panel is False. Uses the provided console if given, otherwise falls back to the shared instance. Works with any Python object — dicts, lists, dataclasses, Pydantic models, and so on.

Parameters:

Name Type Description Default
data Any

The object to render. Passed directly to :class:~rich.pretty.Pretty, which handles formatting.

required
title str | None

Optional title displayed on the panel border. Pass None (the default) to render a panel with no title. Ignored if panel is False.

None
title_align AlignMethod

Horizontal alignment of title along the panel border ("left", "center", or "right"). Defaults to "left". Ignored if panel is False.

'left'
box bool

If True (the default), render the panel with a visible rounded border. If False, render with a minimal (near-invisible) border. Ignored if panel is False.

True
panel bool

If True (the default), wrap the rendered object in a :class:~rich.panel.Panel. If False, print it directly and ignore title, title_align, and box.

True
console Console | None

Optional :class:~rich.console.Console instance to use for this call. Overrides the shared default for this invocation only. Pass None (the default) to use the shared instance.

None

Raises:

Type Description
MissingPackageError

If rich is not installed.

Example
>>> from io import StringIO
>>> from rich.console import Console
>>> from coola.display.rich import print_pretty
>>> console = Console(file=StringIO(), width=20)
>>> print_pretty({"key": "value"}, panel=False, console=console)
>>> console.file.getvalue().strip()
"{'key': 'value'}"
>>> print_pretty({"key": "value"}, title="Demo")
>>> print_pretty({"key": "value"}, title="Demo", box=False)
>>> print_pretty({"key": "value"}, panel=False)