Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/table.py: 24%
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 show_header (bool, optional): Show a header row. Defaults to True.
53 show_footer (bool, optional): Show a footer row. Defaults to False.
54 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
55 show_lines (bool, optional): Draw lines between every row. Defaults to False.
56 leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
57 style (Union[str, Style], optional): Default style for the table. Defaults to "none".
58 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.
59 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
60 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
61 border_style (Union[str, Style], optional): Style of the border. Defaults to None.
62 title_style (Union[str, Style], optional): Style of the title. Defaults to None.
63 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
64 title_justify (str, optional): Justify method for title. Defaults to "center".
65 caption_justify (str, optional): Justify method for caption. Defaults to "center".
66 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
67 """
69 header: "RenderableType" = ""
70 """RenderableType: Renderable for the header (typically a string)"""
72 footer: "RenderableType" = ""
73 """RenderableType: Renderable for the footer (typically a string)"""
75 header_style: StyleType = ""
76 """StyleType: The style of the header."""
78 footer_style: StyleType = ""
79 """StyleType: The style of the footer."""
81 style: StyleType = ""
82 """StyleType: The style of the column."""
84 justify: "JustifyMethod" = "left"
85 """str: How to justify text within the column ("left", "center", "right", or "full")"""
87 vertical: "VerticalAlignMethod" = "top"
88 """str: How to vertically align content ("top", "middle", or "bottom")"""
90 overflow: "OverflowMethod" = "ellipsis"
91 """str: Overflow method."""
93 width: Optional[int] = None
94 """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width."""
96 min_width: Optional[int] = None
97 """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None."""
99 max_width: Optional[int] = None
100 """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None."""
102 ratio: Optional[int] = None
103 """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents."""
105 no_wrap: bool = False
106 """bool: Prevent wrapping of text within the column. Defaults to ``False``."""
108 highlight: bool = False
109 """bool: Apply highlighter to column. Defaults to ``False``."""
111 _index: int = 0
112 """Index of column."""
114 _cells: List["RenderableType"] = field(default_factory=list)
116 def copy(self) -> "Column":
117 """Return a copy of this Column."""
118 return replace(self, _cells=[])
120 @property
121 def cells(self) -> Iterable["RenderableType"]:
122 """Get all cells in the column, not including header."""
123 yield from self._cells
125 @property
126 def flexible(self) -> bool:
127 """Check if this column is flexible."""
128 return self.ratio is not None
131@dataclass
132class Row:
133 """Information regarding a row."""
135 style: Optional[StyleType] = None
136 """Style to apply to row."""
138 end_section: bool = False
139 """Indicated end of section, which will force a line beneath the row."""
142class _Cell(NamedTuple):
143 """A single cell in a table."""
145 style: StyleType
146 """Style to apply to cell."""
147 renderable: "RenderableType"
148 """Cell renderable."""
149 vertical: VerticalAlignMethod
150 """Cell vertical alignment."""
153class Table(JupyterMixin):
154 """A console renderable to draw a table.
156 Args:
157 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
158 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
159 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
160 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
161 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
162 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.
163 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
164 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
165 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
166 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
167 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
168 show_header (bool, optional): Show a header row. Defaults to True.
169 show_footer (bool, optional): Show a footer row. Defaults to False.
170 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
171 show_lines (bool, optional): Draw lines between every row. Defaults to False.
172 leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
173 style (Union[str, Style], optional): Default style for the table. Defaults to "none".
174 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.
175 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
176 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
177 border_style (Union[str, Style], optional): Style of the border. Defaults to None.
178 title_style (Union[str, Style], optional): Style of the title. Defaults to None.
179 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
180 title_justify (str, optional): Justify method for title. Defaults to "center".
181 caption_justify (str, optional): Justify method for caption. Defaults to "center".
182 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
183 """
185 columns: List[Column]
186 rows: List[Row]
188 def __init__(
189 self,
190 *headers: Union[Column, str],
191 title: Optional[TextType] = None,
192 caption: Optional[TextType] = None,
193 width: Optional[int] = None,
194 min_width: Optional[int] = None,
195 box: Optional[box.Box] = box.HEAVY_HEAD,
196 safe_box: Optional[bool] = None,
197 padding: PaddingDimensions = (0, 1),
198 collapse_padding: bool = False,
199 pad_edge: bool = True,
200 expand: bool = False,
201 show_header: bool = True,
202 show_footer: bool = False,
203 show_edge: bool = True,
204 show_lines: bool = False,
205 leading: int = 0,
206 style: StyleType = "none",
207 row_styles: Optional[Iterable[StyleType]] = None,
208 header_style: Optional[StyleType] = "table.header",
209 footer_style: Optional[StyleType] = "table.footer",
210 border_style: Optional[StyleType] = None,
211 title_style: Optional[StyleType] = None,
212 caption_style: Optional[StyleType] = None,
213 title_justify: "JustifyMethod" = "center",
214 caption_justify: "JustifyMethod" = "center",
215 highlight: bool = False,
216 ) -> None:
217 self.columns: List[Column] = []
218 self.rows: List[Row] = []
219 self.title = title
220 self.caption = caption
221 self.width = width
222 self.min_width = min_width
223 self.box = box
224 self.safe_box = safe_box
225 self._padding = Padding.unpack(padding)
226 self.pad_edge = pad_edge
227 self._expand = expand
228 self.show_header = show_header
229 self.show_footer = show_footer
230 self.show_edge = show_edge
231 self.show_lines = show_lines
232 self.leading = leading
233 self.collapse_padding = collapse_padding
234 self.style = style
235 self.header_style = header_style or ""
236 self.footer_style = footer_style or ""
237 self.border_style = border_style
238 self.title_style = title_style
239 self.caption_style = caption_style
240 self.title_justify: "JustifyMethod" = title_justify
241 self.caption_justify: "JustifyMethod" = caption_justify
242 self.highlight = highlight
243 self.row_styles: Sequence[StyleType] = list(row_styles or [])
244 append_column = self.columns.append
245 for header in headers:
246 if isinstance(header, str):
247 self.add_column(header=header)
248 else:
249 header._index = len(self.columns)
250 append_column(header)
252 @classmethod
253 def grid(
254 cls,
255 *headers: Union[Column, str],
256 padding: PaddingDimensions = 0,
257 collapse_padding: bool = True,
258 pad_edge: bool = False,
259 expand: bool = False,
260 ) -> "Table":
261 """Get a table with no lines, headers, or footer.
263 Args:
264 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
265 padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.
266 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.
267 pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.
268 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
270 Returns:
271 Table: A table instance.
272 """
273 return cls(
274 *headers,
275 box=None,
276 padding=padding,
277 collapse_padding=collapse_padding,
278 show_header=False,
279 show_footer=False,
280 show_edge=False,
281 pad_edge=pad_edge,
282 expand=expand,
283 )
285 @property
286 def expand(self) -> bool:
287 """Setting a non-None self.width implies expand."""
288 return self._expand or self.width is not None
290 @expand.setter
291 def expand(self, expand: bool) -> None:
292 """Set expand."""
293 self._expand = expand
295 @property
296 def _extra_width(self) -> int:
297 """Get extra width to add to cell content."""
298 width = 0
299 if self.box and self.show_edge:
300 width += 2
301 if self.box:
302 width += len(self.columns) - 1
303 return width
305 @property
306 def row_count(self) -> int:
307 """Get the current number of rows."""
308 return len(self.rows)
310 def get_row_style(self, console: "Console", index: int) -> StyleType:
311 """Get the current row style."""
312 style = Style.null()
313 if self.row_styles:
314 style += console.get_style(self.row_styles[index % len(self.row_styles)])
315 row_style = self.rows[index].style
316 if row_style is not None:
317 style += console.get_style(row_style)
318 return style
320 def __rich_measure__(
321 self, console: "Console", options: "ConsoleOptions"
322 ) -> Measurement:
323 max_width = options.max_width
324 if self.width is not None:
325 max_width = self.width
326 if max_width < 0:
327 return Measurement(0, 0)
329 extra_width = self._extra_width
330 max_width = sum(
331 self._calculate_column_widths(
332 console, options.update_width(max_width - extra_width)
333 )
334 )
335 _measure_column = self._measure_column
337 measurements = [
338 _measure_column(console, options.update_width(max_width), column)
339 for column in self.columns
340 ]
341 minimum_width = (
342 sum(measurement.minimum for measurement in measurements) + extra_width
343 )
344 maximum_width = (
345 sum(measurement.maximum for measurement in measurements) + extra_width
346 if (self.width is None)
347 else self.width
348 )
349 measurement = Measurement(minimum_width, maximum_width)
350 measurement = measurement.clamp(self.min_width)
351 return measurement
353 @property
354 def padding(self) -> Tuple[int, int, int, int]:
355 """Get cell padding."""
356 return self._padding
358 @padding.setter
359 def padding(self, padding: PaddingDimensions) -> "Table":
360 """Set cell padding."""
361 self._padding = Padding.unpack(padding)
362 return self
364 def add_column(
365 self,
366 header: "RenderableType" = "",
367 footer: "RenderableType" = "",
368 *,
369 header_style: Optional[StyleType] = None,
370 highlight: Optional[bool] = None,
371 footer_style: Optional[StyleType] = None,
372 style: Optional[StyleType] = None,
373 justify: "JustifyMethod" = "left",
374 vertical: "VerticalAlignMethod" = "top",
375 overflow: "OverflowMethod" = "ellipsis",
376 width: Optional[int] = None,
377 min_width: Optional[int] = None,
378 max_width: Optional[int] = None,
379 ratio: Optional[int] = None,
380 no_wrap: bool = False,
381 ) -> None:
382 """Add a column to the table.
384 Args:
385 header (RenderableType, optional): Text or renderable for the header.
386 Defaults to "".
387 footer (RenderableType, optional): Text or renderable for the footer.
388 Defaults to "".
389 header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.
390 highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object.
391 footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
392 style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
393 justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
394 vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
395 overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
396 width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
397 min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
398 max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.
399 ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.
400 no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
401 """
403 column = Column(
404 _index=len(self.columns),
405 header=header,
406 footer=footer,
407 header_style=header_style or "",
408 highlight=highlight if highlight is not None else self.highlight,
409 footer_style=footer_style or "",
410 style=style or "",
411 justify=justify,
412 vertical=vertical,
413 overflow=overflow,
414 width=width,
415 min_width=min_width,
416 max_width=max_width,
417 ratio=ratio,
418 no_wrap=no_wrap,
419 )
420 self.columns.append(column)
422 def add_row(
423 self,
424 *renderables: Optional["RenderableType"],
425 style: Optional[StyleType] = None,
426 end_section: bool = False,
427 ) -> None:
428 """Add a row of renderables.
430 Args:
431 *renderables (None or renderable): Each cell in a row must be a renderable object (including str),
432 or ``None`` for a blank cell.
433 style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
434 end_section (bool, optional): End a section and draw a line. Defaults to False.
436 Raises:
437 errors.NotRenderableError: If you add something that can't be rendered.
438 """
440 def add_cell(column: Column, renderable: "RenderableType") -> None:
441 column._cells.append(renderable)
443 cell_renderables: List[Optional["RenderableType"]] = list(renderables)
445 columns = self.columns
446 if len(cell_renderables) < len(columns):
447 cell_renderables = [
448 *cell_renderables,
449 *[None] * (len(columns) - len(cell_renderables)),
450 ]
451 for index, renderable in enumerate(cell_renderables):
452 if index == len(columns):
453 column = Column(_index=index, highlight=self.highlight)
454 for _ in self.rows:
455 add_cell(column, Text(""))
456 self.columns.append(column)
457 else:
458 column = columns[index]
459 if renderable is None:
460 add_cell(column, "")
461 elif is_renderable(renderable):
462 add_cell(column, renderable)
463 else:
464 raise errors.NotRenderableError(
465 f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
466 )
467 self.rows.append(Row(style=style, end_section=end_section))
469 def add_section(self) -> None:
470 """Add a new section (draw a line after current row)."""
472 if self.rows:
473 self.rows[-1].end_section = True
475 def __rich_console__(
476 self, console: "Console", options: "ConsoleOptions"
477 ) -> "RenderResult":
478 if not self.columns:
479 yield Segment("\n")
480 return
482 max_width = options.max_width
483 if self.width is not None:
484 max_width = self.width
486 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]
534 get_padding_width = self._get_padding_width
535 extra_width = self._extra_width
536 if self.expand:
537 ratios = [col.ratio or 0 for col in columns if col.flexible]
538 if any(ratios):
539 fixed_widths = [
540 0 if column.flexible else _range.maximum
541 for _range, column in zip(width_ranges, columns)
542 ]
543 flex_minimum = [
544 (column.width or 1) + get_padding_width(column._index)
545 for column in columns
546 if column.flexible
547 ]
548 flexible_width = max_width - sum(fixed_widths)
549 flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)
550 iter_flex_widths = iter(flex_widths)
551 for index, column in enumerate(columns):
552 if column.flexible:
553 widths[index] = fixed_widths[index] + next(iter_flex_widths)
554 table_width = sum(widths)
556 if table_width > max_width:
557 widths = self._collapse_widths(
558 widths,
559 [(column.width is None and not column.no_wrap) for column in columns],
560 max_width,
561 )
562 table_width = sum(widths)
563 # last resort, reduce columns evenly
564 if table_width > max_width:
565 excess_width = table_width - max_width
566 widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
567 table_width = sum(widths)
569 width_ranges = [
570 self._measure_column(console, options.update_width(width), column)
571 for width, column in zip(widths, columns)
572 ]
573 widths = [_range.maximum or 0 for _range in width_ranges]
575 if (table_width < max_width and self.expand) or (
576 self.min_width is not None and table_width < (self.min_width - extra_width)
577 ):
578 _max_width = (
579 max_width
580 if self.min_width is None
581 else min(self.min_width - extra_width, max_width)
582 )
583 pad_widths = ratio_distribute(_max_width - table_width, widths)
584 widths = [_width + pad for _width, pad in zip(widths, pad_widths)]
586 return widths
588 @classmethod
589 def _collapse_widths(
590 cls, widths: List[int], wrapable: List[bool], max_width: int
591 ) -> List[int]:
592 """Reduce widths so that the total is under max_width.
594 Args:
595 widths (List[int]): List of widths.
596 wrapable (List[bool]): List of booleans that indicate if a column may shrink.
597 max_width (int): Maximum width to reduce to.
599 Returns:
600 List[int]: A new list of widths.
601 """
602 total_width = sum(widths)
603 excess_width = total_width - max_width
604 if any(wrapable):
605 while total_width and excess_width > 0:
606 max_column = max(
607 width for width, allow_wrap in zip(widths, wrapable) if allow_wrap
608 )
609 second_max_column = max(
610 width if allow_wrap and width != max_column else 0
611 for width, allow_wrap in zip(widths, wrapable)
612 )
613 column_difference = max_column - second_max_column
614 ratios = [
615 (1 if (width == max_column and allow_wrap) else 0)
616 for width, allow_wrap in zip(widths, wrapable)
617 ]
618 if not any(ratios) or not column_difference:
619 break
620 max_reduce = [min(excess_width, column_difference)] * len(widths)
621 widths = ratio_reduce(excess_width, ratios, max_reduce, widths)
623 total_width = sum(widths)
624 excess_width = total_width - max_width
625 return widths
627 def _get_cells(
628 self, console: "Console", column_index: int, column: Column
629 ) -> Iterable[_Cell]:
630 """Get all the cells with padding and optional header."""
632 collapse_padding = self.collapse_padding
633 pad_edge = self.pad_edge
634 padding = self.padding
635 any_padding = any(padding)
637 first_column = column_index == 0
638 last_column = column_index == len(self.columns) - 1
640 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}
642 def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:
643 cached = _padding_cache.get((first_row, last_row))
644 if cached:
645 return cached
646 top, right, bottom, left = padding
648 if collapse_padding:
649 if not first_column:
650 left = max(0, left - right)
651 if not last_row:
652 bottom = max(0, top - bottom)
654 if not pad_edge:
655 if first_column:
656 left = 0
657 if last_column:
658 right = 0
659 if first_row:
660 top = 0
661 if last_row:
662 bottom = 0
663 _padding = (top, right, bottom, left)
664 _padding_cache[(first_row, last_row)] = _padding
665 return _padding
667 raw_cells: List[Tuple[StyleType, "RenderableType"]] = []
668 _append = raw_cells.append
669 get_style = console.get_style
670 if self.show_header:
671 header_style = get_style(self.header_style or "") + get_style(
672 column.header_style
673 )
674 _append((header_style, column.header))
675 cell_style = get_style(column.style or "")
676 for cell in column.cells:
677 _append((cell_style, cell))
678 if self.show_footer:
679 footer_style = get_style(self.footer_style or "") + get_style(
680 column.footer_style
681 )
682 _append((footer_style, column.footer))
684 if any_padding:
685 _Padding = Padding
686 for first, last, (style, renderable) in loop_first_last(raw_cells):
687 yield _Cell(
688 style,
689 _Padding(renderable, get_padding(first, last)),
690 getattr(renderable, "vertical", None) or column.vertical,
691 )
692 else:
693 for style, renderable in raw_cells:
694 yield _Cell(
695 style,
696 renderable,
697 getattr(renderable, "vertical", None) or column.vertical,
698 )
700 def _get_padding_width(self, column_index: int) -> int:
701 """Get extra width from padding."""
702 _, pad_right, _, pad_left = self.padding
704 if self.collapse_padding:
705 pad_left = 0
706 pad_right = abs(pad_left - pad_right)
708 if not self.pad_edge:
709 if column_index == 0:
710 pad_left = 0
711 if column_index == len(self.columns) - 1:
712 pad_right = 0
714 return pad_left + pad_right
716 def _measure_column(
717 self,
718 console: "Console",
719 options: "ConsoleOptions",
720 column: Column,
721 ) -> Measurement:
722 """Get the minimum and maximum width of the column."""
724 max_width = options.max_width
725 if max_width < 1:
726 return Measurement(0, 0)
728 padding_width = self._get_padding_width(column._index)
729 if column.width is not None:
730 # Fixed width column
731 return Measurement(
732 column.width + padding_width, column.width + padding_width
733 ).with_maximum(max_width)
734 # Flexible column, we need to measure contents
735 min_widths: List[int] = []
736 max_widths: List[int] = []
737 append_min = min_widths.append
738 append_max = max_widths.append
739 get_render_width = Measurement.get
740 for cell in self._get_cells(console, column._index, column):
741 _min, _max = get_render_width(console, options, cell.renderable)
742 append_min(_min)
743 append_max(_max)
745 measurement = Measurement(
746 max(min_widths) if min_widths else 1,
747 max(max_widths) if max_widths else max_width,
748 ).with_maximum(max_width)
749 measurement = measurement.clamp(
750 None if column.min_width is None else column.min_width + padding_width,
751 None if column.max_width is None else column.max_width + padding_width,
752 )
753 return measurement
755 def _render(
756 self, console: "Console", options: "ConsoleOptions", widths: List[int]
757 ) -> "RenderResult":
758 table_style = console.get_style(self.style or "")
760 border_style = table_style + console.get_style(self.border_style or "")
761 _column_cells = (
762 self._get_cells(console, column_index, column)
763 for column_index, column in enumerate(self.columns)
764 )
766 row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))
767 _box = (
768 self.box.substitute(
769 options, safe=pick_bool(self.safe_box, console.safe_box)
770 )
771 if self.box
772 else None
773 )
774 _box = _box.get_plain_headed_box() if _box and not self.show_header else _box
776 new_line = Segment.line()
778 columns = self.columns
779 show_header = self.show_header
780 show_footer = self.show_footer
781 show_edge = self.show_edge
782 show_lines = self.show_lines
783 leading = self.leading
785 _Segment = Segment
786 if _box:
787 box_segments = [
788 (
789 _Segment(_box.head_left, border_style),
790 _Segment(_box.head_right, border_style),
791 _Segment(_box.head_vertical, border_style),
792 ),
793 (
794 _Segment(_box.mid_left, border_style),
795 _Segment(_box.mid_right, border_style),
796 _Segment(_box.mid_vertical, border_style),
797 ),
798 (
799 _Segment(_box.foot_left, border_style),
800 _Segment(_box.foot_right, border_style),
801 _Segment(_box.foot_vertical, border_style),
802 ),
803 ]
804 if show_edge:
805 yield _Segment(_box.get_top(widths), border_style)
806 yield new_line
807 else:
808 box_segments = []
810 get_row_style = self.get_row_style
811 get_style = console.get_style
813 for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):
814 header_row = first and show_header
815 footer_row = last and show_footer
816 row = (
817 self.rows[index - show_header]
818 if (not header_row and not footer_row)
819 else None
820 )
821 max_height = 1
822 cells: List[List[List[Segment]]] = []
823 if header_row or footer_row:
824 row_style = Style.null()
825 else:
826 row_style = get_style(
827 get_row_style(console, index - 1 if show_header else index)
828 )
829 for width, cell, column in zip(widths, row_cell, columns):
830 render_options = options.update(
831 width=width,
832 justify=column.justify,
833 no_wrap=column.no_wrap,
834 overflow=column.overflow,
835 height=None,
836 highlight=column.highlight,
837 )
838 lines = console.render_lines(
839 cell.renderable,
840 render_options,
841 style=get_style(cell.style) + row_style,
842 )
843 max_height = max(max_height, len(lines))
844 cells.append(lines)
846 row_height = max(len(cell) for cell in cells)
848 def align_cell(
849 cell: List[List[Segment]],
850 vertical: "VerticalAlignMethod",
851 width: int,
852 style: Style,
853 ) -> List[List[Segment]]:
854 if header_row:
855 vertical = "bottom"
856 elif footer_row:
857 vertical = "top"
859 if vertical == "top":
860 return _Segment.align_top(cell, width, row_height, style)
861 elif vertical == "middle":
862 return _Segment.align_middle(cell, width, row_height, style)
863 return _Segment.align_bottom(cell, width, row_height, style)
865 cells[:] = [
866 _Segment.set_shape(
867 align_cell(
868 cell,
869 _cell.vertical,
870 width,
871 get_style(_cell.style) + row_style,
872 ),
873 width,
874 max_height,
875 )
876 for width, _cell, cell, column in zip(widths, row_cell, cells, columns)
877 ]
879 if _box:
880 if last and show_footer:
881 yield _Segment(
882 _box.get_row(widths, "foot", edge=show_edge), border_style
883 )
884 yield new_line
885 left, right, _divider = box_segments[0 if first else (2 if last else 1)]
887 # If the column divider is whitespace also style it with the row background
888 divider = (
889 _divider
890 if _divider.text.strip()
891 else _Segment(
892 _divider.text, row_style.background_style + _divider.style
893 )
894 )
895 for line_no in range(max_height):
896 if show_edge:
897 yield left
898 for last_cell, rendered_cell in loop_last(cells):
899 yield from rendered_cell[line_no]
900 if not last_cell:
901 yield divider
902 if show_edge:
903 yield right
904 yield new_line
905 else:
906 for line_no in range(max_height):
907 for rendered_cell in cells:
908 yield from rendered_cell[line_no]
909 yield new_line
910 if _box and first and show_header:
911 yield _Segment(
912 _box.get_row(widths, "head", edge=show_edge), border_style
913 )
914 yield new_line
915 end_section = row and row.end_section
916 if _box and (show_lines or leading or end_section):
917 if (
918 not last
919 and not (show_footer and index >= len(row_cells) - 2)
920 and not (show_header and header_row)
921 ):
922 if leading:
923 yield _Segment(
924 _box.get_row(widths, "mid", edge=show_edge) * leading,
925 border_style,
926 )
927 else:
928 yield _Segment(
929 _box.get_row(widths, "row", edge=show_edge), border_style
930 )
931 yield new_line
933 if _box and show_edge:
934 yield _Segment(_box.get_bottom(widths), border_style)
935 yield new_line
938if __name__ == "__main__": # pragma: no cover
939 from rich.console import Console
940 from rich.highlighter import ReprHighlighter
942 from ._timer import timer
944 with timer("Table render"):
945 table = Table(
946 title="Star Wars Movies",
947 caption="Rich example table",
948 caption_justify="right",
949 )
951 table.add_column(
952 "Released", header_style="bright_cyan", style="cyan", no_wrap=True
953 )
954 table.add_column("Title", style="magenta")
955 table.add_column("Box Office", justify="right", style="green")
957 table.add_row(
958 "Dec 20, 2019",
959 "Star Wars: The Rise of Skywalker",
960 "$952,110,690",
961 )
962 table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
963 table.add_row(
964 "Dec 15, 2017",
965 "Star Wars Ep. V111: The Last Jedi",
966 "$1,332,539,889",
967 style="on black",
968 end_section=True,
969 )
970 table.add_row(
971 "Dec 16, 2016",
972 "Rogue One: A Star Wars Story",
973 "$1,332,439,889",
974 )
976 def header(text: str) -> None:
977 console.print()
978 console.rule(highlight(text))
979 console.print()
981 console = Console()
982 highlight = ReprHighlighter()
983 header("Example Table")
984 console.print(table, justify="center")
986 table.expand = True
987 header("expand=True")
988 console.print(table)
990 table.width = 50
991 header("width=50")
993 console.print(table, justify="center")
995 table.width = None
996 table.expand = False
997 table.row_styles = ["dim", "none"]
998 header("row_styles=['dim', 'none']")
1000 console.print(table, justify="center")
1002 table.width = None
1003 table.expand = False
1004 table.row_styles = ["dim", "none"]
1005 table.leading = 1
1006 header("leading=1, row_styles=['dim', 'none']")
1007 console.print(table, justify="center")
1009 table.width = None
1010 table.expand = False
1011 table.row_styles = ["dim", "none"]
1012 table.show_lines = True
1013 table.leading = 0
1014 header("show_lines=True, row_styles=['dim', 'none']")
1015 console.print(table, justify="center")