Repository: ToucanToco/fastexcel (language: python)

Issue:
load_sheet_by_name raises generic CalamineError instead of SheetNotFoundError
Hi,

While loading a sheet from a reader, if a missing sheet name is provided a generic `CalamineError` is raised instead of the more specific `SheetNotFoundError`.

My use case is:

```python
sheets = []
for reader in readers:
    try:
        sheet = reader.load_sheet_by_name("sheet_name")
    except SheetNotFoundError:
        continue
    # Do some other stuff with sheet before appending to list.
```


Discussion / hints:

Fix patch:
diff --git a/src/types/python/excelreader.rs b/src/types/python/excelreader.rs
index 8de50de..e295e6c 100644
--- a/src/types/python/excelreader.rs
+++ b/src/types/python/excelreader.rs
@@ -144,14 +144,25 @@ impl ExcelReader {
         let name = idx_or_name
             .try_into()
             .and_then(|idx_or_name| match idx_or_name {
-                IdxOrName::Name(name) => Ok(name),
+                IdxOrName::Name(name) => {
+                    if self.sheet_names.contains(&name) {
+                        Ok(name)
+                    } else {
+                        Err(FastExcelErrorKind::SheetNotFound(IdxOrName::Name(name.clone())).into()).with_context(||  {
+                            let available_sheets = self.sheet_names.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(", ");
+                            format!(
+                                "Sheet \"{name}\" not found in file. Available sheets: {available_sheets}."
+                            )
+                        })
+                    }
+                }
                 IdxOrName::Idx(idx) => self
                     .sheet_names
                     .get(idx)
                     .ok_or_else(|| FastExcelErrorKind::SheetNotFound(IdxOrName::Idx(idx)).into())
                     .with_context(|| {
                         format!(
-                            "Sheet index {idx} is out of range. File has {} sheets",
+                            "Sheet index {idx} is out of range. File has {} sheets.",
                             self.sheet_names.len()
                         )
                     })


Test patch:
diff --git a/python/tests/test_errors.py b/python/tests/test_errors.py
index fffb4db..bd79751 100644
--- a/python/tests/test_errors.py
+++ b/python/tests/test_errors.py
@@ -27,11 +27,11 @@ def test_does_not_exist() -> None:
         fastexcel.read_excel("path_does_not_exist.nope")
 
 
-def test_sheet_not_found_error() -> None:
+def test_sheet_idx_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"""
+    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)
@@ -43,6 +43,18 @@ def test_sheet_not_found_error() -> None:
         excel_reader.load_sheet(42)
 
 
+def test_sheet_name_not_found_error() -> None:
+    excel_reader = fastexcel.read_excel(path_for_fixture("fixture-single-sheet.xlsx"))
+    expected_message = """sheet with name "idontexist" not found
+Context:
+    0: Sheet "idontexist" not found in file. Available sheets: "January"."""
+
+    with pytest.raises(fastexcel.SheetNotFoundError, match=expected_message) as exc_info:
+        excel_reader.load_sheet("idontexist")
+
+    assert exc_info.value.__doc__ == "Sheet was not found"
+
+
 @pytest.mark.parametrize(
     "exc_class, expected_docstring",
     [