Skip to content

Functions

craftable.get_table

Primary function called to generate a table string.

Parameters:

Name Type Description Default
value_rows Iterable[Iterable[Any]]

A collection of rows, where each row is a collection of values. This is required. However, a default message of "No data to display" will be shown if the collection is empty or None.

required
header_row Iterable[Any] | None

(optional) A collection of header column names. Defaults to None.

None
style TableStyle

(optional) A TableStyle object defining the table's appearance. Defaults to NoBorderScreenStyle.

NoBorderScreenStyle()
col_defs Iterable[str] | Iterable[ColDef] | ColDefList | None

(optional) A collection of column definitions to control width, alignment, etc. for data rows. Defaults to left aligned, auto-sized columns.

None
header_defs Iterable[str] | Iterable[ColDef] | ColDefList | None

(optional) A collection of column definitions for the header row. If not provided, defaults to center-aligned columns with the same widths as col_defs.

None
table_width int

(optional) Desired total width of the table. Will be automatically calculated if not specified. Defaults to 0.

0
lazy_end bool

(optional) If True, omits the right border of the table. Defaults to False.

False
separate_rows bool

(optional) If True, adds a separator line between each row. Defaults to False.

False
preprocessors PreprocessorCallbackList | None

(optional) List of per-column callbacks applied before formatting and sizing. Each entry should be a callable of the form fn(value, row, col_idx) -> value. Defaults to None.

None
postprocessors PostprocessorCallbackList | None

(optional) List of per-column callbacks applied after formatting, sizing, and wrapping. Each entry should be a callable of the form fn(original_value, text, row, col_idx) -> str. Defaults to None.

None
none_text str

(optional) Text to display for None values. Defaults to empty string.

''
Behavior
  • Validates that the style supports string output (raises ValueError if not).
  • If value_rows is empty, returns a table with "No data to display" message.
  • Converts value_rows to a list of lists for consistent processing.
  • Calculates column widths based on content and table_width constraints.
  • Generates column definitions (col_defs) from content if not provided.
  • Generates header definitions (header_defs) from col_defs if not provided, defaulting to center-aligned headers.
  • Renders the table header if header_row is provided or style.force_header is True.
  • Renders each data row, optionally adding separator lines between rows if separate_rows is True.
  • Renders the bottom border if the style includes one.
  • Applies preprocessors before formatting and postprocessors after formatting.

Returns:

Type Description
str

A formatted string representing the complete table, including all

str

borders, header, data rows, and separators as defined by the style.

