New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[REQUEST] Support for deleting/removing rows/columns from table #1331
Comments
The Table class (and all other Rich renderables) exist purely to draw things to the terminal. It leaves the responsibility for storing the row data up to the developer. Your original solution is absolutely fine. Store your data in a convenient format, and generate a Table when you want to print it. Think of it more like HTML. The table is the finished product you create from your data, but its not how you store the data. |
First. @willmcgugan. Rich is my favorite python library. Thank you for all your hard work keeping it up date! It does everything that I've always wanted a terminal to be able to do. (I've yet to delve into textual yet but soon!) I found a workaround for the above request for deleting rows. In my case, i wanted to send log messages to a separate part of the Live TUI while my algorithm runs in the background. Most attempts at this try to tail a log file over time, but as those logs grow, that feasibility decreases with reading massive logs every loop. So instead I built a Handler class to the logging routine that would dump any messages bound for logger into a temp list. Then when the templist gets to more than half the terminal height, I pop off the first item in the templist and redraw the table to get a rolling list of log values. Still cleaning up the code a bit but as an example the below works wonderfully! import logging
import numpy as np
import os
from pathlib import Path
from datetime import datetime
from time import sleep
from rich.layout import Layout
from rich.console import Console
from rich.logging import RichHandler
from rich.panel import Panel
from rich.live import Live
from rich.table import Table
from rich.progress import (
Progress,
BarColumn,
SpinnerColumn,
TextColumn,
TimeRemainingColumn,
MofNCompleteColumn,
TimeElapsedColumn,
)
def get_file_handler(log_dir: Path) -> logging:
log_format = "[%(asctime)s]-[%(funcName)s(%(lineno)d)]-[%(levelname)s]-[%(message)s]"
log_file = log_dir / "test.log"
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(logging.Formatter(log_format))
return file_handler
def get_logger(log_dir: Path, console: Console) -> logging:
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(get_file_handler(log_dir))
return logger
def make_layout() -> Layout:
layout = Layout(name="root")
layout.split(
Layout(name="header", size=3),
Layout(name="main")
)
layout["main"].split_row(
Layout(name="leftside"),
Layout(name="termoutput")
)
layout["leftside"].split_column(
Layout(name="stats", ratio=2),
Layout(name="progbar")
)
return layout
def get_stats() -> Table:
rand_arr = np.random.randint(low=2, high=10, size=(10, 3)) + np.random.random((10, 3))
stats_table = Table(
expand=True,
show_header=True,
header_style="bold",
title="[magenta][b]Hot Stats![/b]",
highlight=True,
)
stats_table.add_column("Column", justify="right")
stats_table.add_column("Mean", justify="center")
stats_table.add_column("Std", justify="center")
stats_table.add_column("Max", justify="center")
stats_table.add_column("Min", justify="center")
for col in range(rand_arr.shape[1]):
stats_table.add_row(
f"Col {col}",
f"{rand_arr[:, col].mean():.2f}",
f"{rand_arr[:, col].std():.2f}",
f"{rand_arr[:, col].max():.2f}",
f"{rand_arr[:, col].min():.2f}",
)
return stats_table
#Old code for reading a the last line of a log file.
# def get_last_log(logger) -> str:
# log_file_path = logger.handlers[0].baseFilename
# with open(log_file_path, "r") as file:
# log_entries = file.readlines()
# last_entry = log_entries[-1].strip() if log_entries else ""
# return last_entry
class make_header:
"""Display header with clock."""
def __rich__(self) -> Panel:
grid = Table.grid(expand=True)
grid.add_column(justify="center", ratio=1)
grid.add_column(justify="right")
grid.add_row(
"[b]Super[/b] cool application",
datetime.now().ctime().replace(":", "[blink]:[/]"),
)
return Panel(grid, style="red on black")
class MainTableHandler(logging.Handler):
def __init__(self, main_table: Table, layout: Layout, log_level: str):
super().__init__()
self.main_table = main_table
self.log_list = []
self.layout = layout
self.log_format = "%(asctime)s-(%(funcName)s)-%(lineno)d-%(levelname)s-[%(message)s]"
self.setLevel(log_level)
#Could set colors for levels here.
def emit(self, record):
record.asctime = record.asctime.split(",")[0]
#msg = self.format(record) #if you want just the message info switch comment lines
msg = self.log_format % record.__dict__
tsize = os.get_terminal_size().lines // 2
if len(self.log_list) > tsize:
self.log_list.append(msg)
self.log_list.pop(0)
self.main_table = redraw_main_table(self.log_list)
self.layout["termoutput"].update(Panel(self.main_table, border_style="red"))
else:
self.main_table.add_row(msg)
self.log_list.append(msg)
def redraw_main_table(temp_list: list) -> Table:
main_table = Table(
expand=True,
show_header=False,
header_style="bold",
title="[blue][b]Log Entries[/b]",
highlight=True,
)
main_table.add_column("Log Entries")
for row in temp_list:
main_table.add_row(row)
return main_table
def main():
console = Console(color_system="truecolor")
logger = get_logger(Path.cwd(), console=console)
main_table = Table(
expand=True,
show_header=False,
header_style="bold",
title="[blue][b]Log Messages[/b]",
highlight=True,
)
main_table.add_column("Log Output")
my_progress_bar = Progress(
SpinnerColumn(),
TextColumn("{task.description}"),
BarColumn(),
"time remain:",
TimeRemainingColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
)
my_counter = range(30)
my_task = my_progress_bar.add_task("The jobs", total=int(len(my_counter)))
progress_table = Table.grid(expand=True)
progress_table.add_row(
Panel(
my_progress_bar,
title="Super awesome progress bar",
border_style="green",
padding=(1, 1),
)
)
stats_table = get_stats()
layout = make_layout()
layout["header"].update(make_header())
layout["progbar"].update(Panel(progress_table, border_style="green"))
layout["termoutput"].update(Panel(main_table, border_style="blue"))
layout["stats"].update(Panel(stats_table, border_style="magenta"))
with Live(layout, refresh_per_second=10, screen=True) as live:
# Add MainTableHandler to logger
logger.addHandler(MainTableHandler(main_table, layout, logger.level))
for count in my_counter:
sleep(1)
logger.info(f"I made it to count {count}")
my_progress_bar.update(my_task, completed=count)
if count % 5 == 0:
logger.warning(f"Gettin some STATS")
stats_table = get_stats()
layout["stats"].update(Panel(stats_table, border_style="magenta"))
logger.critical("Stranger Danger")
live.refresh()
logger.info("Done logging.")
if __name__ == "__main__":
main() |
Hi! I'm trying to build a UI through the
Table
class, but I couldn't find a way to remove rows or columns from the table, only add. How I'm getting around it now is by just constantly re-building the table, aka:This works fine, but I wanted to use the
Live
class, but to do so, I believe there would have to be aremove_row
method for the table. I could use theupdate()
method, but I believe I would still have to store the rows in an external array (outside that of therows
array inside theTable
class). I would hoperemove_row
would help to reduce this need.How would you improve Rich?
I would hope that the remove methods for tables could help to add more flexibility to their generation. Two methods of implementation that I had in mind include:
What problem does it solve for you?
I believe implementing this function would allow me to remove the need to manage my table data in an external array that I know I can add to and delete from. This would allow me to keep the data inside the
Table
class and only use the class' methods to manipulate data.Did I help
If I was able to resolve your problem, consider sponsoring my work on Rich, or buy me a coffee to say thanks.
The text was updated successfully, but these errors were encountered: