Skip to content

Commit

Permalink
feat: implemented custom errors (#186)
Browse files Browse the repository at this point in the history
* feat: implemented custom errors

This adds custom exception types, which should make error management by users easier

closes #45

Signed-off-by: Luka Peschke <[email protected]>

* replace match with .map_err

Signed-off-by: Luka Peschke <[email protected]>

* added docstirngs to exception types

Signed-off-by: Luka Peschke <[email protected]>

* reorder __all__ to be more logical in html docs

Signed-off-by: Luka Peschke <[email protected]>

* ci: added a docs check job

Signed-off-by: Luka Peschke <[email protected]>

---------

Signed-off-by: Luka Peschke <[email protected]>
  • Loading branch information
lukapeschke authored Feb 25, 2024
1 parent 5d33b75 commit 6d76a19
Show file tree
Hide file tree
Showing 13 changed files with 456 additions and 53 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,31 @@ jobs:
command: build
args: "-o dist --interpreter python${{ matrix.python-version }}"
target: ${{ steps.target.outputs.target }}

check-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Set up rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: |
git config user.name github-actions
git config user.email [email protected]
# venv required by maturin
python3 -m venv .venv
source .venv/bin/activate
make install-test-requirements
make install-doc-requirements
# Required for pdoc to be able to import the sources
make dev-install
make doc
8 changes: 0 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ name = "fastexcel"
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0.80"
calamine = { version = "0.24.0", features = ["dates"] }
chrono = { version = "0.4.34", default-features = false }
pyo3 = { version = "0.20.3", features = ["extension-module", "anyhow", "abi3-py38"] }
pyo3 = { version = "0.20.3", features = ["extension-module", "abi3-py38"] }

[dependencies.arrow]
version = "50.0.0"
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ format:
$(ruff) --fix
$(format)
$(fmt)
$(clippy) --fix --lib -p fastexcel --allow-dirty --allow-staged

install-test-requirements:
pip install -U -r test-requirements.txt -r build-requirements.txt
Expand Down
29 changes: 27 additions & 2 deletions python/fastexcel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@

import pyarrow as pa

from ._fastexcel import __version__, _ExcelReader, _ExcelSheet
from ._fastexcel import (
ArrowError,
CalamineCellError,
CalamineError,
CannotRetrieveCellDataError,
FastExcelError,
InvalidParametersError,
SheetNotFoundError,
UnsupportedColumnTypeCombinationError,
__version__,
_ExcelReader,
_ExcelSheet,
)
from ._fastexcel import read_excel as _read_excel


Expand Down Expand Up @@ -202,4 +214,17 @@ def read_excel(path: Path | str) -> ExcelReader:
return ExcelReader(_read_excel(expanduser(path)))


__all__ = ("ExcelReader", "ExcelSheet", "read_excel", "__version__")
__all__ = (
"__version__",
"read_excel",
"ExcelReader",
"ExcelSheet",
"FastExcelError",
"CannotRetrieveCellDataError",
"CalamineCellError",
"CalamineError",
"SheetNotFoundError",
"ArrowError",
"InvalidParametersError",
"UnsupportedColumnTypeCombinationError",
)
10 changes: 10 additions & 0 deletions python/fastexcel/_fastexcel.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,13 @@ def read_excel(path: str) -> _ExcelReader:
"""Reads an excel file and returns an ExcelReader"""

__version__: str

# Exceptions
class FastExcelError(Exception): ...
class UnsupportedColumnTypeCombinationError(FastExcelError): ...
class CannotRetrieveCellDataError(FastExcelError): ...
class CalamineCellError(FastExcelError): ...
class CalamineError(FastExcelError): ...
class SheetNotFoundError(FastExcelError): ...
class ArrowError(FastExcelError): ...
class InvalidParametersError(FastExcelError): ...
60 changes: 60 additions & 0 deletions python/tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from __future__ import annotations

import fastexcel
import pytest
from utils import path_for_fixture


def test_does_not_exist() -> None:
expected_message = """calamine error: Cannot detect file format
Context:
0: Could not open workbook at path_does_not_exist.nope
1: could not load excel file at path_does_not_exist.nope"""

with pytest.raises(fastexcel.CalamineError, match=expected_message) as exc_info:
fastexcel.read_excel("path_does_not_exist.nope")

assert exc_info.value.__doc__ == "Generic calamine error"

# Should also work with the base error type
with pytest.raises(fastexcel.FastExcelError, match=expected_message):
fastexcel.read_excel("path_does_not_exist.nope")


def test_sheet_not_found_error() -> None:
excel_reader = fastexcel.read_excel(path_for_fixture("fixture-single-sheet.xlsx"))
expected_message = """sheet at index 42 not found
Context:
0: Sheet index 42 is out of range. File has 1 sheets"""

with pytest.raises(fastexcel.SheetNotFoundError, match=expected_message) as exc_info:
excel_reader.load_sheet(42)

assert exc_info.value.__doc__ == "Sheet was not found"

# Should also work with the base error type
with pytest.raises(fastexcel.FastExcelError, match=expected_message):
excel_reader.load_sheet(42)


@pytest.mark.parametrize(
"exc_class, expected_docstring",
[
(fastexcel.FastExcelError, "The base class for all fastexcel errors"),
(
fastexcel.UnsupportedColumnTypeCombinationError,
"Column contains an unsupported type combination",
),
(fastexcel.CannotRetrieveCellDataError, "Data for a given cell cannot be retrieved"),
(
fastexcel.CalamineCellError,
"calamine returned an error regarding the content of the cell",
),
(fastexcel.CalamineError, "Generic calamine error"),
(fastexcel.SheetNotFoundError, "Sheet was not found"),
(fastexcel.ArrowError, "Generic arrow error"),
(fastexcel.InvalidParametersError, "Provided parameters are invalid"),
],
)
def test_docstrings(exc_class: type[Exception], expected_docstring: str) -> None:
assert exc_class.__doc__ == expected_docstring
4 changes: 3 additions & 1 deletion python/tests/test_fastexcel.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,9 @@ def test_sheet_with_pagination_out_of_bound():
excel_reader = fastexcel.read_excel(path_for_fixture("fixture-single-sheet-with-types.xlsx"))
assert excel_reader.sheet_names == ["Sheet1"]

with pytest.raises(RuntimeError, match="To many rows skipped. Max height is 4"):
with pytest.raises(
fastexcel.InvalidParametersError, match="Too many rows skipped. Max height is 4"
):
excel_reader.load_sheet(
0,
skip_rows=1000000,
Expand Down
Loading

0 comments on commit 6d76a19

Please sign in to comment.