Source code in src/craftable/craftable.py
def get_table(
    value_rows: Iterable[Iterable[Any]],
    header_row: Iterable[Any] | None = None,
    style: TableStyle = NoBorderScreenStyle(),
    col_defs: Iterable[str] | Iterable[ColDef] | ColDefList | None = None,
    header_defs: Iterable[str] | Iterable[ColDef] | ColDefList | None = None,
    table_width: int = 0,
    lazy_end: bool = False,
    separate_rows: bool = False,
    preprocessors: PreprocessorCallbackList | None = None,
    postprocessors: PostprocessorCallbackList | None = None,
    none_text: str = "",
) -> str:
    """
    Primary function called to generate a table string.

    Parameters:
        value_rows:
            A collection of rows, where each row is a collection of values. This
            is required. However, a default message of "No data to display" will
            be shown if the collection is empty or None.
        header_row:
            (optional) A collection of header column names. Defaults to None.
        style:
            (optional) A TableStyle object defining the table's appearance.
            Defaults to NoBorderScreenStyle.
        col_defs:
            (optional) A collection of column definitions to control width,
            alignment, etc. for data rows. Defaults to left aligned, auto-sized
            columns.
        header_defs:
            (optional) A collection of column definitions for the header row. If
            not provided, defaults to center-aligned columns with the same
            widths as col_defs.
        table_width:
            (optional) Desired total width of the table. Will be automatically
            calculated if not specified. Defaults to 0.
        lazy_end:
            (optional) If True, omits the right border of the table. Defaults to
            False.
        separate_rows:
            (optional) If True, adds a separator line between each row. Defaults
            to False.
        preprocessors:
            (optional) List of per-column callbacks applied before formatting
            and sizing. Each entry should be a callable of the form
            ``fn(value, row, col_idx) -> value``. Defaults to None.
        postprocessors:
            (optional) List of per-column callbacks applied after formatting,
            sizing, and wrapping. Each entry should be a callable of the form
            ``fn(original_value, text, row, col_idx) -> str``. Defaults to None.
        none_text:
            (optional) Text to display for None values. Defaults to empty
            string.

    Behavior:
        - Validates that the style supports string output (raises ValueError if
          not).
        - If value_rows is empty, returns a table with "No data to display"
          message.
        - Converts value_rows to a list of lists for consistent processing.
        - Calculates column widths based on content and table_width constraints.
        - Generates column definitions (col_defs) from content if not provided.
        - Generates header definitions (header_defs) from col_defs if not
          provided, defaulting to center-aligned headers.
        - Renders the table header if header_row is provided or
          style.force_header is True.
        - Renders each data row, optionally adding separator lines between rows
          if separate_rows is True.
        - Renders the bottom border if the style includes one.
        - Applies preprocessors before formatting and postprocessors after
          formatting.

    Returns:
        A formatted string representing the complete table, including all
        borders, header, data rows, and separators as defined by the style.
    """
    # Validate that style supports string output for get_table
    if not getattr(style, "string_output", True):
        raise ValueError(
            f"{style.__class__.__name__} does not support string output. "
            "Use export_table() instead."
        )

    if not value_rows:
        return get_table(
            [["No data to display"]],
            style=style,
            table_width=table_width,
            lazy_end=lazy_end,
        )

    if not table_width and style.terminal_style:
        table_width = get_term_width()

    padding_width = 2 * style.cell_padding

    _value_rows, _header_row, _col_defs, _header_defs, max_cols = _copy_and_normalize(
        value_rows,
        header_row,
        col_defs,
        header_defs,
    )

    # createa a second (shallow) copy to help with calculating column widths
    all_rows = _value_rows.copy()
    if _header_row:
        all_rows.insert(0, _header_row)

    _col_defs = _get_adjusted_col_defs(
        all_rows=all_rows,
        style=style,
        col_defs=_col_defs,
        table_width=table_width,
        preprocessors=preprocessors,
        postprocessors=postprocessors,
        none_text=none_text,
    )

    if not header_row and style.force_header:
        _header_row = [""] * max_cols

    _header_defs = _generate_header_defs(_header_row, _header_defs, _col_defs)

    # Delegate to a custom renderer when the style provides one (e.g., HTML/LaTeX)
    renderer = getattr(style, "render_table", None)
    if callable(renderer):
        return str(renderer(_value_rows, _header_row, _col_defs, _header_defs))

    # Generate header and rows
    output_rows = []
    if _header_row:
        row = get_table_header(
            header_cols=_header_row,
            style=style,
            header_defs=_header_defs,
            col_defs=_col_defs,
            table_width=table_width,
            lazy_end=lazy_end,
        )
        output_rows.append(row)
    else:
        if style.top_border:
            line = str(style.no_header_top_line)
            delim = str(style.no_header_top_delimiter)
            left = str(style.no_header_top_left)
            right = line if lazy_end else str(style.no_header_top_right)
            border_lines = [line * (col.width + padding_width) for col in _col_defs]
            border = delim.join(border_lines)
            border = left + border + right
            output_rows.append(border)

    # Add Value Rows
    rowcount = 0
    for values in _value_rows:
        rowcount += 1
        lastrow = rowcount == len(_value_rows)
        row = _get_table_row(
            values=values,
            style=style,
            col_defs=_col_defs,
            table_width=table_width,
            lazy_end=lazy_end,
        )
        output_rows.append(row)

        # Optionally add Separators Between Rows
        if not lastrow and separate_rows and style.row_separator_line:
            line = str(style.row_separator_line)
            left = str(style.row_separator_left)
            right = line if lazy_end else str(style.row_separator_right)
            delim = str(style.row_separator_delimiter)
            sep_lines = [line * (col.width + padding_width) for col in _col_defs]
            separator = delim.join(sep_lines)
            separator = left + separator + right
            output_rows.append(separator)

    # Add Bottom Border
    if style.bottom_border:
        line = str(style.values_bottom_line)
        delim = str(style.values_bottom_delimiter)
        left = str(style.values_bottom_left)
        right = line if lazy_end else str(style.values_bottom_right)
        border_lines = [line * (col.width + padding_width) for col in _col_defs]
        border = delim.join(border_lines)
        border = left + border + right
        output_rows.append(border)

    return "\n".join(output_rows)

