Skip to content

harbor_cli.output.table

Module for generating compact tables and panels for output.

What is a compact table?

NOTE: For brevity's sake, "compact table" refers to both rich.table.Table and rich.panel.Panel objects in this documentation.

A compact table is a table that displays one or more models in a custom-made table for that specific model. With compact tables, multiple model instances can be displayed in the same table, unlike the auto-generated tables from harborapi which only display one model instance at a time. This is useful for aggregating information from a list of multiple model instances and presenting them in a single table, or for displaying information from a single model that contains multiple submodels in a single table or panel.

A compact table is generated by a render function, which is a function that takes in a harborapi model instance or list of instances and returns a rich.table.Table object or a rich.panel.Panel object, both of which can be passed to a Rich console and be printed.

How it works

There is a lot magic and metaprogramming (same thing) going on in this module. We use the type annotations of render functions to determine which model goes to which render function. This is done by populating a dictionary with the type annotations of the render functions, and then looking up the type of the object we want to render in that dictionary. If the type is found, we return the render function. If not, we raise a NotImplementedError.

In cases where we receive a single model instance, and we only have a render function for a sequence of that model type, we try to wrap the model in a list and pass it to the sequence render function. If we have a render function for both sequences and non-sequences, we will always prioritize the non-sequence function when a single model instance is passed in. However, lists with a single item will always use a sequence function if available.

Attributes

T = TypeVar('T') module-attribute

RenderableType = Union[Table, Panel] module-attribute

RenderFuncSeq = Callable[..., RenderableType] module-attribute

RenderFuncSingle = Callable[..., RenderableType] module-attribute

RenderFuncType = Union[RenderFuncSeq, RenderFuncSingle] module-attribute

RENDER_FUNCTIONS: Dict[Any, RenderFuncType] = {} module-attribute

Classes

BuiltinTypeException

Bases: TypeError

Source code in harbor_cli/output/table/__init__.py
class BuiltinTypeException(TypeError):
    pass

EmptySequenceError

Bases: ValueError

Source code in harbor_cli/output/table/__init__.py
class EmptySequenceError(ValueError):
    pass

Functions

get_render_function(obj: Any) -> RenderFuncType

Get the render function for a given object.

If the object is a sequence, only render functions that take in sequences are returned.

If the object is not a sequence, render functions that take in a a single object are prioritized, but if none are found, a sequence render func is returned.

The caller of this function must discern whether or not a sequence function has been returned, and if so, wrap the object in a sequence if it is not a sequence. In scenarios where non-metaprogrammy code calls this function, this should not be an issue, since the caller should know if they passed in a sequence or not.

Parameters:

Name Type Description Default
obj Any

The object to get the render function for.

required

Returns:

Type Description
RenderFuncType

The render function for the object. A render function is a function that takes in a BaseModel or a list of BaseModels and returns a rich.table.Table or rich.panel.Panel object.

See Also
Source code in harbor_cli/output/table/__init__.py
def get_render_function(obj: Any) -> RenderFuncType:
    """Get the render function for a given object.

    If the object is a sequence, only render functions that take in
    sequences are returned.

    If the object is not a sequence, render functions that take in a
    a single object are prioritized, but if none are found, a sequence
    render func is returned.

    The caller of this function must discern whether or not a sequence function
    has been returned, and if so, wrap the object in a sequence if it is
    not a sequence. In scenarios where non-metaprogrammy code calls this
    function, this should not be an issue, since the caller should know if they
    passed in a sequence or not.

    Parameters
    ----------
    obj : Any
        The object to get the render function for.

    Returns
    -------
    RenderFuncType
        The render function for the object. A render function is a function
        that takes in a BaseModel or a list of BaseModels and returns a
        rich.table.Table or rich.panel.Panel object.

    See Also
    --------
    * [harbor_cli.output.table.get_renderable][]
    * [harbor_cli.types.is_sequence_func][]
    * [harbor_cli.output.render.render_table_compact][]
    """
    if isinstance(obj, Sequence) and not isinstance(obj, str):
        if len(obj) == 0:  # type: ignore # type of items is irrelevant
            raise EmptySequenceError("Cannot render empty sequence.")
        t = Sequence[type(obj[0])]  # type: ignore # TODO: find a way to type this
    else:
        t = type(obj)  # type: ignore # type of obj is irrelevant

    def _get_render_func(t: Any) -> RenderFuncType:
        try:
            return RENDER_FUNCTIONS[t]
        except KeyError:
            # FIXME: handle list of builtins
            if is_builtin_obj(t):
                raise BuiltinTypeException(
                    "Builtin types cannot be rendered as a compact table."
                )
            raise NotImplementedError(f"{t} not implemented.")

    # try to get the single obj render func
    try:
        return _get_render_func(t)
    # fall back on the sequence render func
    except NotImplementedError:
        return _get_render_func(Sequence[t])

get_renderable(obj: Any, **kwargs: Any) -> Table | Panel

Get the renderable for a given object.

Source code in harbor_cli/output/table/__init__.py
def get_renderable(obj: Any, **kwargs: Any) -> Table | Panel:
    """Get the renderable for a given object."""
    # TODO: add typeguard here to only allow BaseModel or list of BaseModel
    render_function = get_render_function(obj)
    # wrap object in sequence if necessary (use sequence func if we cant find a single func)
    if is_sequence_func(render_function) and not isinstance(obj, Sequence):
        return render_function([obj], **kwargs)
    return render_function(obj, **kwargs)