Skip to content

Commit 71f990f

Browse files
committed
refactor: Use less restrictive types
1 parent 36e74b8 commit 71f990f

File tree

4 files changed

+78
-66
lines changed

4 files changed

+78
-66
lines changed

README.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,20 +195,22 @@ print(output)
195195

196196
## ⚙️ Options
197197

198-
All parameters are optional.
199-
200-
| Option | Type | Default | Description |
201-
| :-----------------: | :-------------------: | :-------------------: | :-------------------------------------------------------------------------------: |
202-
| `header` | `List[Any]` | `None` | First table row seperated by header row separator. Values should support `str()` |
203-
| `body` | `List[List[Any]]` | `None` | List of rows for the main section of the table. Values should support `str()` |
204-
| `footer` | `List[Any]` | `None` | Last table row seperated by header row separator. Values should support `str()` |
205-
| `column_widths` | `List[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column |
206-
| `alignments` | `List[Alignment]` | `None` (all centered) | Column alignments<br/>(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) |
207-
| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* |
208-
| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column |
209-
| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column |
210-
| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border |
211-
| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width |
198+
All parameters are optional. At least one of `header`, `body`, and `footer` must be provided.
199+
200+
Refer to the [documentation](https://table2ascii.readthedocs.io/en/stable/api.html#table2ascii) for more information.
201+
202+
| Option | Type | Default | Description |
203+
| :-----------------: | :----------------------------: | :-------------------: | :-------------------------------------------------------------------------------: |
204+
| `header` | `Sequence[SupportsStr]` | `None` | First table row seperated by header row separator. Values should support `str()` |
205+
| `body` | `Sequence[Sequence[Sequence]]` | `None` | 2D List of rows for the main section of the table. Values should support `str()` |
206+
| `footer` | `Sequence[Sequence]` | `None` | Last table row seperated by header row separator. Values should support `str()` |
207+
| `column_widths` | `Sequence[Optional[int]]` | `None` (automatic) | List of column widths in characters for each column |
208+
| `alignments` | `Sequence[Alignment]` | `None` (all centered) | Column alignments<br/>(ex. `[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]`) |
209+
| `style` | `TableStyle` | `double_thin_compact` | Table style to use for the table\* |
210+
| `first_col_heading` | `bool` | `False` | Whether to add a heading column separator after the first column |
211+
| `last_col_heading` | `bool` | `False` | Whether to add a heading column separator before the last column |
212+
| `cell_padding` | `int` | `1` | The minimum number of spaces to add between the cell content and the cell border |
213+
| `use_wcwidth` | `bool` | `True` | Whether to use [wcwidth][wcwidth] instead of `len()` to calculate cell width |
212214

213215
[wcwidth]: https://pypi.org/project/wcwidth/
214216

table2ascii/exceptions.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from __future__ import annotations
2+
3+
from collections.abc import Sequence
24
from typing import Any
35

46
from .alignment import Alignment
5-
67
from .annotations import SupportsStr
78

89

910
class Table2AsciiError(Exception):
1011
"""Base class for all table2ascii exceptions"""
1112

12-
def _message(self):
13+
def _message(self) -> str:
1314
"""Return the error message"""
1415
raise NotImplementedError
1516

@@ -39,16 +40,16 @@ class FooterColumnCountMismatchError(ColumnCountMismatchError):
3940
This class is a subclass of :class:`ColumnCountMismatchError`.
4041
4142
Attributes:
42-
footer (list[SupportsStr]): The footer that caused the error
43+
footer (Sequence[SupportsStr]): The footer that caused the error
4344
expected_columns (int): The number of columns that were expected
4445
"""
4546

46-
def __init__(self, footer: list[SupportsStr], expected_columns: int):
47+
def __init__(self, footer: Sequence[SupportsStr], expected_columns: int):
4748
self.footer = footer
4849
self.expected_columns = expected_columns
4950
super().__init__(self._message())
5051

51-
def _message(self):
52+
def _message(self) -> str:
5253
return (
5354
f"Footer column count mismatch: {len(self.footer)} columns "
5455
f"found, expected {self.expected_columns}."
@@ -62,20 +63,20 @@ class BodyColumnCountMismatchError(ColumnCountMismatchError):
6263
This class is a subclass of :class:`ColumnCountMismatchError`.
6364
6465
Attributes:
65-
body (list[list[SupportsStr]]): The body that caused the error
66+
body (Sequence[Sequence[SupportsStr]]): The body that caused the error
6667
expected_columns (int): The number of columns that were expected
67-
first_invalid_row (list[SupportsStr]): The first row with an invalid column count
68+
first_invalid_row (Sequence[SupportsStr]): The first row with an invalid column count
6869
"""
6970

70-
def __init__(self, body: list[list[SupportsStr]], expected_columns: int):
71+
def __init__(self, body: Sequence[Sequence[SupportsStr]], expected_columns: int):
7172
self.body = body
7273
self.expected_columns = expected_columns
7374
self.first_invalid_row = next(
7475
(row for row in self.body if len(row) != self.expected_columns)
7576
)
7677
super().__init__(self._message())
7778

78-
def _message(self):
79+
def _message(self) -> str:
7980
return (
8081
f"Body column count mismatch: A row with {len(self.first_invalid_row)} "
8182
f"columns was found, expected {self.expected_columns}."
@@ -89,16 +90,16 @@ class AlignmentCountMismatchError(ColumnCountMismatchError):
8990
This class is a subclass of :class:`ColumnCountMismatchError`.
9091
9192
Attributes:
92-
alignments (list[Alignment]): The alignments that caused the error
93+
alignments (Sequence[Alignment]): The alignments that caused the error
9394
expected_columns (int): The number of columns that were expected
9495
"""
9596

96-
def __init__(self, alignments: list[Alignment], expected_columns: int):
97+
def __init__(self, alignments: Sequence[Alignment], expected_columns: int):
9798
self.alignments = alignments
9899
self.expected_columns = expected_columns
99100
super().__init__(self._message())
100101

101-
def _message(self):
102+
def _message(self) -> str:
102103
return (
103104
f"Alignment count mismatch: {len(self.alignments)} alignments "
104105
f"found, expected {self.expected_columns}."
@@ -112,16 +113,16 @@ class ColumnWidthsCountMismatchError(ColumnCountMismatchError):
112113
This class is a subclass of :class:`ColumnCountMismatchError`.
113114
114115
Attributes:
115-
column_widths (list[Optional[int]]): The column widths that caused the error
116+
column_widths (Sequence[Optional[int]]): The column widths that caused the error
116117
expected_columns (int): The number of columns that were expected
117118
"""
118119

119-
def __init__(self, column_widths: list[int | None], expected_columns: int):
120+
def __init__(self, column_widths: Sequence[int | None], expected_columns: int):
120121
self.column_widths = column_widths
121122
self.expected_columns = expected_columns
122123
super().__init__(self._message())
123124

124-
def _message(self):
125+
def _message(self) -> str:
125126
return (
126127
f"Column widths count mismatch: {len(self.column_widths)} column widths "
127128
f"found, expected {self.expected_columns}."
@@ -154,7 +155,7 @@ def __init__(self, padding: int):
154155
self.padding = padding
155156
super().__init__(self._message())
156157

157-
def _message(self):
158+
def _message(self) -> str:
158159
return f"Invalid cell padding: {self.padding} is not a positive integer."
159160

160161

@@ -176,7 +177,7 @@ def __init__(self, column_index: int, column_width: int, min_width: int):
176177
self.min_width = min_width
177178
super().__init__(self._message())
178179

179-
def _message(self):
180+
def _message(self) -> str:
180181
return (
181182
f"Column width too small: The column width for column index {self.column_index} "
182183
f" of `column_widths` is {self.column_width}, but the minimum width "
@@ -197,7 +198,7 @@ def __init__(self, alignment: Any):
197198
self.alignment = alignment
198199
super().__init__(self._message())
199200

200-
def _message(self):
201+
def _message(self) -> str:
201202
return (
202203
f"Invalid alignment: {self.alignment!r} is not a valid alignment. "
203204
f"Valid alignments are: {', '.join(a.__repr__() for a in Alignment)}"
@@ -221,7 +222,7 @@ def __init__(self, string: str, max_characters: int):
221222
self.max_characters = max_characters
222223
super().__init__(self._message())
223224

224-
def _message(self):
225+
def _message(self) -> str:
225226
return (
226227
f"Too many characters for table style: {len(self.string)} characters "
227228
f"found, but the maximum number of characters allowed is {self.max_characters}."
@@ -247,7 +248,7 @@ def __init__(self, string: str, max_characters: int):
247248
self.max_characters = max_characters
248249
super().__init__(self._message())
249250

250-
def _message(self):
251+
def _message(self) -> str:
251252
return (
252253
f"Too few characters for table style: {len(self.string)} characters "
253254
f"found, but table styles can accept {self.max_characters} characters. "

table2ascii/options.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from collections.abc import Sequence
34
from dataclasses import dataclass
45

56
from .alignment import Alignment
@@ -17,8 +18,8 @@ class Options:
1718

1819
first_col_heading: bool
1920
last_col_heading: bool
20-
column_widths: list[int | None] | None
21-
alignments: list[Alignment] | None
21+
column_widths: Sequence[int | None] | None
22+
alignments: Sequence[Alignment] | None
2223
cell_padding: int
2324
style: TableStyle
2425
use_wcwidth: bool

table2ascii/table_to_ascii.py

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import textwrap
44
from math import ceil, floor
5+
from collections.abc import Sequence
56

67
from wcwidth import wcswidth
78

@@ -15,6 +16,7 @@
1516
FooterColumnCountMismatchError,
1617
InvalidAlignmentError,
1718
InvalidCellPaddingError,
19+
NoHeaderBodyOrFooterError,
1820
)
1921
from .merge import Merge
2022
from .options import Options
@@ -27,9 +29,9 @@ class TableToAscii:
2729

2830
def __init__(
2931
self,
30-
header: list[SupportsStr] | None,
31-
body: list[list[SupportsStr]] | None,
32-
footer: list[SupportsStr] | None,
32+
header: Sequence[SupportsStr] | None,
33+
body: Sequence[Sequence[SupportsStr]] | None,
34+
footer: Sequence[SupportsStr] | None,
3335
options: Options,
3436
):
3537
"""Validate arguments and initialize fields
@@ -41,9 +43,9 @@ def __init__(
4143
options: The options for the table
4244
"""
4345
# initialize fields
44-
self.__header = header
45-
self.__body = body
46-
self.__footer = footer
46+
self.__header = list(header) if header else None
47+
self.__body = list([list(row) for row in body]) if body else None
48+
self.__footer = list(footer) if footer else None
4749
self.__style = options.style
4850
self.__first_col_heading = options.first_col_heading
4951
self.__last_col_heading = options.last_col_heading
@@ -60,6 +62,10 @@ def __init__(
6062
if body and any(len(row) != self.__columns for row in body):
6163
raise BodyColumnCountMismatchError(body, self.__columns)
6264

65+
# check that at least one of header, body, or footer is not None
66+
if not header and not body and not footer:
67+
raise NoHeaderBodyOrFooterError()
68+
6369
# calculate or use given column widths
6470
self.__column_widths = self.__calculate_column_widths(options.column_widths)
6571

@@ -103,7 +109,7 @@ def widest_line(value: SupportsStr) -> int:
103109
text = str(value)
104110
return max(self.__str_width(line) for line in text.splitlines()) if len(text) else 0
105111

106-
def get_column_width(row: list[SupportsStr], column: int) -> int:
112+
def get_column_width(row: Sequence[SupportsStr], column: int) -> int:
107113
"""Get the width of a cell in a column"""
108114
value = row[column]
109115
next_value = row[column + 1] if column < self.__columns - 1 else None
@@ -122,7 +128,9 @@ def get_column_width(row: list[SupportsStr], column: int) -> int:
122128
column_widths.append(max(header_size, body_size, footer_size) + self.__cell_padding * 2)
123129
return column_widths
124130

125-
def __calculate_column_widths(self, user_column_widths: list[int | None] | None) -> list[int]:
131+
def __calculate_column_widths(
132+
self, user_column_widths: Sequence[int | None] | None
133+
) -> list[int]:
126134
"""Calculate the width of each column in the table based on the cell values and provided column widths.
127135
128136
Args:
@@ -187,7 +195,7 @@ def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> st
187195
raise InvalidAlignmentError(alignment)
188196

189197
def __wrap_long_lines_in_merged_cells(
190-
self, row: list[SupportsStr], column_separator: str
198+
self, row: Sequence[SupportsStr], column_separator: str
191199
) -> list[SupportsStr]:
192200
"""Wrap long lines in merged cells to the width of the merged cell
193201
@@ -219,9 +227,9 @@ def __row_to_ascii(
219227
heading_col_sep: str,
220228
column_separator: str,
221229
right_edge: str,
222-
filler: str | list[SupportsStr],
223-
previous_content_row: list[SupportsStr] | None = None,
224-
next_content_row: list[SupportsStr] | None = None,
230+
filler: str | Sequence[SupportsStr],
231+
previous_content_row: Sequence[SupportsStr] | None = None,
232+
next_content_row: Sequence[SupportsStr] | None = None,
225233
top_tee: str | None = None,
226234
bottom_tee: str | None = None,
227235
heading_col_top_tee: str | None = None,
@@ -266,9 +274,9 @@ def __line_in_row_to_ascii(
266274
heading_col_sep: str,
267275
column_separator: str,
268276
right_edge: str,
269-
filler: str | list[SupportsStr],
270-
previous_content_row: list[SupportsStr] | None = None,
271-
next_content_row: list[SupportsStr] | None = None,
277+
filler: str | Sequence[SupportsStr],
278+
previous_content_row: Sequence[SupportsStr] | None = None,
279+
next_content_row: Sequence[SupportsStr] | None = None,
272280
top_tee: str | None = None,
273281
bottom_tee: str | None = None,
274282
heading_col_top_tee: str | None = None,
@@ -306,9 +314,9 @@ def __line_in_cell_column_to_ascii(
306314
heading_col_sep: str,
307315
column_separator: str,
308316
right_edge: str,
309-
filler: str | list[SupportsStr],
310-
previous_content_row: list[SupportsStr] | None = None,
311-
next_content_row: list[SupportsStr] | None = None,
317+
filler: str | Sequence[SupportsStr],
318+
previous_content_row: Sequence[SupportsStr] | None = None,
319+
next_content_row: Sequence[SupportsStr] | None = None,
312320
top_tee: str | None = None,
313321
bottom_tee: str | None = None,
314322
heading_col_top_tee: str | None = None,
@@ -373,7 +381,7 @@ def __line_in_cell_column_to_ascii(
373381
return output + sep
374382

375383
def __get_padded_cell_line_content(
376-
self, line_index: int, col_index: int, column_separator: str, filler: list[SupportsStr]
384+
self, line_index: int, col_index: int, column_separator: str, filler: Sequence[SupportsStr]
377385
) -> str:
378386
# If this is a merge cell, merge with the previous column
379387
if filler[col_index] is Merge.LEFT:
@@ -437,7 +445,7 @@ def __bottom_edge_to_ascii(self) -> str:
437445
heading_col_bottom_tee=self.__style.heading_col_bottom_tee,
438446
)
439447

440-
def __content_row_to_ascii(self, row: list[SupportsStr]) -> str:
448+
def __content_row_to_ascii(self, row: Sequence[SupportsStr]) -> str:
441449
"""Assembles a row of cell values into a single line of the ascii table
442450
443451
Returns:
@@ -453,8 +461,8 @@ def __content_row_to_ascii(self, row: list[SupportsStr]) -> str:
453461

454462
def __heading_sep_to_ascii(
455463
self,
456-
previous_content_row: list[SupportsStr] | None = None,
457-
next_content_row: list[SupportsStr] | None = None,
464+
previous_content_row: Sequence[SupportsStr] | None = None,
465+
next_content_row: Sequence[SupportsStr] | None = None,
458466
) -> str:
459467
"""Assembles the separator below the header or above footer of the ascii table
460468
@@ -475,7 +483,7 @@ def __heading_sep_to_ascii(
475483
heading_col_bottom_tee=self.__style.heading_col_heading_row_bottom_tee,
476484
)
477485

478-
def __body_to_ascii(self, body: list[list[SupportsStr]]) -> str:
486+
def __body_to_ascii(self, body: Sequence[Sequence[SupportsStr]]) -> str:
479487
"""Assembles the body of the ascii table
480488
481489
Returns:
@@ -551,14 +559,14 @@ def to_ascii(self) -> str:
551559

552560

553561
def table2ascii(
554-
header: list[SupportsStr] | None = None,
555-
body: list[list[SupportsStr]] | None = None,
556-
footer: list[SupportsStr] | None = None,
562+
header: Sequence[SupportsStr] | None = None,
563+
body: Sequence[Sequence[SupportsStr]] | None = None,
564+
footer: Sequence[SupportsStr] | None = None,
557565
*,
558566
first_col_heading: bool = False,
559567
last_col_heading: bool = False,
560-
column_widths: list[int | None] | None = None,
561-
alignments: list[Alignment] | None = None,
568+
column_widths: Sequence[int | None] | None = None,
569+
alignments: Sequence[Alignment] | None = None,
562570
cell_padding: int = 1,
563571
style: TableStyle = PresetStyle.double_thin_compact,
564572
use_wcwidth: bool = True,
@@ -581,8 +589,8 @@ def table2ascii(
581589
Defaults to :py:obj:`False`.
582590
column_widths: List of widths in characters for each column. Any value of :py:obj:`None`
583591
indicates that the column width should be determined automatically. If :py:obj:`None`
584-
is passed instead of a :class:`list`, all columns will be automatically sized.
585-
Defaults to :py:obj:`None`.
592+
is passed instead of a :class:`~collections.abc.Sequence`, all columns will be automatically
593+
sized. Defaults to :py:obj:`None`.
586594
alignments: List of alignments for each column
587595
(ex. ``[Alignment.LEFT, Alignment.CENTER, Alignment.RIGHT]``). If not specified or set to
588596
:py:obj:`None`, all columns will be center-aligned. Defaults to :py:obj:`None`.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy