Source code for collective.transmute.layout

from collective.transmute import _types as t
from collective.transmute.utils import sort_data_by_value
from rich.layout import Layout
from rich.live import Live
from rich.panel import Panel
from rich.progress import BarColumn
from rich.progress import Progress
from rich.progress import SpinnerColumn
from rich.progress import TextColumn
from rich.table import Table






[docs] class TransmuteReport: """ Display a report panel with metadata information. Parameters ---------- data : dict[str, int] The data to display in the report. title : str The title of the report panel. limit : int, optional Maximum length for item names (default: 30). Example ------- .. code-block:: pycon >>> report = TransmuteReport({'TypeA': 10, 'TypeB': 5}, 'Exported') >>> panel = report.__rich__() """ limit: int
[docs] def __init__(self, data: dict[str, int], title: str, limit: int = 30): self.title = title self.data = data self.limit = limit
def __rich__(self) -> Panel: """ Render the report as a Rich Panel. Returns ------- Panel A Rich Panel object displaying the report. """ grid = Table.grid(expand=True) grid.add_column(justify="left", ratio=2) grid.add_column(justify="right", ratio=1) for name, total in sort_data_by_value(self.data): if len(name) > self.limit: idx = self.limit - 3 name = name[:idx] + "..." grid.add_row(name, f"{total}") return Panel(grid, title=self.title, border_style="green")
[docs] def progress_panel(progress: t.PipelineProgress | t.ReportProgress) -> Panel: """ Create a progress panel for the current pipeline or report progress. Parameters ---------- progress : PipelineProgress or ReportProgress The progress object to display. Returns ------- Panel A Rich Panel object showing progress. """ progress_table = Table.grid(expand=True) progress_table.add_row(progress.processed) if isinstance(progress, t.PipelineProgress): progress_table.add_row(progress.dropped) return Panel( progress_table, title="[b]Progress", border_style="green", )
[docs] def create_consoles() -> t.ConsoleArea: """ Create a ``ConsoleArea`` object with two console panels. Returns ------- ConsoleArea An object containing main and side consoles. Example ------- .. code-block:: pycon >>> consoles = create_consoles() """ main_console = t.ConsolePanel() side_console = t.ConsolePanel() return t.ConsoleArea(main_console, side_console)
[docs] class ApplicationLayout: """ Base layout for the application. Parameters ---------- title : str The title for the layout. Attributes ---------- title : str The layout title. layout : Layout The Rich Layout object. consoles : ConsoleArea The console area for logs and side info. progress : ReportProgress or PipelineProgress The progress bar object. Example ------- .. code-block:: pycon >>> layout = ApplicationLayout('My App') """ title: str layout: Layout consoles: t.ConsoleArea progress: t.ReportProgress | t.PipelineProgress
[docs] def __init__(self, title: str): self.consoles = create_consoles() self.layout = self._create_layout(title) self.title = title
[docs] def _create_layout(self, title: str) -> Layout: """ Create the Rich Layout for the application. Parameters ---------- title : str The title for the layout. Returns ------- Layout The Rich Layout object. """ return Layout(name="root")
[docs] def update_layout(self, state: t.PipelineState | t.ReportState) -> None: """ Update the layout with the current state. Parameters ---------- state : PipelineState or ReportState The state object containing progress and report data. """ pass
[docs] def initialize_progress(self, total: int) -> None: """ Initialize the progress bar. Parameters ---------- total : int The total number of items to process. """ pass
[docs] class TransmuteLayout(ApplicationLayout): """ Layout for the transmute pipeline application. Example ------- .. code-block:: pycon >>> layout = TransmuteLayout('Transmute') """
[docs] def _create_layout(self, title: str) -> Layout: """ Create the layout for the transmute pipeline. Parameters ---------- title : str The title for the layout. Returns ------- Layout The Rich Layout object. """ consoles = self.consoles layout = Layout(name="root") layout.split( Layout(name="header", ratio=1), Layout(name="main", ratio=5), Layout(name="footer", ratio=1), ) layout["main"].split_row( Layout(name="body", ratio=5, minimum_size=60), Layout(name="side", ratio=2), ) layout["header"].update(Header(title=title)) layout["body"].update( Panel(consoles.main, title="[b]Log", border_style="green"), ) layout["side"].update( Panel("", title="[b]Report", border_style="green"), ) layout["footer"].update( Panel("", title="[b]Progress", border_style="green"), ) return layout
[docs] def update_layout(self, state: t.PipelineState) -> None: """ Update the layout with the pipeline state. Parameters ---------- state : PipelineState The pipeline state object. """ layout = self.layout layout["footer"].update(progress_panel(state.progress)) grid = Table.grid(expand=True) grid.add_column(justify="left", ratio=1) grid.add_row(TransmuteReport(state.exported, "Transmuted")) grid.add_row(TransmuteReport(state.dropped, "Dropped")) layout["side"].update( Panel( grid, title="[b]Report", border_style="green", ), )
[docs] def initialize_progress(self, total: int) -> None: """ Initialize the progress bar for the pipeline. Parameters ---------- total : int The total number of items to process. """ processed = Progress( "{task.description}", SpinnerColumn(), BarColumn(), TextColumn( "[progress.percentage]{task.percentage:>3.0f}%[/progress.percentage] " "({task.completed}/{task.total})" ), expand=True, ) dropped = Progress( "{task.description}", SpinnerColumn(), TextColumn("{task.completed}"), ) processed_id = processed.add_task("[green]Processed", total=total) dropped_id = dropped.add_task("[red]Dropped") self.progress = t.PipelineProgress(processed, processed_id, dropped, dropped_id)
[docs] class ReportLayout(ApplicationLayout): """ Layout for displaying report information. Example ------- .. code-block:: pycon >>> layout = ReportLayout('Report') """
[docs] def _create_layout(self, title: str) -> Layout: """ Create the layout for the report. Parameters ---------- title : str The title for the layout. Returns ------- Layout The Rich Layout object. """ consoles = self.consoles layout = Layout(name="root") layout.split( Layout(name="header", ratio=1), Layout(name="main", ratio=4), Layout(name="body", ratio=2), Layout(name="footer", ratio=1), ) layout["header"].update(Header(title=title)) layout["body"].update( Panel(consoles.main, title="[b]Log", border_style="green"), ) layout["main"].update( Panel("", title="[b]Report", border_style="green"), ) layout["footer"].update( Panel("", title="[b]Progress", border_style="green"), ) return layout
[docs] def update_layout(self, state: t.ReportState) -> None: """ Update the layout with the report state. Parameters ---------- state : ReportState The report state object. """ layout = self.layout layout["footer"].update(progress_panel(state.progress)) grid = Table.grid(expand=True) columns = ("Types", "States", "Creators", "Subjects") for _ in columns: grid.add_column(justify="left", ratio=1) row = [] for name in columns: data = getattr(state, name.lower()) row.append(TransmuteReport(data, name, limit=30)) grid.add_row(*row) layout["main"].update( Panel( grid, title="[b]Report", border_style="green", ), )
[docs] def initialize_progress(self, total: int) -> None: """ Initialize the progress bar for the report. Parameters ---------- total : int The total number of items to process. """ processed = Progress( "{task.description}", SpinnerColumn(), BarColumn(), TextColumn( "[progress.percentage]{task.percentage:>3.0f}%[/progress.percentage] " "({task.completed}/{task.total})" ), expand=True, ) processed_id = processed.add_task("[green]Processed", total=total) self.progress = t.ReportProgress(processed, processed_id)
[docs] def live(app_layout: ApplicationLayout, redirect_stderr: bool = True) -> Live: """ Create a Rich Live instance for the given application layout. Parameters ---------- app_layout : ApplicationLayout The application layout to display. redirect_stderr : bool, optional Whether to redirect stderr to the live display (default: ``True``). Returns ------- Live A Rich Live instance for the layout. Example ------- .. code-block:: pycon >>> live_display = live(layout) """ return Live( app_layout.layout, refresh_per_second=10, screen=True, redirect_stderr=redirect_stderr, )