diff --git a/docs/source/api.rst b/docs/source/api.rst index 5a46121..b890a84 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -27,4 +27,27 @@ TableStyle ~~~~~~~~~~ .. autoclass:: TableStyle - :members: \ No newline at end of file + :members: + +Exceptions +~~~~~~~~~~ + +.. autoexception:: table2ascii.exceptions.Table2AsciiError + +.. autoexception:: table2ascii.exceptions.TableOptionError + +.. autoexception:: table2ascii.exceptions.ColumnCountMismatchError + +.. autoexception:: table2ascii.exceptions.FooterColumnCountMismatchError + +.. autoexception:: table2ascii.exceptions.BodyColumnCountMismatchError + +.. autoexception:: table2ascii.exceptions.AlignmentCountMismatchError + +.. autoexception:: table2ascii.exceptions.InvalidCellPaddingError + +.. autoexception:: table2ascii.exceptions.ColumnWidthsCountMismatchError + +.. autoexception:: table2ascii.exceptions.ColumnWidthTooSmallError + +.. autoexception:: table2ascii.exceptions.InvalidAlignmentError diff --git a/table2ascii/alignment.py b/table2ascii/alignment.py index 68f48d5..56d062b 100644 --- a/table2ascii/alignment.py +++ b/table2ascii/alignment.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import IntEnum -class Alignment(Enum): +class Alignment(IntEnum): """Enum for text alignment types within a table cell Example:: diff --git a/table2ascii/exceptions.py b/table2ascii/exceptions.py new file mode 100644 index 0000000..efdf177 --- /dev/null +++ b/table2ascii/exceptions.py @@ -0,0 +1,191 @@ +from __future__ import annotations +from typing import Any + +from .alignment import Alignment + +from .annotations import SupportsStr + + +class Table2AsciiError(Exception): + """Base class for all table2ascii exceptions""" + + def _message(self): + """Return the error message""" + raise NotImplementedError + + +class TableOptionError(Table2AsciiError, ValueError): + """Base class for exceptions raised when an invalid option + is passed when creating an ascii table + + This class is a subclass of :class:`Table2AsciiError` and :class:`ValueError`. + """ + + +class ColumnCountMismatchError(TableOptionError): + """Base class for exceptions raised when a parameter has an + invalid number of columns + + This class is a subclass of :class:`TableOptionError`. + """ + + expected_columns: int + + +class FooterColumnCountMismatchError(ColumnCountMismatchError): + """Exception raised when the number of columns in the footer + does not match the number of columns in the header + + This class is a subclass of :class:`ColumnCountMismatchError`. + + Attributes: + footer (list[SupportsStr]): The footer that caused the error + expected_columns (int): The number of columns that were expected + """ + + def __init__(self, footer: list[SupportsStr], expected_columns: int): + self.footer = footer + self.expected_columns = expected_columns + super().__init__(self._message()) + + def _message(self): + return ( + f"Footer column count mismatch: {len(self.footer)} columns " + f"found, expected {self.expected_columns}." + ) + + +class BodyColumnCountMismatchError(ColumnCountMismatchError): + """Exception raised when the number of columns in the body + does not match the number of columns in the footer or header + + This class is a subclass of :class:`ColumnCountMismatchError`. + + Attributes: + body (list[list[SupportsStr]]): The body that caused the error + expected_columns (int): The number of columns that were expected + first_invalid_row (list[SupportsStr]): The first row with an invalid column count + """ + + def __init__(self, body: list[list[SupportsStr]], expected_columns: int): + self.body = body + self.expected_columns = expected_columns + self.first_invalid_row = next( + (row for row in self.body if len(row) != self.expected_columns) + ) + super().__init__(self._message()) + + def _message(self): + return ( + f"Body column count mismatch: A row with {len(self.first_invalid_row)} " + f"columns was found, expected {self.expected_columns}." + ) + + +class AlignmentCountMismatchError(ColumnCountMismatchError): + """Exception raised when the number of alignments does not match + the number of columns in the table + + This class is a subclass of :class:`ColumnCountMismatchError`. + + Attributes: + alignments (list[Alignment]): The alignments that caused the error + expected_columns (int): The number of columns that were expected + """ + + def __init__(self, alignments: list[Alignment], expected_columns: int): + self.alignments = alignments + self.expected_columns = expected_columns + super().__init__(self._message()) + + def _message(self): + return ( + f"Alignment count mismatch: {len(self.alignments)} alignments " + f"found, expected {self.expected_columns}." + ) + + +class ColumnWidthsCountMismatchError(ColumnCountMismatchError): + """Exception raised when the number of column widths does not match + the number of columns in the table + + This class is a subclass of :class:`ColumnCountMismatchError`. + + Attributes: + column_widths (list[Optional[int]]): The column widths that caused the error + expected_columns (int): The number of columns that were expected + """ + + def __init__(self, column_widths: list[int | None], expected_columns: int): + self.column_widths = column_widths + self.expected_columns = expected_columns + super().__init__(self._message()) + + def _message(self): + return ( + f"Column widths count mismatch: {len(self.column_widths)} column widths " + f"found, expected {self.expected_columns}." + ) + + +class InvalidCellPaddingError(TableOptionError): + """Exception raised when the cell padding is invalid + + This class is a subclass of :class:`TableOptionError`. + + Attributes: + padding (int): The padding that caused the error + """ + + def __init__(self, padding: int): + self.padding = padding + super().__init__(self._message()) + + def _message(self): + return f"Invalid cell padding: {self.padding} is not a positive integer." + + +class ColumnWidthTooSmallError(TableOptionError): + """Exception raised when the column width is smaller than the minimum + number of characters that are required to display the content + + This class is a subclass of :class:`TableOptionError`. + + Attributes: + column_index (int): The index of the column that caused the error + column_width (int): The column width that caused the error + min_width (int): The minimum width that is allowed + """ + + def __init__(self, column_index: int, column_width: int, min_width: int): + self.column_index = column_index + self.column_width = column_width + self.min_width = min_width + super().__init__(self._message()) + + def _message(self): + return ( + f"Column width too small: The column width for column index {self.column_index} " + f" of `column_widths` is {self.column_width}, but the minimum width " + f"required to display the content is {self.min_width}." + ) + + +class InvalidAlignmentError(TableOptionError): + """Exception raised when an invalid value is passed for an :class:`Alignment` + + This class is a subclass of :class:`TableOptionError`. + + Attributes: + alignment (Any): The alignment value that caused the error + """ + + def __init__(self, alignment: Any): + self.alignment = alignment + super().__init__(self._message()) + + def _message(self): + return ( + f"Invalid alignment: {self.alignment!r} is not a valid alignment. " + f"Valid alignments are: {', '.join(a.__repr__() for a in Alignment)}" + ) diff --git a/table2ascii/table_to_ascii.py b/table2ascii/table_to_ascii.py index 70359c8..a8ff2c6 100644 --- a/table2ascii/table_to_ascii.py +++ b/table2ascii/table_to_ascii.py @@ -4,6 +4,15 @@ from .alignment import Alignment from .annotations import SupportsStr +from .exceptions import ( + AlignmentCountMismatchError, + BodyColumnCountMismatchError, + ColumnWidthTooSmallError, + ColumnWidthsCountMismatchError, + FooterColumnCountMismatchError, + InvalidAlignmentError, + InvalidCellPaddingError, +) from .options import Options from .preset_style import PresetStyle from .table_style import TableStyle @@ -41,12 +50,10 @@ def __init__( # check if footer has a different number of columns if footer and len(footer) != self.__columns: - raise ValueError("Footer must have the same number of columns as the other rows") + raise FooterColumnCountMismatchError(footer, self.__columns) # check if any rows in body have a different number of columns if body and any(len(row) != self.__columns for row in body): - raise ValueError( - "All rows in body must have the same number of columns as the other rows" - ) + raise BodyColumnCountMismatchError(body, self.__columns) # calculate or use given column widths self.__column_widths = self.__calculate_column_widths(options.column_widths) @@ -55,11 +62,11 @@ def __init__( # check if alignments specified have a different number of columns if options.alignments and len(options.alignments) != self.__columns: - raise ValueError("Length of `alignments` list must equal the number of columns") + raise AlignmentCountMismatchError(options.alignments, self.__columns) # check if the cell padding is valid if self.__cell_padding < 0: - raise ValueError("Cell padding must be greater than or equal to 0") + raise InvalidCellPaddingError(self.__cell_padding) def __count_columns(self) -> int: """Get the number of columns in the table based on the provided header, footer, and body lists. @@ -112,7 +119,7 @@ def __calculate_column_widths(self, user_column_widths: list[int | None] | None) if user_column_widths: # check that the right number of columns were specified if len(user_column_widths) != self.__columns: - raise ValueError("Length of `column_widths` list must equal the number of columns") + raise ColumnWidthsCountMismatchError(user_column_widths, self.__columns) # check that each column is at least as large as the minimum size for i in range(len(user_column_widths)): option = user_column_widths[i] @@ -120,9 +127,7 @@ def __calculate_column_widths(self, user_column_widths: list[int | None] | None) if option is None: option = minimum elif option < minimum: - raise ValueError( - f"The value at index {i} of `column_widths` is {option} which is less than the minimum {minimum}." - ) + raise ColumnWidthTooSmallError(i, option, minimum) column_widths[i] = option return column_widths @@ -151,7 +156,7 @@ def __pad(self, cell_value: SupportsStr, width: int, alignment: Alignment) -> st if alignment == Alignment.RIGHT: # pad with spaces at the beginning return (" " * (width - len(padded_text))) + padded_text - raise ValueError(f"The value '{alignment}' is not valid for alignment.") + raise InvalidAlignmentError(alignment) def __row_to_ascii( self, diff --git a/tests/test_alignments.py b/tests/test_alignments.py index b0c18b2..3e8b874 100644 --- a/tests/test_alignments.py +++ b/tests/test_alignments.py @@ -1,6 +1,7 @@ import pytest from table2ascii import Alignment, table2ascii as t2a +from table2ascii.exceptions import AlignmentCountMismatchError, InvalidAlignmentError def test_first_left_four_right(): @@ -25,7 +26,7 @@ def test_first_left_four_right(): def test_wrong_number_alignments(): - with pytest.raises(ValueError): + with pytest.raises(AlignmentCountMismatchError): t2a( header=["#", "G", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], @@ -36,7 +37,7 @@ def test_wrong_number_alignments(): def test_invalid_alignments(): - with pytest.raises(ValueError): + with pytest.raises(InvalidAlignmentError): t2a( header=["#", "G", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], diff --git a/tests/test_cell_padding.py b/tests/test_cell_padding.py index 6190053..7d68275 100644 --- a/tests/test_cell_padding.py +++ b/tests/test_cell_padding.py @@ -1,6 +1,7 @@ import pytest from table2ascii import Alignment, table2ascii as t2a +from table2ascii.exceptions import InvalidCellPaddingError def test_without_cell_padding(): @@ -72,7 +73,7 @@ def test_cell_padding_more_than_one(): def test_negative_cell_padding(): - with pytest.raises(ValueError): + with pytest.raises(InvalidCellPaddingError): t2a( header=["#", "G", "H", "R", "S"], body=[[1, 2, 3, 4, 5]], diff --git a/tests/test_column_widths.py b/tests/test_column_widths.py index 60f23f9..5a3f1ba 100644 --- a/tests/test_column_widths.py +++ b/tests/test_column_widths.py @@ -1,6 +1,7 @@ import pytest from table2ascii import table2ascii as t2a +from table2ascii.exceptions import ColumnWidthsCountMismatchError, ColumnWidthTooSmallError def test_column_widths(): @@ -70,7 +71,7 @@ def test_column_widths_contains_none(): def test_wrong_number_column_widths(): - with pytest.raises(ValueError): + with pytest.raises(ColumnWidthsCountMismatchError): t2a( header=["#", "G", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], @@ -82,7 +83,7 @@ def test_wrong_number_column_widths(): def test_negative_column_widths(): - with pytest.raises(ValueError): + with pytest.raises(ColumnWidthTooSmallError): t2a( header=["#", "G", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], @@ -94,7 +95,7 @@ def test_negative_column_widths(): def test_column_width_less_than_size(): - with pytest.raises(ValueError): + with pytest.raises(ColumnWidthTooSmallError): t2a( header=["Wide Column", "Another Wide Column", "H", "R", "S"], body=[["1", "30", "40", "35", "30"], ["2", "30", "40", "35", "30"]], diff --git a/tests/test_convert.py b/tests/test_convert.py index 3e7a045..414636f 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -1,6 +1,7 @@ import pytest from table2ascii import table2ascii as t2a +from table2ascii.exceptions import BodyColumnCountMismatchError, FooterColumnCountMismatchError def test_header_body_footer(): @@ -117,7 +118,7 @@ def test_footer(): def test_header_footer_unequal(): - with pytest.raises(ValueError): + with pytest.raises(FooterColumnCountMismatchError): t2a( header=["H", "R", "S"], footer=["SUM", "130", "140", "135", "130"], @@ -126,7 +127,7 @@ def test_header_footer_unequal(): def test_header_body_unequal(): - with pytest.raises(ValueError): + with pytest.raises(BodyColumnCountMismatchError): t2a( header=["#", "G", "H", "R", "S"], body=[ @@ -139,7 +140,7 @@ def test_header_body_unequal(): def test_footer_body_unequal(): - with pytest.raises(ValueError): + with pytest.raises(BodyColumnCountMismatchError): t2a( body=[ ["0", "45", "30", "32", "28"], 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