craftable.export_table

Render the table and optionally write it to a file.

Parameters:

Name Type Description Default
value_rows Iterable[Iterable[Any]]

A collection of rows, where each row is a collection of values.

required
header_row Iterable[Any] | None

(optional) A collection of header column names. Defaults to None.

None
style TableStyle

(optional) A TableStyle object defining the table's appearance and export format. Defaults to NoBorderScreenStyle.

NoBorderScreenStyle()
col_defs Iterable[str] | Iterable[ColDef] | ColDefList | None

(optional) A collection of column definitions to control width, alignment, formatting, etc. for data rows. Defaults to left aligned, auto-sized columns.

None
header_defs Iterable[str] | Iterable[ColDef] | ColDefList | None

(optional) A collection of column definitions for the header row. If not provided, defaults to center-aligned columns with the same widths as col_defs. Used to control header alignment in exported files.

None
preprocessors PreprocessorCallbackList | None

(optional) List of per-column callbacks applied before formatting and sizing. Each entry should be a callable of the form fn(value, row, col_idx) -> value. Defaults to None.

None
postprocessors PostprocessorCallbackList | None

(optional) List of per-column callbacks applied after formatting, sizing, and wrapping. Each entry should be a callable of the form fn(original_value, text, row, col_idx) -> str. Defaults to None.

None
none_text str

(optional) Text to display for None values. Defaults to empty string.

''
file str | Path | IO[str] | IO[bytes] | None

(optional) File path (str or Path) or file-like object (IO) to write the output. If None, returns the rendered content as a string. Defaults to None.

None
encoding str

(optional) Character encoding for text output. Only used when writing text content to a file path. Defaults to "utf-8".

'utf-8'
Behavior
  • If the style provides a write_table(..., file) method and a file is given, delegate writing to the style and return the provided file path/handle.
  • Else, if the style provides render_table(...), render to a string (or bytes). If a file is provided, write it; otherwise return the rendered content.
  • Else, fall back to craftable's text renderer via get_table().

Returns:

Type Description
str | Path | None
  • If file is provided as a path (str or Path), returns the file path for convenience.
str | Path | None
  • If file is a file-like object, returns None after writing.
str | Path | None
  • Otherwise, returns the rendered content as a string.
