Skip to content

Commit

Permalink
feat: implement more detailed error types
Browse files Browse the repository at this point in the history
  • Loading branch information
dimastbk committed Jan 6, 2024
1 parent 6009cb4 commit 0b3cbca
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 7 deletions.
8 changes: 8 additions & 0 deletions python/python_calamine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,26 @@
CalamineError,
CalamineSheet,
CalamineWorkbook,
PasswordError,
SheetMetadata,
SheetTypeEnum,
SheetVisibleEnum,
WorksheetNotFound,
XmlError,
ZipError,
load_workbook,
)

__all__ = (
"CalamineError",
"CalamineSheet",
"CalamineWorkbook",
"PasswordError",
"SheetMetadata",
"SheetTypeEnum",
"SheetVisibleEnum",
"WorksheetNotFound",
"XmlError",
"ZipError",
"load_workbook",
)
4 changes: 4 additions & 0 deletions python/python_calamine/_python_calamine.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ class CalamineWorkbook:
def get_sheet_by_index(self, index: int) -> CalamineSheet: ...

class CalamineError(Exception): ...
class PasswordError(CalamineError): ...
class WorksheetNotFound(CalamineError): ...
class XmlError(CalamineError): ...
class ZipError(CalamineError): ...

def load_workbook(
path_or_filelike: str | os.PathLike | ReadBuffer,
Expand Down
8 changes: 6 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use pyo3::prelude::*;
mod types;
mod utils;
use crate::types::{
CalamineError, CalamineSheet, CalamineWorkbook, CellValue, SheetMetadata, SheetTypeEnum,
SheetVisibleEnum,
CalamineError, CalamineSheet, CalamineWorkbook, CellValue, PasswordError, SheetMetadata,
SheetTypeEnum, SheetVisibleEnum, WorksheetNotFound, XmlError, ZipError,
};

#[pyfunction]
Expand All @@ -21,5 +21,9 @@ fn _python_calamine(py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<SheetTypeEnum>()?;
m.add_class::<SheetVisibleEnum>()?;
m.add("CalamineError", py.get_type::<CalamineError>())?;
m.add("PasswordError", py.get_type::<PasswordError>())?;
m.add("WorksheetNotFound", py.get_type::<WorksheetNotFound>())?;
m.add("XmlError", py.get_type::<XmlError>())?;
m.add("ZipError", py.get_type::<ZipError>())?;
Ok(())
}
4 changes: 4 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ pub use sheet::{CalamineSheet, SheetMetadata, SheetTypeEnum, SheetVisibleEnum};
pub use workbook::CalamineWorkbook;

create_exception!(python_calamine, CalamineError, PyException);
create_exception!(python_calamine, PasswordError, CalamineError);
create_exception!(python_calamine, WorksheetNotFound, CalamineError);
create_exception!(python_calamine, XmlError, CalamineError);
create_exception!(python_calamine, ZipError, CalamineError);
5 changes: 3 additions & 2 deletions src/types/workbook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use pyo3::prelude::*;
use pyo3::types::{PyString, PyType};
use pyo3_file::PyFileLikeObject;

use super::WorksheetNotFound;
use crate::utils::err_to_py;
use crate::{CalamineError, CalamineSheet, SheetMetadata};
use crate::{CalamineSheet, SheetMetadata};

enum SheetsEnum {
File(Sheets<BufReader<File>>),
Expand Down Expand Up @@ -167,7 +168,7 @@ impl CalamineWorkbook {
let name = self
.sheet_names
.get(index)
.ok_or_else(|| CalamineError::new_err("Workbook is empty"))?
.ok_or_else(|| WorksheetNotFound::new_err(format!("Worksheet '{}' not found", index)))?
.to_string();
let range = self
.sheets
Expand Down
36 changes: 34 additions & 2 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
use calamine::Error;
use calamine::{Error, OdsError, XlsError, XlsbError, XlsxError};
use pyo3::exceptions::PyIOError;
use pyo3::PyErr;

use crate::types::CalamineError;
use crate::types::{CalamineError, PasswordError, WorksheetNotFound, XmlError, ZipError};

pub fn err_to_py(e: Error) -> PyErr {
match e {
Error::Io(err) => PyIOError::new_err(err.to_string()),
Error::Ods(ref err) => match err {
OdsError::Io(error) => PyIOError::new_err(error.to_string()),
OdsError::Zip(error) => ZipError::new_err(error.to_string()),
OdsError::Xml(error) => XmlError::new_err(error.to_string()),
OdsError::XmlAttr(error) => XmlError::new_err(error.to_string()),
OdsError::WorksheetNotFound(error) => WorksheetNotFound::new_err(error.to_string()),
_ => CalamineError::new_err(err.to_string()),
},
Error::Xls(ref err) => match err {
XlsError::Io(error) => PyIOError::new_err(error.to_string()),
XlsError::Password => PasswordError::new_err(err.to_string()),
XlsError::WorksheetNotFound(error) => WorksheetNotFound::new_err(error.to_string()),
_ => CalamineError::new_err(err.to_string()),
},
Error::Xlsx(ref err) => match err {
XlsxError::Io(error) => PyIOError::new_err(error.to_string()),
XlsxError::Zip(error) => ZipError::new_err(error.to_string()),
XlsxError::Xml(error) => XmlError::new_err(error.to_string()),
XlsxError::XmlAttr(error) => XmlError::new_err(error.to_string()),
XlsxError::XmlEof(error) => XmlError::new_err(error.to_string()),
XlsxError::Password => PasswordError::new_err(err.to_string()),
XlsxError::WorksheetNotFound(error) => WorksheetNotFound::new_err(error.to_string()),
_ => CalamineError::new_err(err.to_string()),
},
Error::Xlsb(ref err) => match err {
XlsbError::Io(error) => PyIOError::new_err(error.to_string()),
XlsbError::Zip(error) => ZipError::new_err(error.to_string()),
XlsbError::Xml(error) => XmlError::new_err(error.to_string()),
XlsbError::XmlAttr(error) => XmlError::new_err(error.to_string()),
XlsbError::WorksheetNotFound(error) => WorksheetNotFound::new_err(error.to_string()),
_ => CalamineError::new_err(err.to_string()),
},
_ => CalamineError::new_err(e.to_string()),
}
}
Empty file added tests/data/error.ods
Empty file.
Empty file added tests/data/error.xlsb
Empty file.
Empty file added tests/data/error.xlsx
Empty file.
Binary file added tests/data/password.ods
Binary file not shown.
Binary file added tests/data/password.xls
Binary file not shown.
Binary file added tests/data/password.xlsx
Binary file not shown.
65 changes: 64 additions & 1 deletion tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pathlib import Path

import pytest
from python_calamine import CalamineWorkbook
from python_calamine import CalamineWorkbook, PasswordError, WorksheetNotFound, ZipError

PATH = Path(__file__).parent / "data"

Expand Down Expand Up @@ -218,6 +218,69 @@ def test_nrows():
]


@pytest.mark.parametrize(
"path",
[
PATH / "base.xlsx",
PATH / "base.xls",
PATH / "base.xlsb",
PATH / "base.ods",
],
)
def test_worksheet_errors(path):
reader = CalamineWorkbook.from_object(path)
with pytest.raises(WorksheetNotFound):
reader.get_sheet_by_name("Sheet4")


@pytest.mark.parametrize(
"path",
[
PATH / "password.xlsx",
PATH / "password.xls",
pytest.param(
PATH / "password.xlsb",
marks=pytest.mark.xfail(reason="calamine doesn't support password in xlsb"),
),
# https://github.com/tafia/calamine/pull/392
pytest.param(
PATH / "password.ods",
marks=pytest.mark.xfail(reason="calamine doesn't support password in ods"),
),
],
)
def test_password_errors(path):
with pytest.raises(PasswordError):
CalamineWorkbook.from_object(path)


@pytest.mark.parametrize(
"path",
[
PATH / "error.xlsx",
PATH / "error.xlsb",
PATH / "error.ods",
],
)
def test_zip_errors(path):
with pytest.raises(ZipError):
CalamineWorkbook.from_path(path)


@pytest.mark.parametrize(
"path",
[
PATH / "base1.xlsx",
PATH / "base1.xls",
PATH / "base1.xlsb",
PATH / "base1.ods",
],
)
def test_io_errors(path):
with pytest.raises(IOError):
CalamineWorkbook.from_path(path)


@pytest.mark.parametrize(
"path",
[
Expand Down

0 comments on commit 0b3cbca

Please sign in to comment.