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

388 statements  

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) 

13 

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 

26 

27if TYPE_CHECKING: 

28 from .console import ( 

29 Console, 

30 ConsoleOptions, 

31 JustifyMethod, 

32 OverflowMethod, 

33 RenderableType, 

34 RenderResult, 

35 ) 

36 

37 

38@dataclass 

39class Column: 

40 """Defines a column within a ~Table. 

41 

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 """ 

69 

70 header: "RenderableType" = "" 

71 """RenderableType: Renderable for the header (typically a string)""" 

72 

73 footer: "RenderableType" = "" 

74 """RenderableType: Renderable for the footer (typically a string)""" 

75 

76 header_style: StyleType = "" 

77 """StyleType: The style of the header.""" 

78 

79 footer_style: StyleType = "" 

80 """StyleType: The style of the footer.""" 

81 

82 style: StyleType = "" 

83 """StyleType: The style of the column.""" 

84 

85 justify: "JustifyMethod" = "left" 

86 """str: How to justify text within the column ("left", "center", "right", or "full")""" 

87 

88 vertical: "VerticalAlignMethod" = "top" 

89 """str: How to vertically align content ("top", "middle", or "bottom")""" 

90 

91 overflow: "OverflowMethod" = "ellipsis" 

92 """str: Overflow method.""" 

93 

94 width: Optional[int] = None 

95 """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width.""" 

96 

97 min_width: Optional[int] = None 

98 """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None.""" 

99 

100 max_width: Optional[int] = None 

101 """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None.""" 

102 

103 ratio: Optional[int] = None 

104 """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents.""" 

105 

106 no_wrap: bool = False 

107 """bool: Prevent wrapping of text within the column. Defaults to ``False``.""" 

108 

109 _index: int = 0 

110 """Index of column.""" 

111 

112 _cells: List["RenderableType"] = field(default_factory=list) 

113 

114 def copy(self) -> "Column": 

115 """Return a copy of this Column.""" 

116 return replace(self, _cells=[]) 

117 

118 @property 

119 def cells(self) -> Iterable["RenderableType"]: 

120 """Get all cells in the column, not including header.""" 

121 yield from self._cells 

122 

123 @property 

124 def flexible(self) -> bool: 

125 """Check if this column is flexible.""" 

126 return self.ratio is not None 

127 

128 

129@dataclass 

130class Row: 

131 """Information regarding a row.""" 

132 

133 style: Optional[StyleType] = None 

134 """Style to apply to row.""" 

135 

136 end_section: bool = False 

137 """Indicated end of section, which will force a line beneath the row.""" 

138 

139 

140class _Cell(NamedTuple): 

141 """A single cell in a table.""" 

142 

143 style: StyleType 

144 """Style to apply to cell.""" 

145 renderable: "RenderableType" 

146 """Cell renderable.""" 

147 vertical: VerticalAlignMethod 

148 """Cell vertical alignment.""" 

149 

150 

151class Table(JupyterMixin): 

152 """A console renderable to draw a table. 

153 

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 """ 

182 

183 columns: List[Column] 

184 rows: List[Row] 

185 

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) 

249 

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. 

260 

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. 

267 

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 ) 

282 

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 

287 

288 @expand.setter 

289 def expand(self, expand: bool) -> None: 

290 """Set expand.""" 

291 self._expand = expand 

292 

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 

302 

303 @property 

304 def row_count(self) -> int: 

305 """Get the current number of rows.""" 

306 return len(self.rows) 

307 

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 

317 

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) 

326 

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 

334 

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 

350 

351 @property 

352 def padding(self) -> Tuple[int, int, int, int]: 

353 """Get cell padding.""" 

354 return self._padding 

355 

356 @padding.setter 

357 def padding(self, padding: PaddingDimensions) -> "Table": 

358 """Set cell padding.""" 

359 self._padding = Padding.unpack(padding) 

360 return self 

361 

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. 

380 

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 """ 

398 

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) 

416 

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. 

424 

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. 

430 

431 Raises: 

432 errors.NotRenderableError: If you add something that can't be rendered. 

433 """ 

434 

435 def add_cell(column: Column, renderable: "RenderableType") -> None: 

436 column._cells.append(renderable) 

437 

438 cell_renderables: List[Optional["RenderableType"]] = list(renderables) 

439 

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)) 

463 

464 def add_section(self) -> None: 

465 """Add a new section (draw a line after current row).""" 

466 

467 if self.rows: 

468 self.rows[-1].end_section = True 

469 

470 def __rich_console__( 

471 self, console: "Console", options: "ConsoleOptions" 

472 ) -> "RenderResult": 

473 if not self.columns: 

474 yield Segment("\n") 

475 return 

476 

477 max_width = options.max_width 

478 if self.width is not None: 

479 max_width = self.width 

480 

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 

486 

487 render_options = options.update( 

488 width=table_width, highlight=self.highlight, height=None 

489 ) 

490 

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 ) 

502 

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 ) 

516 

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) 

548 

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) 

561 

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] 

567 

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)] 

578 

579 return widths 

580 

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. 

586 

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. 

591 

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) 

615 

616 total_width = sum(widths) 

617 excess_width = total_width - max_width 

618 return widths 

619 

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.""" 

624 

625 collapse_padding = self.collapse_padding 

626 pad_edge = self.pad_edge 

627 padding = self.padding 

628 any_padding = any(padding) 

629 

630 first_column = column_index == 0 

631 last_column = column_index == len(self.columns) - 1 

632 

633 _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {} 

634 

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 

640 

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) 

646 

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 

659 

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)) 

676 

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 ) 

692 

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 

700 

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.""" 

708 

709 max_width = options.max_width 

710 if max_width < 1: 

711 return Measurement(0, 0) 

712 

713 padding_width = self._get_padding_width(column._index) 

714 

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) 

730 

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 

740 

741 def _render( 

742 self, console: "Console", options: "ConsoleOptions", widths: List[int] 

743 ) -> "RenderResult": 

744 table_style = console.get_style(self.style or "") 

745 

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 

760 

761 new_line = Segment.line() 

762 

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 

769 

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 = [] 

794 

795 get_row_style = self.get_row_style 

796 get_style = console.get_style 

797 

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) 

829 

830 row_height = max(len(cell) for cell in cells) 

831 

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" 

842 

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) 

848 

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 ] 

862 

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)] 

870 

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 

916 

917 if _box and show_edge: 

918 yield _Segment(_box.get_bottom(widths), border_style) 

919 yield new_line 

920 

921 

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 

926 

927 from ._timer import timer 

928 

929 with timer("Table render"): 

930 table = Table( 

931 title="Star Wars Movies", 

932 caption="Rich example table", 

933 caption_justify="right", 

934 ) 

935 

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") 

941 

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 ) 

960 

961 def header(text: str) -> None: 

962 console.print() 

963 console.rule(highlight(text)) 

964 console.print() 

965 

966 console = Console() 

967 highlight = ReprHighlighter() 

968 header("Example Table") 

969 console.print(table, justify="center") 

970 

971 table.expand = True 

972 header("expand=True") 

973 console.print(table) 

974 

975 table.width = 50 

976 header("width=50") 

977 

978 console.print(table, justify="center") 

979 

980 table.width = None 

981 table.expand = False 

982 table.row_styles = ["dim", "none"] 

983 header("row_styles=['dim', 'none']") 

984 

985 console.print(table, justify="center") 

986 

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") 

993 

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")