Source code in src/craftable/craftable.py
def export_table(
    value_rows: Iterable[Iterable[Any]],
    header_row: Iterable[Any] | None = None,
    style: TableStyle = NoBorderScreenStyle(),
    col_defs: Iterable[str] | Iterable[ColDef] | ColDefList | None = None,
    header_defs: Iterable[str] | Iterable[ColDef] | ColDefList | None = None,
    preprocessors: PreprocessorCallbackList | None = None,
    postprocessors: PostprocessorCallbackList | None = None,
    none_text: str = "",
    file: str | Path | IO[str] | IO[bytes] | None = None,
    encoding: str = "utf-8",
) -> str | Path | None:
    """
    Render the table and optionally write it to a file.

    Parameters:
        value_rows:
            A collection of rows, where each row is a collection of values.
        header_row:
            (optional) A collection of header column names. Defaults to None.
        style:
            (optional) A TableStyle object defining the table's appearance and
            export format. Defaults to NoBorderScreenStyle.
        col_defs:
            (optional) A collection of column definitions to control width,
            alignment, formatting, etc. for data rows. Defaults to left aligned,
            auto-sized columns.
        header_defs:
            (optional) A collection of column definitions for the header row. If
            not provided, defaults to center-aligned columns with the same
            widths as col_defs. Used to control header alignment in exported
            files.
        preprocessors:
            (optional) List of per-column callbacks applied before formatting
            and sizing. Each entry should be a callable of the form
            ``fn(value, row, col_idx) -> value``. Defaults to None.
        postprocessors:
            (optional) List of per-column callbacks applied after formatting,
            sizing, and wrapping. Each entry should be a callable of the form
            ``fn(original_value, text, row, col_idx) -> str``. Defaults to None.
        none_text:
            (optional) Text to display for None values. Defaults to empty
            string.
        file:
            (optional) File path (str or Path) or file-like object (IO) to write
            the output. If None, returns the rendered content as a string.
            Defaults to None.
        encoding:
            (optional) Character encoding for text output. Only used when
            writing text content to a file path. Defaults to "utf-8".

    Behavior:
        - If the style provides a write_table(..., file) method and a file is given,
          delegate writing to the style and return the provided file path/handle.
        - Else, if the style provides render_table(...), render to a string (or bytes).
          If a file is provided, write it; otherwise return the rendered content.
        - Else, fall back to craftable's text renderer via get_table().

    Returns:
        - If file is provided as a path (str or Path), returns the file path for convenience.
        - If file is a file-like object, returns None after writing.
        - Otherwise, returns the rendered content as a string.
    """

    _value_rows, _header_row, _col_defs, _header_defs, max_cols = _copy_and_normalize(
        value_rows,
        header_row,
        col_defs,
        header_defs,
    )

    all_rows = _value_rows.copy()
    if _header_row:
        all_rows.insert(0, _header_row)

    _col_defs = _get_adjusted_col_defs(
        all_rows=all_rows,
        style=style,
        col_defs=col_defs,
        preprocessors=preprocessors,
        postprocessors=postprocessors,
        none_text=none_text,
    )

    max_cols = max(len(row) for row in all_rows)

    if not header_row and style.force_header:
        header_row = [""] * max_cols

    _header_defs = _generate_header_defs(_header_row, _header_defs, _col_defs)

    # Prefer explicit writer when available
    writer = getattr(style, "write_table", None)
    if callable(writer) and file is not None:
        writer(_value_rows, _header_row, _col_defs, _header_defs, file)
        return Path(file) if isinstance(file, (str, Path)) else None

    # Otherwise render content and optionally write
    renderer = getattr(style, "render_table", None)
    if callable(renderer):
        content = renderer(_value_rows, _header_row, _col_defs)
        if file is None:
            return str(content)
        # Decide binary vs text write
        if isinstance(content, (bytes, bytearray)):
            if isinstance(file, (str, Path)):
                with open(file, "wb") as f:
                    f.write(content)
                return Path(file)
            else:
                # Assume binary-capable IO
                file.write(content)  # type: ignore[arg-type]
                return None
        else:
            # Text content
            if isinstance(file, (str, Path)):
                with open(file, "w", encoding=encoding) as f:
                    f.write(str(content))
                return Path(file)
            else:
                # Assume text-capable IO
                file.write(str(content))  # type: ignore[arg-type]
                return None

    # Fallback to core text renderer
    content = get_table(
        _value_rows,
        header_row=_header_row,
        style=style,
        col_defs=_col_defs,
        header_defs=_header_defs,
        preprocessors=preprocessors,
        postprocessors=postprocessors,
        none_text=none_text,
    )
    if file is None:
        return content
    if isinstance(file, (str, Path)):
        with open(file, "w", encoding=encoding) as f:
            f.write(content)
        return Path(file)
    else:
        file.write(content)  # type: ignore[arg-type]
        return None

craftable.get_table_row

Generate a string for a single table row.

Parameters:

Name Type Description Default
values list[Any]

A list of values.

required
style TableStyle

(optional) A TableStyle object defining the table's appearance. Defaults to NoBorderScreenStyle.

NoBorderScreenStyle()
col_defs list[str] | list[ColDef] | ColDefList | None

(optional) Column definitions to control width, alignment, etc. If not provided, generates default left-aligned columns based on values.

None
table_width int

(optional) Desired total width of the table. If 0, uses terminal width when style.terminal_style is True. Defaults to 0.

0
lazy_end bool

(optional) If True, omits the right border of the table. Defaults to True.

True
preprocessors PreprocessorCallbackList | None

(optional) List of per-column callbacks applied before formatting and sizing. Each entry should be a callable of the form fn(value, row, col_idx) -> value. Defaults to None.

None
postprocessors PostprocessorCallbackList | None

(optional) List of per-column callbacks applied after formatting, sizing, and wrapping. Each entry should be a callable of the form fn(original_value, text, row, col_idx) -> str. Defaults to None.

None
none_text str

(optional) Text to display for None values. Defaults to empty string.

''
Behavior
  • If col_defs is not provided, generates default left-aligned column definitions based on the values.
  • Applies preprocessors to transform values before formatting.
  • Formats each value according to its column definition.
  • Applies postprocessors to transform formatted text.
  • Renders the row using the specified table style.

Returns:

Type Description
str

A formatted string representing a single table row, including borders

str

and cell padding as defined by the style.

Source code in src/craftable/craftable.py
def get_table_row(
    values: list[Any],
    style: TableStyle = NoBorderScreenStyle(),
    col_defs: list[str] | list[ColDef] | ColDefList | None = None,
    table_width: int = 0,
    lazy_end: bool = True,
    preprocessors: PreprocessorCallbackList | None = None,
    postprocessors: PostprocessorCallbackList | None = None,
    none_text: str = "",
) -> str:
    """
    Generate a string for a single table row.

    Parameters:
        values:
            A list of values.
        style:
            (optional) A TableStyle object defining the table's appearance.
            Defaults to NoBorderScreenStyle.
        col_defs:
            (optional) Column definitions to control width, alignment, etc. If
            not provided, generates default left-aligned columns based on values.
        table_width:
            (optional) Desired total width of the table. If 0, uses terminal
            width when style.terminal_style is True. Defaults to 0.
        lazy_end:
            (optional) If True, omits the right border of the table. Defaults
            to True.
        preprocessors:
            (optional) List of per-column callbacks applied before formatting
            and sizing. Each entry should be a callable of the form
            ``fn(value, row, col_idx) -> value``. Defaults to None.
        postprocessors:
            (optional) List of per-column callbacks applied after formatting,
            sizing, and wrapping. Each entry should be a callable of the form
            ``fn(original_value, text, row, col_idx) -> str``. Defaults to None.
        none_text:
            (optional) Text to display for None values. Defaults to empty
            string.

    Behavior:
        - If col_defs is not provided, generates default left-aligned column
          definitions based on the values.
        - Applies preprocessors to transform values before formatting.
        - Formats each value according to its column definition.
        - Applies postprocessors to transform formatted text.
        - Renders the row using the specified table style.

    Returns:
        A formatted string representing a single table row, including borders
        and cell padding as defined by the style.
    """

    _col_defs = _get_adjusted_col_defs(
        all_rows=[values],
        style=style,
        col_defs=col_defs,
        table_width=table_width,
        preprocessors=preprocessors,
        postprocessors=postprocessors,
        none_text=none_text,
    )
    return _get_table_row(
        values=values,
        style=style,
        col_defs=_col_defs,
        table_width=table_width,
        lazy_end=lazy_end,
        is_header=False,
    )

craftable.get_table_header

Generate a string for the header of a table.

Parameters:

Name Type Description Default
header_cols list[str]

A list of header column names.

required
style TableStyle

(optional) A TableStyle object defining the table's appearance. Defaults to NoBorderScreenStyle.

NoBorderScreenStyle()
header_defs Iterable[str] | Iterable[ColDef] | ColDefList | None

(optional) Column definitions for the header row. If not provided, defaults to column definitions generated from header_cols.

None
col_defs Iterable[str] | Iterable[ColDef] | ColDefList | None

(optional) Column definitions for the rest of the table rows. Only required for styles that have an alignment character (e.g., Markdown). If not provided, uses the same as header_defs.

None
table_width int

(optional) Desired total width of the table. Will be automatically calculated if not provided. Defaults to 0.

0
lazy_end bool

(optional) If True, omits the right border of the table. Defaults to True.

