Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/table.py: 72%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from dataclasses import dataclass, field, replace
2from typing import (
3 TYPE_CHECKING,
4 Dict,
5 Iterable,
6 List,
7 NamedTuple,
8 Optional,
9 Sequence,
10 Tuple,
11 Union,
12)
14from . import box, errors
15from ._loop import loop_first_last, loop_last
16from ._pick import pick_bool
17from ._ratio import ratio_distribute, ratio_reduce
18from .align import VerticalAlignMethod
19from .jupyter import JupyterMixin
20from .measure import Measurement
21from .padding import Padding, PaddingDimensions
22from .protocol import is_renderable
23from .segment import Segment
24from .style import Style, StyleType
25from .text import Text, TextType
27if TYPE_CHECKING:
28 from .console import (
29 Console,
30 ConsoleOptions,
31 JustifyMethod,
32 OverflowMethod,
33 RenderableType,
34 RenderResult,
35 )
38@dataclass
39class Column:
40 """Defines a column within a ~Table.
42 Args:
43 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
44 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
45 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
46 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
47 box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
48 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
49 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
50 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
51 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
52 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
53 show_header (bool, optional): Show a header row. Defaults to True.
54 show_footer (bool, optional): Show a footer row. Defaults to False.
55 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
56 show_lines (bool, optional): Draw lines between every row. Defaults to False.
57 leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
58 style (Union[str, Style], optional): Default style for the table. Defaults to "none".
59 row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
60 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
61 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
62 border_style (Union[str, Style], optional): Style of the border. Defaults to None.
63 title_style (Union[str, Style], optional): Style of the title. Defaults to None.
64 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
65 title_justify (str, optional): Justify method for title. Defaults to "center".
66 caption_justify (str, optional): Justify method for caption. Defaults to "center".
67 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
68 """
70 header: "RenderableType" = ""
71 """RenderableType: Renderable for the header (typically a string)"""
73 footer: "RenderableType" = ""
74 """RenderableType: Renderable for the footer (typically a string)"""
76 header_style: StyleType = ""
77 """StyleType: The style of the header."""
79 footer_style: StyleType = ""
80 """StyleType: The style of the footer."""
82 style: StyleType = ""
83 """StyleType: The style of the column."""
85 justify: "JustifyMethod" = "left"
86 """str: How to justify text within the column ("left", "center", "right", or "full")"""
88 vertical: "VerticalAlignMethod" = "top"
89 """str: How to vertically align content ("top", "middle", or "bottom")"""
91 overflow: "OverflowMethod" = "ellipsis"
92 """str: Overflow method."""
94 width: Optional[int] = None
95 """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width."""
97 min_width: Optional[int] = None
98 """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None."""
100 max_width: Optional[int] = None
101 """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None."""
103 ratio: Optional[int] = None
104 """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents."""
106 no_wrap: bool = False
107 """bool: Prevent wrapping of text within the column. Defaults to ``False``."""
109 highlight: bool = False
110 """bool: Apply highlighter to column. Defaults to ``False``."""
112 _index: int = 0
113 """Index of column."""
115 _cells: List["RenderableType"] = field(default_factory=list)
117 def copy(self) -> "Column":
118 """Return a copy of this Column."""
119 return replace(self, _cells=[])
121 @property
122 def cells(self) -> Iterable["RenderableType"]:
123 """Get all cells in the column, not including header."""
124 yield from self._cells
126 @property
127 def flexible(self) -> bool:
128 """Check if this column is flexible."""
129 return self.ratio is not None
132@dataclass
133class Row:
134 """Information regarding a row."""
136 style: Optional[StyleType] = None
137 """Style to apply to row."""
139 end_section: bool = False
140 """Indicated end of section, which will force a line beneath the row."""
143class _Cell(NamedTuple):
144 """A single cell in a table."""
146 style: StyleType
147 """Style to apply to cell."""
148 renderable: "RenderableType"
149 """Cell renderable."""
150 vertical: VerticalAlignMethod
151 """Cell vertical alignment."""
154class Table(JupyterMixin):
155 """A console renderable to draw a table.
157 Args:
158 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
159 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
160 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
161 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
162 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
163 box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD.
164 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
165 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
166 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
167 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
168 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
169 show_header (bool, optional): Show a header row. Defaults to True.
170 show_footer (bool, optional): Show a footer row. Defaults to False.
171 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
172 show_lines (bool, optional): Draw lines between every row. Defaults to False.
173 leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
174 style (Union[str, Style], optional): Default style for the table. Defaults to "none".
175 row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None.
176 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
177 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
178 border_style (Union[str, Style], optional): Style of the border. Defaults to None.
179 title_style (Union[str, Style], optional): Style of the title. Defaults to None.
180 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
181 title_justify (str, optional): Justify method for title. Defaults to "center".
182 caption_justify (str, optional): Justify method for caption. Defaults to "center".
183 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
184 """
186 columns: List[Column]
187 rows: List[Row]
189 def __init__(
190 self,
191 *headers: Union[Column, str],
192 title: Optional[TextType] = None,
193 caption: Optional[TextType] = None,
194 width: Optional[int] = None,
195 min_width: Optional[int] = None,
196 box: Optional[box.Box] = box.HEAVY_HEAD,
197 safe_box: Optional[bool] = None,
198 padding: PaddingDimensions = (0, 1),
199 collapse_padding: bool = False,
200 pad_edge: bool = True,
201 expand: bool = False,
202 show_header: bool = True,
203 show_footer: bool = False,
204 show_edge: bool = True,
205 show_lines: bool = False,
206 leading: int = 0,
207 style: StyleType = "none",
208 row_styles: Optional[Iterable[StyleType]] = None,
209 header_style: Optional[StyleType] = "table.header",
210 footer_style: Optional[StyleType] = "table.footer",
211 border_style: Optional[StyleType] = None,
212 title_style: Optional[StyleType] = None,
213 caption_style: Optional[StyleType] = None,
214 title_justify: "JustifyMethod" = "center",
215 caption_justify: "JustifyMethod" = "center",
216 highlight: bool = False,
217 ) -> None:
218 self.columns: List[Column] = []
219 self.rows: List[Row] = []
220 self.title = title
221 self.caption = caption
222 self.width = width
223 self.min_width = min_width
224 self.box = box
225 self.safe_box = safe_box
226 self._padding = Padding.unpack(padding)
227 self.pad_edge = pad_edge
228 self._expand = expand
229 self.show_header = show_header
230 self.show_footer = show_footer
231 self.show_edge = show_edge
232 self.show_lines = show_lines
233 self.leading = leading
234 self.collapse_padding = collapse_padding
235 self.style = style
236 self.header_style = header_style or ""
237 self.footer_style = footer_style or ""
238 self.border_style = border_style
239 self.title_style = title_style
240 self.caption_style = caption_style
241 self.title_justify: "JustifyMethod" = title_justify
242 self.caption_justify: "JustifyMethod" = caption_justify
243 self.highlight = highlight
244 self.row_styles: Sequence[StyleType] = list(row_styles or [])
245 append_column = self.columns.append
246 for header in headers:
247 if isinstance(header, str):
248 self.add_column(header=header)
249 else:
250 header._index = len(self.columns)
251 append_column(header)
253 @classmethod
254 def grid(
255 cls,
256 *headers: Union[Column, str],
257 padding: PaddingDimensions = 0,
258 collapse_padding: bool = True,
259 pad_edge: bool = False,
260 expand: bool = False,
261 ) -> "Table":
262 """Get a table with no lines, headers, or footer.
264 Args:
265 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
266 padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.
267 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.
268 pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.
269 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
271 Returns:
272 Table: A table instance.
273 """
274 return cls(
275 *headers,
276 box=None,
277 padding=padding,
278 collapse_padding=collapse_padding,
279 show_header=False,
280 show_footer=False,
281 show_edge=False,
282 pad_edge=pad_edge,
283 expand=expand,
284 )
286 @property
287 def expand(self) -> bool:
288 """Setting a non-None self.width implies expand."""
289 return self._expand or self.width is not None
291 @expand.setter
292 def expand(self, expand: bool) -> None:
293 """Set expand."""
294 self._expand = expand
296 @property
297 def _extra_width(self) -> int:
298 """Get extra width to add to cell content."""
299 width = 0
300 if self.box and self.show_edge:
301 width += 2
302 if self.box:
303 width += len(self.columns) - 1
304 return width
306 @property
307 def row_count(self) -> int:
308 """Get the current number of rows."""
309 return len(self.rows)
311 def get_row_style(self, console: "Console", index: int) -> StyleType:
312 """Get the current row style."""
313 style = Style.null()
314 if self.row_styles:
315 style += console.get_style(self.row_styles[index % len(self.row_styles)])
316 row_style = self.rows[index].style
317 if row_style is not None:
318 style += console.get_style(row_style)
319 return style
321 def __rich_measure__(
322 self, console: "Console", options: "ConsoleOptions"
323 ) -> Measurement:
324 max_width = options.max_width
325 if self.width is not None:
326 max_width = self.width
327 if max_width < 0:
328 return Measurement(0, 0)
330 extra_width = self._extra_width
331 max_width = sum(
332 self._calculate_column_widths(
333 console, options.update_width(max_width - extra_width)
334 )
335 )
336 _measure_column = self._measure_column
338 measurements = [
339 _measure_column(console, options.update_width(max_width), column)
340 for column in self.columns
341 ]
342 minimum_width = (
343 sum(measurement.minimum for measurement in measurements) + extra_width
344 )
345 maximum_width = (
346 sum(measurement.maximum for measurement in measurements) + extra_width
347 if (self.width is None)
348 else self.width
349 )
350 measurement = Measurement(minimum_width, maximum_width)
351 measurement = measurement.clamp(self.min_width)
352 return measurement
354 @property
355 def padding(self) -> Tuple[int, int, int, int]:
356 """Get cell padding."""
357 return self._padding
359 @padding.setter
360 def padding(self, padding: PaddingDimensions) -> "Table":
361 """Set cell padding."""
362 self._padding = Padding.unpack(padding)
363 return self
365 def add_column(
366 self,
367 header: "RenderableType" = "",
368 footer: "RenderableType" = "",
369 *,
370 header_style: Optional[StyleType] = None,
371 highlight: Optional[bool] = None,
372 footer_style: Optional[StyleType] = None,
373 style: Optional[StyleType] = None,
374 justify: "JustifyMethod" = "left",
375 vertical: "VerticalAlignMethod" = "top",
376 overflow: "OverflowMethod" = "ellipsis",
377 width: Optional[int] = None,
378 min_width: Optional[int] = None,
379 max_width: Optional[int] = None,
380 ratio: Optional[int] = None,
381 no_wrap: bool = False,
382 ) -> None:
383 """Add a column to the table.
385 Args:
386 header (RenderableType, optional): Text or renderable for the header.
387 Defaults to "".
388 footer (RenderableType, optional): Text or renderable for the footer.
389 Defaults to "".
390 header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.
391 highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object.
392 footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
393 style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
394 justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
395 vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
396 overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
397 width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
398 min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
399 max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.
400 ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.
401 no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
402 """
404 column = Column(
405 _index=len(self.columns),
406 header=header,
407 footer=footer,
408 header_style=header_style or "",
409 highlight=highlight if highlight is not None else self.highlight,
410 footer_style=footer_style or "",
411 style=style or "",
412 justify=justify,
413 vertical=vertical,
414 overflow=overflow,
415 width=width,
416 min_width=min_width,
417 max_width=max_width,
418 ratio=ratio,
419 no_wrap=no_wrap,
420 )
421 self.columns.append(column)
423 def add_row(
424 self,
425 *renderables: Optional["RenderableType"],
426 style: Optional[StyleType] = None,
427 end_section: bool = False,
428 ) -> None:
429 """Add a row of renderables.
431 Args:
432 *renderables (None or renderable): Each cell in a row must be a renderable object (including str),
433 or ``None`` for a blank cell.
434 style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
435 end_section (bool, optional): End a section and draw a line. Defaults to False.
437 Raises:
438 errors.NotRenderableError: If you add something that can't be rendered.
439 """
441 def add_cell(column: Column, renderable: "RenderableType") -> None:
442 column._cells.append(renderable)
444 cell_renderables: List[Optional["RenderableType"]] = list(renderables)
446 columns = self.columns
447 if len(cell_renderables) < len(columns):
448 cell_renderables = [
449 *cell_renderables,
450 *[None] * (len(columns) - len(cell_renderables)),
451 ]
452 for index, renderable in enumerate(cell_renderables):
453 if index == len(columns):
454 column = Column(_index=index, highlight=self.highlight)
455 for _ in self.rows:
456 add_cell(column, Text(""))
457 self.columns.append(column)
458 else:
459 column = columns[index]
460 if renderable is None:
461 add_cell(column, "")
462 elif is_renderable(renderable):
463 add_cell(column, renderable)
464 else:
465 raise errors.NotRenderableError(
466 f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
467 )
468 self.rows.append(Row(style=style, end_section=end_section))
470 def add_section(self) -> None:
471 """Add a new section (draw a line after current row)."""
473 if self.rows:
474 self.rows[-1].end_section = True
476 def __rich_console__(
477 self, console: "Console", options: "ConsoleOptions"
478 ) -> "RenderResult":
479 if not self.columns:
480 yield Segment("\n")
481 return
483 max_width = options.max_width
484 if self.width is not None:
485 max_width = self.width
487 extra_width = self._extra_width
488 widths = self._calculate_column_widths(
489 console, options.update_width(max_width - extra_width)
490 )
491 table_width = sum(widths) + extra_width
493 render_options = options.update(
494 width=table_width, highlight=self.highlight, height=None
495 )
497 def render_annotation(
498 text: TextType, style: StyleType, justify: "JustifyMethod" = "center"
499 ) -> "RenderResult":
500 render_text = (
501 console.render_str(text, style=style, highlight=False)
502 if isinstance(text, str)
503 else text
504 )
505 return console.render(
506 render_text, options=render_options.update(justify=justify)
507 )
509 if self.title:
510 yield from render_annotation(
511 self.title,
512 style=Style.pick_first(self.title_style, "table.title"),
513 justify=self.title_justify,
514 )
515 yield from self._render(console, render_options, widths)
516 if self.caption:
517 yield from render_annotation(
518 self.caption,
519 style=Style.pick_first(self.caption_style, "table.caption"),
520 justify=self.caption_justify,
521 )
523 def _calculate_column_widths(
524 self, console: "Console", options: "ConsoleOptions"
525 ) -> List[int]:
526 """Calculate the widths of each column, including padding, not including borders."""
527 max_width = options.max_width
528 columns = self.columns
529 width_ranges = [
530 self._measure_column(console, options, column) for column in columns
531 ]
532 widths = [_range.maximum or 1 for _range in width_ranges]
533 get_padding_width = self._get_padding_width
534 extra_width = self._extra_width
535 if self.expand:
536 ratios = [col.ratio or 0 for col in columns if col.flexible]
537 if any(ratios):
538 fixed_widths = [
539 0 if column.flexible else _range.maximum
540 for _range, column in zip(width_ranges, columns)
541 ]
542 flex_minimum = [
543 (column.width or 1) + get_padding_width(column._index)
544 for column in columns
545 if column.flexible
546 ]
547 flexible_width = max_width - sum(fixed_widths)
548 flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)
549 iter_flex_widths = iter(flex_widths)
550 for index, column in enumerate(columns):
551 if column.flexible:
552 widths[index] = fixed_widths[index] + next(iter_flex_widths)
553 table_width = sum(widths)
555 if table_width > max_width:
556 widths = self._collapse_widths(
557 widths,
558 [(column.width is None and not column.no_wrap) for column in columns],
559 max_width,
560 )
561 table_width = sum(widths)
562 # last resort, reduce columns evenly
563 if table_width > max_width:
564 excess_width = table_width - max_width
565 widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
566 table_width = sum(widths)
568 width_ranges = [
569 self._measure_column(console, options.update_width(width), column)
570 for width, column in zip(widths, columns)
571 ]
572 widths = [_range.maximum or 0 for _range in width_ranges]
574 if (table_width < max_width and self.expand) or (
575 self.min_width is not None and table_width < (self.min_width - extra_width)
576 ):
577 _max_width = (
578 max_width
579 if self.min_width is None
580 else min(self.min_width - extra_width, max_width)
581 )
582 pad_widths = ratio_distribute(_max_width - table_width, widths)
583 widths = [_width + pad for _width, pad in zip(widths, pad_widths)]
585 return widths
587 @classmethod
588 def _collapse_widths(
589 cls, widths: List[int], wrapable: List[bool], max_width: int
590 ) -> List[int]:
591 """Reduce widths so that the total is under max_width.
593 Args:
594 widths (List[int]): List of widths.
595 wrapable (List[bool]): List of booleans that indicate if a column may shrink.
596 max_width (int): Maximum width to reduce to.
598 Returns:
599 List[int]: A new list of widths.
600 """
601 total_width = sum(widths)
602 excess_width = total_width - max_width
603 if any(wrapable):
604 while total_width and excess_width > 0:
605 max_column = max(
606 width for width, allow_wrap in zip(widths, wrapable) if allow_wrap
607 )
608 second_max_column = max(
609 width if allow_wrap and width != max_column else 0
610 for width, allow_wrap in zip(widths, wrapable)
611 )
612 column_difference = max_column - second_max_column
613 ratios = [
614 (1 if (width == max_column and allow_wrap) else 0)
615 for width, allow_wrap in zip(widths, wrapable)
616 ]
617 if not any(ratios) or not column_difference:
618 break
619 max_reduce = [min(excess_width, column_difference)] * len(widths)
620 widths = ratio_reduce(excess_width, ratios, max_reduce, widths)
622 total_width = sum(widths)
623 excess_width = total_width - max_width
624 return widths
626 def _get_cells(
627 self, console: "Console", column_index: int, column: Column
628 ) -> Iterable[_Cell]:
629 """Get all the cells with padding and optional header."""
631 collapse_padding = self.collapse_padding
632 pad_edge = self.pad_edge
633 padding = self.padding
634 any_padding = any(padding)
636 first_column = column_index == 0
637 last_column = column_index == len(self.columns) - 1
639 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}
641 def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:
642 cached = _padding_cache.get((first_row, last_row))
643 if cached:
644 return cached
645 top, right, bottom, left = padding
647 if collapse_padding:
648 if not first_column:
649 left = max(0, left - right)
650 if not last_row:
651 bottom = max(0, top - bottom)
653 if not pad_edge:
654 if first_column:
655 left = 0
656 if last_column:
657 right = 0
658 if first_row:
659 top = 0
660 if last_row:
661 bottom = 0
662 _padding = (top, right, bottom, left)
663 _padding_cache[(first_row, last_row)] = _padding
664 return _padding
666 raw_cells: List[Tuple[StyleType, "RenderableType"]] = []
667 _append = raw_cells.append
668 get_style = console.get_style
669 if self.show_header:
670 header_style = get_style(self.header_style or "") + get_style(
671 column.header_style
672 )
673 _append((header_style, column.header))
674 cell_style = get_style(column.style or "")
675 for cell in column.cells:
676 _append((cell_style, cell))
677 if self.show_footer:
678 footer_style = get_style(self.footer_style or "") + get_style(
679 column.footer_style
680 )
681 _append((footer_style, column.footer))
683 if any_padding:
684 _Padding = Padding
685 for first, last, (style, renderable) in loop_first_last(raw_cells):
686 yield _Cell(
687 style,
688 _Padding(renderable, get_padding(first, last)),
689 getattr(renderable, "vertical", None) or column.vertical,
690 )
691 else:
692 for style, renderable in raw_cells:
693 yield _Cell(
694 style,
695 renderable,
696 getattr(renderable, "vertical", None) or column.vertical,
697 )
699 def _get_padding_width(self, column_index: int) -> int:
700 """Get extra width from padding."""
701 _, pad_right, _, pad_left = self.padding
702 if self.collapse_padding:
703 if column_index > 0:
704 pad_left = max(0, pad_left - pad_right)
705 return pad_left + pad_right
707 def _measure_column(
708 self,
709 console: "Console",
710 options: "ConsoleOptions",
711 column: Column,
712 ) -> Measurement:
713 """Get the minimum and maximum width of the column."""
715 max_width = options.max_width
716 if max_width < 1:
717 return Measurement(0, 0)
719 padding_width = self._get_padding_width(column._index)
721 if column.width is not None:
722 # Fixed width column
723 return Measurement(
724 column.width + padding_width, column.width + padding_width
725 ).with_maximum(max_width)
726 # Flexible column, we need to measure contents
727 min_widths: List[int] = []
728 max_widths: List[int] = []
729 append_min = min_widths.append
730 append_max = max_widths.append
731 get_render_width = Measurement.get
732 for cell in self._get_cells(console, column._index, column):
733 _min, _max = get_render_width(console, options, cell.renderable)
734 append_min(_min)
735 append_max(_max)
737 measurement = Measurement(
738 max(min_widths) if min_widths else 1,
739 max(max_widths) if max_widths else max_width,
740 ).with_maximum(max_width)
741 measurement = measurement.clamp(
742 None if column.min_width is None else column.min_width + padding_width,
743 None if column.max_width is None else column.max_width + padding_width,
744 )
745 return measurement
747 def _render(
748 self, console: "Console", options: "ConsoleOptions", widths: List[int]
749 ) -> "RenderResult":
750 table_style = console.get_style(self.style or "")
752 border_style = table_style + console.get_style(self.border_style or "")
753 _column_cells = (
754 self._get_cells(console, column_index, column)
755 for column_index, column in enumerate(self.columns)
756 )
757 row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))
758 _box = (
759 self.box.substitute(
760 options, safe=pick_bool(self.safe_box, console.safe_box)
761 )
762 if self.box
763 else None
764 )
765 _box = _box.get_plain_headed_box() if _box and not self.show_header else _box
767 new_line = Segment.line()
769 columns = self.columns
770 show_header = self.show_header
771 show_footer = self.show_footer
772 show_edge = self.show_edge
773 show_lines = self.show_lines
774 leading = self.leading
776 _Segment = Segment
777 if _box:
778 box_segments = [
779 (
780 _Segment(_box.head_left, border_style),
781 _Segment(_box.head_right, border_style),
782 _Segment(_box.head_vertical, border_style),
783 ),
784 (
785 _Segment(_box.mid_left, border_style),
786 _Segment(_box.mid_right, border_style),
787 _Segment(_box.mid_vertical, border_style),
788 ),
789 (
790 _Segment(_box.foot_left, border_style),
791 _Segment(_box.foot_right, border_style),
792 _Segment(_box.foot_vertical, border_style),
793 ),
794 ]
795 if show_edge:
796 yield _Segment(_box.get_top(widths), border_style)
797 yield new_line
798 else:
799 box_segments = []
801 get_row_style = self.get_row_style
802 get_style = console.get_style
804 for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):
805 header_row = first and show_header
806 footer_row = last and show_footer
807 row = (
808 self.rows[index - show_header]
809 if (not header_row and not footer_row)
810 else None
811 )
812 max_height = 1
813 cells: List[List[List[Segment]]] = []
814 if header_row or footer_row:
815 row_style = Style.null()
816 else:
817 row_style = get_style(
818 get_row_style(console, index - 1 if show_header else index)
819 )
820 for width, cell, column in zip(widths, row_cell, columns):
821 render_options = options.update(
822 width=width,
823 justify=column.justify,
824 no_wrap=column.no_wrap,
825 overflow=column.overflow,
826 height=None,
827 highlight=column.highlight,
828 )
829 lines = console.render_lines(
830 cell.renderable,
831 render_options,
832 style=get_style(cell.style) + row_style,
833 )
834 max_height = max(max_height, len(lines))
835 cells.append(lines)
837 row_height = max(len(cell) for cell in cells)
839 def align_cell(
840 cell: List[List[Segment]],
841 vertical: "VerticalAlignMethod",
842 width: int,
843 style: Style,
844 ) -> List[List[Segment]]:
845 if header_row:
846 vertical = "bottom"
847 elif footer_row:
848 vertical = "top"
850 if vertical == "top":
851 return _Segment.align_top(cell, width, row_height, style)
852 elif vertical == "middle":
853 return _Segment.align_middle(cell, width, row_height, style)
854 return _Segment.align_bottom(cell, width, row_height, style)
856 cells[:] = [
857 _Segment.set_shape(
858 align_cell(
859 cell,
860 _cell.vertical,
861 width,
862 get_style(_cell.style) + row_style,
863 ),
864 width,
865 max_height,
866 )
867 for width, _cell, cell, column in zip(widths, row_cell, cells, columns)
868 ]
870 if _box:
871 if last and show_footer:
872 yield _Segment(
873 _box.get_row(widths, "foot", edge=show_edge), border_style
874 )
875 yield new_line
876 left, right, _divider = box_segments[0 if first else (2 if last else 1)]
878 # If the column divider is whitespace also style it with the row background
879 divider = (
880 _divider
881 if _divider.text.strip()
882 else _Segment(
883 _divider.text, row_style.background_style + _divider.style
884 )
885 )
886 for line_no in range(max_height):
887 if show_edge:
888 yield left
889 for last_cell, rendered_cell in loop_last(cells):
890 yield from rendered_cell[line_no]
891 if not last_cell:
892 yield divider
893 if show_edge:
894 yield right
895 yield new_line
896 else:
897 for line_no in range(max_height):
898 for rendered_cell in cells:
899 yield from rendered_cell[line_no]
900 yield new_line
901 if _box and first and show_header:
902 yield _Segment(
903 _box.get_row(widths, "head", edge=show_edge), border_style
904 )
905 yield new_line
906 end_section = row and row.end_section
907 if _box and (show_lines or leading or end_section):
908 if (
909 not last
910 and not (show_footer and index >= len(row_cells) - 2)
911 and not (show_header and header_row)
912 ):
913 if leading:
914 yield _Segment(
915 _box.get_row(widths, "mid", edge=show_edge) * leading,
916 border_style,
917 )
918 else:
919 yield _Segment(
920 _box.get_row(widths, "row", edge=show_edge), border_style
921 )
922 yield new_line
924 if _box and show_edge:
925 yield _Segment(_box.get_bottom(widths), border_style)
926 yield new_line
929if __name__ == "__main__": # pragma: no cover
930 from rich.console import Console
931 from rich.highlighter import ReprHighlighter
933 from ._timer import timer
935 with timer("Table render"):
936 table = Table(
937 title="Star Wars Movies",
938 caption="Rich example table",
939 caption_justify="right",
940 )
942 table.add_column(
943 "Released", header_style="bright_cyan", style="cyan", no_wrap=True
944 )
945 table.add_column("Title", style="magenta")
946 table.add_column("Box Office", justify="right", style="green")
948 table.add_row(
949 "Dec 20, 2019",
950 "Star Wars: The Rise of Skywalker",
951 "$952,110,690",
952 )
953 table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
954 table.add_row(
955 "Dec 15, 2017",
956 "Star Wars Ep. V111: The Last Jedi",
957 "$1,332,539,889",
958 style="on black",
959 end_section=True,
960 )
961 table.add_row(
962 "Dec 16, 2016",
963 "Rogue One: A Star Wars Story",
964 "$1,332,439,889",
965 )
967 def header(text: str) -> None:
968 console.print()
969 console.rule(highlight(text))
970 console.print()
972 console = Console()
973 highlight = ReprHighlighter()
974 header("Example Table")
975 console.print(table, justify="center")
977 table.expand = True
978 header("expand=True")
979 console.print(table)
981 table.width = 50
982 header("width=50")
984 console.print(table, justify="center")
986 table.width = None
987 table.expand = False
988 table.row_styles = ["dim", "none"]
989 header("row_styles=['dim', 'none']")
991 console.print(table, justify="center")
993 table.width = None
994 table.expand = False
995 table.row_styles = ["dim", "none"]
996 table.leading = 1
997 header("leading=1, row_styles=['dim', 'none']")
998 console.print(table, justify="center")
1000 table.width = None
1001 table.expand = False
1002 table.row_styles = ["dim", "none"]
1003 table.show_lines = True
1004 table.leading = 0
1005 header("show_lines=True, row_styles=['dim', 'none']")
1006 console.print(table, justify="center")