Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rich/table.py: 20%
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 (bool, 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 _index: int = 0
110 """Index of column."""
112 _cells: List["RenderableType"] = field(default_factory=list)
114 def copy(self) -> "Column":
115 """Return a copy of this Column."""
116 return replace(self, _cells=[])
118 @property
119 def cells(self) -> Iterable["RenderableType"]:
120 """Get all cells in the column, not including header."""
121 yield from self._cells
123 @property
124 def flexible(self) -> bool:
125 """Check if this column is flexible."""
126 return self.ratio is not None
129@dataclass
130class Row:
131 """Information regarding a row."""
133 style: Optional[StyleType] = None
134 """Style to apply to row."""
136 end_section: bool = False
137 """Indicated end of section, which will force a line beneath the row."""
140class _Cell(NamedTuple):
141 """A single cell in a table."""
143 style: StyleType
144 """Style to apply to cell."""
145 renderable: "RenderableType"
146 """Cell renderable."""
147 vertical: VerticalAlignMethod
148 """Cell vertical alignment."""
151class Table(JupyterMixin):
152 """A console renderable to draw a table.
154 Args:
155 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
156 title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None.
157 caption (Union[str, Text], optional): The table caption rendered below. Defaults to None.
158 width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None.
159 min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None.
160 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.
161 safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True.
162 padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1).
163 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False.
164 pad_edge (bool, optional): Enable padding of edge cells. Defaults to True.
165 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
166 show_header (bool, optional): Show a header row. Defaults to True.
167 show_footer (bool, optional): Show a footer row. Defaults to False.
168 show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True.
169 show_lines (bool, optional): Draw lines between every row. Defaults to False.
170 leading (bool, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0.
171 style (Union[str, Style], optional): Default style for the table. Defaults to "none".
172 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.
173 header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header".
174 footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer".
175 border_style (Union[str, Style], optional): Style of the border. Defaults to None.
176 title_style (Union[str, Style], optional): Style of the title. Defaults to None.
177 caption_style (Union[str, Style], optional): Style of the caption. Defaults to None.
178 title_justify (str, optional): Justify method for title. Defaults to "center".
179 caption_justify (str, optional): Justify method for caption. Defaults to "center".
180 highlight (bool, optional): Highlight cell contents (if str). Defaults to False.
181 """
183 columns: List[Column]
184 rows: List[Row]
186 def __init__(
187 self,
188 *headers: Union[Column, str],
189 title: Optional[TextType] = None,
190 caption: Optional[TextType] = None,
191 width: Optional[int] = None,
192 min_width: Optional[int] = None,
193 box: Optional[box.Box] = box.HEAVY_HEAD,
194 safe_box: Optional[bool] = None,
195 padding: PaddingDimensions = (0, 1),
196 collapse_padding: bool = False,
197 pad_edge: bool = True,
198 expand: bool = False,
199 show_header: bool = True,
200 show_footer: bool = False,
201 show_edge: bool = True,
202 show_lines: bool = False,
203 leading: int = 0,
204 style: StyleType = "none",
205 row_styles: Optional[Iterable[StyleType]] = None,
206 header_style: Optional[StyleType] = "table.header",
207 footer_style: Optional[StyleType] = "table.footer",
208 border_style: Optional[StyleType] = None,
209 title_style: Optional[StyleType] = None,
210 caption_style: Optional[StyleType] = None,
211 title_justify: "JustifyMethod" = "center",
212 caption_justify: "JustifyMethod" = "center",
213 highlight: bool = False,
214 ) -> None:
215 self.columns: List[Column] = []
216 self.rows: List[Row] = []
217 self.title = title
218 self.caption = caption
219 self.width = width
220 self.min_width = min_width
221 self.box = box
222 self.safe_box = safe_box
223 self._padding = Padding.unpack(padding)
224 self.pad_edge = pad_edge
225 self._expand = expand
226 self.show_header = show_header
227 self.show_footer = show_footer
228 self.show_edge = show_edge
229 self.show_lines = show_lines
230 self.leading = leading
231 self.collapse_padding = collapse_padding
232 self.style = style
233 self.header_style = header_style or ""
234 self.footer_style = footer_style or ""
235 self.border_style = border_style
236 self.title_style = title_style
237 self.caption_style = caption_style
238 self.title_justify: "JustifyMethod" = title_justify
239 self.caption_justify: "JustifyMethod" = caption_justify
240 self.highlight = highlight
241 self.row_styles: Sequence[StyleType] = list(row_styles or [])
242 append_column = self.columns.append
243 for header in headers:
244 if isinstance(header, str):
245 self.add_column(header=header)
246 else:
247 header._index = len(self.columns)
248 append_column(header)
250 @classmethod
251 def grid(
252 cls,
253 *headers: Union[Column, str],
254 padding: PaddingDimensions = 0,
255 collapse_padding: bool = True,
256 pad_edge: bool = False,
257 expand: bool = False,
258 ) -> "Table":
259 """Get a table with no lines, headers, or footer.
261 Args:
262 *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance.
263 padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0.
264 collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True.
265 pad_edge (bool, optional): Enable padding around edges of table. Defaults to False.
266 expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False.
268 Returns:
269 Table: A table instance.
270 """
271 return cls(
272 *headers,
273 box=None,
274 padding=padding,
275 collapse_padding=collapse_padding,
276 show_header=False,
277 show_footer=False,
278 show_edge=False,
279 pad_edge=pad_edge,
280 expand=expand,
281 )
283 @property
284 def expand(self) -> bool:
285 """Setting a non-None self.width implies expand."""
286 return self._expand or self.width is not None
288 @expand.setter
289 def expand(self, expand: bool) -> None:
290 """Set expand."""
291 self._expand = expand
293 @property
294 def _extra_width(self) -> int:
295 """Get extra width to add to cell content."""
296 width = 0
297 if self.box and self.show_edge:
298 width += 2
299 if self.box:
300 width += len(self.columns) - 1
301 return width
303 @property
304 def row_count(self) -> int:
305 """Get the current number of rows."""
306 return len(self.rows)
308 def get_row_style(self, console: "Console", index: int) -> StyleType:
309 """Get the current row style."""
310 style = Style.null()
311 if self.row_styles:
312 style += console.get_style(self.row_styles[index % len(self.row_styles)])
313 row_style = self.rows[index].style
314 if row_style is not None:
315 style += console.get_style(row_style)
316 return style
318 def __rich_measure__(
319 self, console: "Console", options: "ConsoleOptions"
320 ) -> Measurement:
321 max_width = options.max_width
322 if self.width is not None:
323 max_width = self.width
324 if max_width < 0:
325 return Measurement(0, 0)
327 extra_width = self._extra_width
328 max_width = sum(
329 self._calculate_column_widths(
330 console, options.update_width(max_width - extra_width)
331 )
332 )
333 _measure_column = self._measure_column
335 measurements = [
336 _measure_column(console, options.update_width(max_width), column)
337 for column in self.columns
338 ]
339 minimum_width = (
340 sum(measurement.minimum for measurement in measurements) + extra_width
341 )
342 maximum_width = (
343 sum(measurement.maximum for measurement in measurements) + extra_width
344 if (self.width is None)
345 else self.width
346 )
347 measurement = Measurement(minimum_width, maximum_width)
348 measurement = measurement.clamp(self.min_width)
349 return measurement
351 @property
352 def padding(self) -> Tuple[int, int, int, int]:
353 """Get cell padding."""
354 return self._padding
356 @padding.setter
357 def padding(self, padding: PaddingDimensions) -> "Table":
358 """Set cell padding."""
359 self._padding = Padding.unpack(padding)
360 return self
362 def add_column(
363 self,
364 header: "RenderableType" = "",
365 footer: "RenderableType" = "",
366 *,
367 header_style: Optional[StyleType] = None,
368 footer_style: Optional[StyleType] = None,
369 style: Optional[StyleType] = None,
370 justify: "JustifyMethod" = "left",
371 vertical: "VerticalAlignMethod" = "top",
372 overflow: "OverflowMethod" = "ellipsis",
373 width: Optional[int] = None,
374 min_width: Optional[int] = None,
375 max_width: Optional[int] = None,
376 ratio: Optional[int] = None,
377 no_wrap: bool = False,
378 ) -> None:
379 """Add a column to the table.
381 Args:
382 header (RenderableType, optional): Text or renderable for the header.
383 Defaults to "".
384 footer (RenderableType, optional): Text or renderable for the footer.
385 Defaults to "".
386 header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None.
387 footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None.
388 style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None.
389 justify (JustifyMethod, optional): Alignment for cells. Defaults to "left".
390 vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top".
391 overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis".
392 width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None.
393 min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None.
394 max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None.
395 ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None.
396 no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column.
397 """
399 column = Column(
400 _index=len(self.columns),
401 header=header,
402 footer=footer,
403 header_style=header_style or "",
404 footer_style=footer_style or "",
405 style=style or "",
406 justify=justify,
407 vertical=vertical,
408 overflow=overflow,
409 width=width,
410 min_width=min_width,
411 max_width=max_width,
412 ratio=ratio,
413 no_wrap=no_wrap,
414 )
415 self.columns.append(column)
417 def add_row(
418 self,
419 *renderables: Optional["RenderableType"],
420 style: Optional[StyleType] = None,
421 end_section: bool = False,
422 ) -> None:
423 """Add a row of renderables.
425 Args:
426 *renderables (None or renderable): Each cell in a row must be a renderable object (including str),
427 or ``None`` for a blank cell.
428 style (StyleType, optional): An optional style to apply to the entire row. Defaults to None.
429 end_section (bool, optional): End a section and draw a line. Defaults to False.
431 Raises:
432 errors.NotRenderableError: If you add something that can't be rendered.
433 """
435 def add_cell(column: Column, renderable: "RenderableType") -> None:
436 column._cells.append(renderable)
438 cell_renderables: List[Optional["RenderableType"]] = list(renderables)
440 columns = self.columns
441 if len(cell_renderables) < len(columns):
442 cell_renderables = [
443 *cell_renderables,
444 *[None] * (len(columns) - len(cell_renderables)),
445 ]
446 for index, renderable in enumerate(cell_renderables):
447 if index == len(columns):
448 column = Column(_index=index)
449 for _ in self.rows:
450 add_cell(column, Text(""))
451 self.columns.append(column)
452 else:
453 column = columns[index]
454 if renderable is None:
455 add_cell(column, "")
456 elif is_renderable(renderable):
457 add_cell(column, renderable)
458 else:
459 raise errors.NotRenderableError(
460 f"unable to render {type(renderable).__name__}; a string or other renderable object is required"
461 )
462 self.rows.append(Row(style=style, end_section=end_section))
464 def add_section(self) -> None:
465 """Add a new section (draw a line after current row)."""
467 if self.rows:
468 self.rows[-1].end_section = True
470 def __rich_console__(
471 self, console: "Console", options: "ConsoleOptions"
472 ) -> "RenderResult":
473 if not self.columns:
474 yield Segment("\n")
475 return
477 max_width = options.max_width
478 if self.width is not None:
479 max_width = self.width
481 extra_width = self._extra_width
482 widths = self._calculate_column_widths(
483 console, options.update_width(max_width - extra_width)
484 )
485 table_width = sum(widths) + extra_width
487 render_options = options.update(
488 width=table_width, highlight=self.highlight, height=None
489 )
491 def render_annotation(
492 text: TextType, style: StyleType, justify: "JustifyMethod" = "center"
493 ) -> "RenderResult":
494 render_text = (
495 console.render_str(text, style=style, highlight=False)
496 if isinstance(text, str)
497 else text
498 )
499 return console.render(
500 render_text, options=render_options.update(justify=justify)
501 )
503 if self.title:
504 yield from render_annotation(
505 self.title,
506 style=Style.pick_first(self.title_style, "table.title"),
507 justify=self.title_justify,
508 )
509 yield from self._render(console, render_options, widths)
510 if self.caption:
511 yield from render_annotation(
512 self.caption,
513 style=Style.pick_first(self.caption_style, "table.caption"),
514 justify=self.caption_justify,
515 )
517 def _calculate_column_widths(
518 self, console: "Console", options: "ConsoleOptions"
519 ) -> List[int]:
520 """Calculate the widths of each column, including padding, not including borders."""
521 max_width = options.max_width
522 columns = self.columns
523 width_ranges = [
524 self._measure_column(console, options, column) for column in columns
525 ]
526 widths = [_range.maximum or 1 for _range in width_ranges]
527 get_padding_width = self._get_padding_width
528 extra_width = self._extra_width
529 if self.expand:
530 ratios = [col.ratio or 0 for col in columns if col.flexible]
531 if any(ratios):
532 fixed_widths = [
533 0 if column.flexible else _range.maximum
534 for _range, column in zip(width_ranges, columns)
535 ]
536 flex_minimum = [
537 (column.width or 1) + get_padding_width(column._index)
538 for column in columns
539 if column.flexible
540 ]
541 flexible_width = max_width - sum(fixed_widths)
542 flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum)
543 iter_flex_widths = iter(flex_widths)
544 for index, column in enumerate(columns):
545 if column.flexible:
546 widths[index] = fixed_widths[index] + next(iter_flex_widths)
547 table_width = sum(widths)
549 if table_width > max_width:
550 widths = self._collapse_widths(
551 widths,
552 [(column.width is None and not column.no_wrap) for column in columns],
553 max_width,
554 )
555 table_width = sum(widths)
556 # last resort, reduce columns evenly
557 if table_width > max_width:
558 excess_width = table_width - max_width
559 widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths)
560 table_width = sum(widths)
562 width_ranges = [
563 self._measure_column(console, options.update_width(width), column)
564 for width, column in zip(widths, columns)
565 ]
566 widths = [_range.maximum or 0 for _range in width_ranges]
568 if (table_width < max_width and self.expand) or (
569 self.min_width is not None and table_width < (self.min_width - extra_width)
570 ):
571 _max_width = (
572 max_width
573 if self.min_width is None
574 else min(self.min_width - extra_width, max_width)
575 )
576 pad_widths = ratio_distribute(_max_width - table_width, widths)
577 widths = [_width + pad for _width, pad in zip(widths, pad_widths)]
579 return widths
581 @classmethod
582 def _collapse_widths(
583 cls, widths: List[int], wrapable: List[bool], max_width: int
584 ) -> List[int]:
585 """Reduce widths so that the total is under max_width.
587 Args:
588 widths (List[int]): List of widths.
589 wrapable (List[bool]): List of booleans that indicate if a column may shrink.
590 max_width (int): Maximum width to reduce to.
592 Returns:
593 List[int]: A new list of widths.
594 """
595 total_width = sum(widths)
596 excess_width = total_width - max_width
597 if any(wrapable):
598 while total_width and excess_width > 0:
599 max_column = max(
600 width for width, allow_wrap in zip(widths, wrapable) if allow_wrap
601 )
602 second_max_column = max(
603 width if allow_wrap and width != max_column else 0
604 for width, allow_wrap in zip(widths, wrapable)
605 )
606 column_difference = max_column - second_max_column
607 ratios = [
608 (1 if (width == max_column and allow_wrap) else 0)
609 for width, allow_wrap in zip(widths, wrapable)
610 ]
611 if not any(ratios) or not column_difference:
612 break
613 max_reduce = [min(excess_width, column_difference)] * len(widths)
614 widths = ratio_reduce(excess_width, ratios, max_reduce, widths)
616 total_width = sum(widths)
617 excess_width = total_width - max_width
618 return widths
620 def _get_cells(
621 self, console: "Console", column_index: int, column: Column
622 ) -> Iterable[_Cell]:
623 """Get all the cells with padding and optional header."""
625 collapse_padding = self.collapse_padding
626 pad_edge = self.pad_edge
627 padding = self.padding
628 any_padding = any(padding)
630 first_column = column_index == 0
631 last_column = column_index == len(self.columns) - 1
633 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {}
635 def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]:
636 cached = _padding_cache.get((first_row, last_row))
637 if cached:
638 return cached
639 top, right, bottom, left = padding
641 if collapse_padding:
642 if not first_column:
643 left = max(0, left - right)
644 if not last_row:
645 bottom = max(0, top - bottom)
647 if not pad_edge:
648 if first_column:
649 left = 0
650 if last_column:
651 right = 0
652 if first_row:
653 top = 0
654 if last_row:
655 bottom = 0
656 _padding = (top, right, bottom, left)
657 _padding_cache[(first_row, last_row)] = _padding
658 return _padding
660 raw_cells: List[Tuple[StyleType, "RenderableType"]] = []
661 _append = raw_cells.append
662 get_style = console.get_style
663 if self.show_header:
664 header_style = get_style(self.header_style or "") + get_style(
665 column.header_style
666 )
667 _append((header_style, column.header))
668 cell_style = get_style(column.style or "")
669 for cell in column.cells:
670 _append((cell_style, cell))
671 if self.show_footer:
672 footer_style = get_style(self.footer_style or "") + get_style(
673 column.footer_style
674 )
675 _append((footer_style, column.footer))
677 if any_padding:
678 _Padding = Padding
679 for first, last, (style, renderable) in loop_first_last(raw_cells):
680 yield _Cell(
681 style,
682 _Padding(renderable, get_padding(first, last)),
683 getattr(renderable, "vertical", None) or column.vertical,
684 )
685 else:
686 for style, renderable in raw_cells:
687 yield _Cell(
688 style,
689 renderable,
690 getattr(renderable, "vertical", None) or column.vertical,
691 )
693 def _get_padding_width(self, column_index: int) -> int:
694 """Get extra width from padding."""
695 _, pad_right, _, pad_left = self.padding
696 if self.collapse_padding:
697 if column_index > 0:
698 pad_left = max(0, pad_left - pad_right)
699 return pad_left + pad_right
701 def _measure_column(
702 self,
703 console: "Console",
704 options: "ConsoleOptions",
705 column: Column,
706 ) -> Measurement:
707 """Get the minimum and maximum width of the column."""
709 max_width = options.max_width
710 if max_width < 1:
711 return Measurement(0, 0)
713 padding_width = self._get_padding_width(column._index)
715 if column.width is not None:
716 # Fixed width column
717 return Measurement(
718 column.width + padding_width, column.width + padding_width
719 ).with_maximum(max_width)
720 # Flexible column, we need to measure contents
721 min_widths: List[int] = []
722 max_widths: List[int] = []
723 append_min = min_widths.append
724 append_max = max_widths.append
725 get_render_width = Measurement.get
726 for cell in self._get_cells(console, column._index, column):
727 _min, _max = get_render_width(console, options, cell.renderable)
728 append_min(_min)
729 append_max(_max)
731 measurement = Measurement(
732 max(min_widths) if min_widths else 1,
733 max(max_widths) if max_widths else max_width,
734 ).with_maximum(max_width)
735 measurement = measurement.clamp(
736 None if column.min_width is None else column.min_width + padding_width,
737 None if column.max_width is None else column.max_width + padding_width,
738 )
739 return measurement
741 def _render(
742 self, console: "Console", options: "ConsoleOptions", widths: List[int]
743 ) -> "RenderResult":
744 table_style = console.get_style(self.style or "")
746 border_style = table_style + console.get_style(self.border_style or "")
747 _column_cells = (
748 self._get_cells(console, column_index, column)
749 for column_index, column in enumerate(self.columns)
750 )
751 row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells))
752 _box = (
753 self.box.substitute(
754 options, safe=pick_bool(self.safe_box, console.safe_box)
755 )
756 if self.box
757 else None
758 )
759 _box = _box.get_plain_headed_box() if _box and not self.show_header else _box
761 new_line = Segment.line()
763 columns = self.columns
764 show_header = self.show_header
765 show_footer = self.show_footer
766 show_edge = self.show_edge
767 show_lines = self.show_lines
768 leading = self.leading
770 _Segment = Segment
771 if _box:
772 box_segments = [
773 (
774 _Segment(_box.head_left, border_style),
775 _Segment(_box.head_right, border_style),
776 _Segment(_box.head_vertical, border_style),
777 ),
778 (
779 _Segment(_box.foot_left, border_style),
780 _Segment(_box.foot_right, border_style),
781 _Segment(_box.foot_vertical, border_style),
782 ),
783 (
784 _Segment(_box.mid_left, border_style),
785 _Segment(_box.mid_right, border_style),
786 _Segment(_box.mid_vertical, border_style),
787 ),
788 ]
789 if show_edge:
790 yield _Segment(_box.get_top(widths), border_style)
791 yield new_line
792 else:
793 box_segments = []
795 get_row_style = self.get_row_style
796 get_style = console.get_style
798 for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)):
799 header_row = first and show_header
800 footer_row = last and show_footer
801 row = (
802 self.rows[index - show_header]
803 if (not header_row and not footer_row)
804 else None
805 )
806 max_height = 1
807 cells: List[List[List[Segment]]] = []
808 if header_row or footer_row:
809 row_style = Style.null()
810 else:
811 row_style = get_style(
812 get_row_style(console, index - 1 if show_header else index)
813 )
814 for width, cell, column in zip(widths, row_cell, columns):
815 render_options = options.update(
816 width=width,
817 justify=column.justify,
818 no_wrap=column.no_wrap,
819 overflow=column.overflow,
820 height=None,
821 )
822 lines = console.render_lines(
823 cell.renderable,
824 render_options,
825 style=get_style(cell.style) + row_style,
826 )
827 max_height = max(max_height, len(lines))
828 cells.append(lines)
830 row_height = max(len(cell) for cell in cells)
832 def align_cell(
833 cell: List[List[Segment]],
834 vertical: "VerticalAlignMethod",
835 width: int,
836 style: Style,
837 ) -> List[List[Segment]]:
838 if header_row:
839 vertical = "bottom"
840 elif footer_row:
841 vertical = "top"
843 if vertical == "top":
844 return _Segment.align_top(cell, width, row_height, style)
845 elif vertical == "middle":
846 return _Segment.align_middle(cell, width, row_height, style)
847 return _Segment.align_bottom(cell, width, row_height, style)
849 cells[:] = [
850 _Segment.set_shape(
851 align_cell(
852 cell,
853 _cell.vertical,
854 width,
855 get_style(_cell.style) + row_style,
856 ),
857 width,
858 max_height,
859 )
860 for width, _cell, cell, column in zip(widths, row_cell, cells, columns)
861 ]
863 if _box:
864 if last and show_footer:
865 yield _Segment(
866 _box.get_row(widths, "foot", edge=show_edge), border_style
867 )
868 yield new_line
869 left, right, _divider = box_segments[0 if first else (2 if last else 1)]
871 # If the column divider is whitespace also style it with the row background
872 divider = (
873 _divider
874 if _divider.text.strip()
875 else _Segment(
876 _divider.text, row_style.background_style + _divider.style
877 )
878 )
879 for line_no in range(max_height):
880 if show_edge:
881 yield left
882 for last_cell, rendered_cell in loop_last(cells):
883 yield from rendered_cell[line_no]
884 if not last_cell:
885 yield divider
886 if show_edge:
887 yield right
888 yield new_line
889 else:
890 for line_no in range(max_height):
891 for rendered_cell in cells:
892 yield from rendered_cell[line_no]
893 yield new_line
894 if _box and first and show_header:
895 yield _Segment(
896 _box.get_row(widths, "head", edge=show_edge), border_style
897 )
898 yield new_line
899 end_section = row and row.end_section
900 if _box and (show_lines or leading or end_section):
901 if (
902 not last
903 and not (show_footer and index >= len(row_cells) - 2)
904 and not (show_header and header_row)
905 ):
906 if leading:
907 yield _Segment(
908 _box.get_row(widths, "mid", edge=show_edge) * leading,
909 border_style,
910 )
911 else:
912 yield _Segment(
913 _box.get_row(widths, "row", edge=show_edge), border_style
914 )
915 yield new_line
917 if _box and show_edge:
918 yield _Segment(_box.get_bottom(widths), border_style)
919 yield new_line
922if __name__ == "__main__": # pragma: no cover
923 from rich.console import Console
924 from rich.highlighter import ReprHighlighter
925 from rich.table import Table as Table
927 from ._timer import timer
929 with timer("Table render"):
930 table = Table(
931 title="Star Wars Movies",
932 caption="Rich example table",
933 caption_justify="right",
934 )
936 table.add_column(
937 "Released", header_style="bright_cyan", style="cyan", no_wrap=True
938 )
939 table.add_column("Title", style="magenta")
940 table.add_column("Box Office", justify="right", style="green")
942 table.add_row(
943 "Dec 20, 2019",
944 "Star Wars: The Rise of Skywalker",
945 "$952,110,690",
946 )
947 table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347")
948 table.add_row(
949 "Dec 15, 2017",
950 "Star Wars Ep. V111: The Last Jedi",
951 "$1,332,539,889",
952 style="on black",
953 end_section=True,
954 )
955 table.add_row(
956 "Dec 16, 2016",
957 "Rogue One: A Star Wars Story",
958 "$1,332,439,889",
959 )
961 def header(text: str) -> None:
962 console.print()
963 console.rule(highlight(text))
964 console.print()
966 console = Console()
967 highlight = ReprHighlighter()
968 header("Example Table")
969 console.print(table, justify="center")
971 table.expand = True
972 header("expand=True")
973 console.print(table)
975 table.width = 50
976 header("width=50")
978 console.print(table, justify="center")
980 table.width = None
981 table.expand = False
982 table.row_styles = ["dim", "none"]
983 header("row_styles=['dim', 'none']")
985 console.print(table, justify="center")
987 table.width = None
988 table.expand = False
989 table.row_styles = ["dim", "none"]
990 table.leading = 1
991 header("leading=1, row_styles=['dim', 'none']")
992 console.print(table, justify="center")
994 table.width = None
995 table.expand = False
996 table.row_styles = ["dim", "none"]
997 table.show_lines = True
998 table.leading = 0
999 header("show_lines=True, row_styles=['dim', 'none']")
1000 console.print(table, justify="center")