True
Behavior
  • If header_defs is not provided, generates default column definitions from header_cols.
  • If col_defs is not provided, uses a copy of header_defs.
  • Adjusts column definitions to fit the specified or calculated table width.
  • Renders the top border if the style includes one.
  • Renders the header row with the specified header definitions.
  • Renders the bottom border (separator between header and data rows).
  • For styles with alignment characters (e.g., Markdown), uses col_defs to determine column alignment indicators in the separator line.

Returns:

Type Description
str

A formatted string representing the table header, including top border,

str

header row, and header separator line as defined by the style.

Source code in src/craftable/craftable.py
def get_table_header(
    header_cols: list[str],
    style: TableStyle = NoBorderScreenStyle(),
    header_defs: Iterable[str] | Iterable[ColDef] | ColDefList | None = None,
    col_defs: Iterable[str] | Iterable[ColDef] | ColDefList | None = None,
    table_width: int = 0,
    lazy_end: bool = True,
) -> str:
    """
    Generate a string for the header of a table.

    Parameters:
        header_cols:
            A list of header column names.
        style:
            (optional) A TableStyle object defining the table's appearance.
            Defaults to NoBorderScreenStyle.
        header_defs:
            (optional) Column definitions for the header row. If not provided,
            defaults to column definitions generated from header_cols.
        col_defs:
            (optional) Column definitions for the rest of the table rows. Only
            required for styles that have an alignment character (e.g.,
            Markdown). If not provided, uses the same as header_defs.
        table_width:
            (optional) Desired total width of the table. Will be automatically
            calculated if not provided. Defaults to 0.
        lazy_end:
            (optional) If True, omits the right border of the table. Defaults
            to True.

    Behavior:
        - If header_defs is not provided, generates default column definitions from header_cols.
        - If col_defs is not provided, uses a copy of header_defs.
        - Adjusts column definitions to fit the specified or calculated table width.
        - Renders the top border if the style includes one.
        - Renders the header row with the specified header definitions.
        - Renders the bottom border (separator between header and data rows).
        - For styles with alignment characters (e.g., Markdown), uses col_defs to determine
          column alignment indicators in the separator line.

    Returns:
        A formatted string representing the table header, including top border,
        header row, and header separator line as defined by the style.
    """
    if not table_width and style.terminal_style:
        table_width = get_term_width()

    _header_defs = _get_adjusted_col_defs(
        all_rows=[header_cols],
        style=style,
        col_defs=header_defs,
        table_width=table_width,
    )

    if not col_defs:
        _col_defs = _header_defs.copy()
    elif isinstance(col_defs, ColDefList):
        _col_defs = col_defs
    else:
        _col_defs = ColDefList(col_defs)

    lazy_end = lazy_end and style.allow_lazy_header
    padding_width = 2 * style.cell_padding

    lines = []
    if style.top_border:
        line = str(style.header_top_line)
        delim = str(style.header_top_delimiter)
        left = str(style.header_top_left)
        right = line if lazy_end else str(style.header_top_right)
        border_lines = [line * (col.width + padding_width) for col in _col_defs]
        border = delim.join(border_lines)
        border = left + border + right
        lines.append(border)

    headers = _get_table_row(
        values=header_cols,
        style=style,
        col_defs=_header_defs,
        table_width=table_width,
        lazy_end=lazy_end,
        is_header=True,
    )
    lines.append(headers)

    line = str(style.header_bottom_line)
    delim = str(style.header_bottom_delimiter)
    left = str(style.header_bottom_left)
    right = line if lazy_end else str(style.header_bottom_right)
    border_lines = []
    for col_idx, _ in enumerate(header_cols):
        header_def = _header_defs[col_idx]
        if not style.align_char:
            h_line = line * (header_def.width + padding_width)
        else:
            col_def = None
            if col_idx < len(_col_defs):
                col_def = _col_defs[col_idx]
            h_line = line * header_def.width
            if col_def and col_def.align == "^":
                h_line = str(style.align_char) + h_line + str(style.align_char)
            elif col_def and col_def.align == ">":
                h_line = " " + h_line + str(style.align_char)
            else:
                h_line = " " + h_line + " "
        border_lines.append(h_line)
    border = delim.join(border_lines)
    border = left + border + right
    lines.append(border)

    return "\n".join(lines)