Skip to content

Commit 82f59c1

Browse files
authored
feat(python): add support for defined names (named ranges) (#393)
1 parent ed3918b commit 82f59c1

File tree

8 files changed

+86
-4
lines changed

8 files changed

+86
-4
lines changed

python/fastexcel/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
ColumnInfo,
3636
ColumnInfoNoDtype,
3737
ColumnNotFoundError,
38+
DefinedName,
3839
FastExcelError,
3940
InvalidParametersError,
4041
SheetNotFoundError,
@@ -420,6 +421,16 @@ def table_names(self, sheet_name: str | None = None) -> list[str]:
420421
"""
421422
return self._reader.table_names(sheet_name)
422423

424+
def defined_names(self) -> list[DefinedName]:
425+
"""The list of defined names (named ranges) in the workbook.
426+
427+
Returns a list of DefinedName objects with 'name' and 'formula' attributes.
428+
The formula is a string representation of the range or expression.
429+
430+
Will return an empty list if no defined names are found.
431+
"""
432+
return self._reader.defined_names()
433+
423434
@typing.overload
424435
def load_table(
425436
self,
@@ -684,6 +695,8 @@ def read_excel(source: Path | str | bytes) -> ExcelReader:
684695
"DTypeFrom",
685696
"ColumnNameFrom",
686697
"ColumnInfo",
698+
# Defined names
699+
"DefinedName",
687700
# Parse error information
688701
"CellError",
689702
"CellErrors",

python/fastexcel/_fastexcel.pyi

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ class ColumnInfo:
4242
@property
4343
def dtype_from(self) -> DTypeFrom: ...
4444

45+
class DefinedName:
46+
@property
47+
def name(self) -> str: ...
48+
@property
49+
def formula(self) -> str: ...
50+
4551
class CellError:
4652
@property
4753
def position(self) -> tuple[int, int]: ...
@@ -268,6 +274,7 @@ class _ExcelReader:
268274
@property
269275
def sheet_names(self) -> list[str]: ...
270276
def table_names(self, sheet_name: str | None = None) -> list[str]: ...
277+
def defined_names(self) -> list[DefinedName]: ...
271278

272279
def read_excel(source: str | bytes) -> _ExcelReader:
273280
"""Reads an excel file and returns an ExcelReader"""

python/tests/test_defined_names.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import fastexcel
2+
import pytest
3+
4+
from .utils import path_for_fixture
5+
6+
7+
@pytest.mark.parametrize("path", ("sheet-with-defined-names.xlsx",))
8+
def test_defined_names(path: str) -> None:
9+
excel_reader = fastexcel.read_excel(path_for_fixture(path))
10+
defined_names = excel_reader.defined_names()
11+
12+
names_dict = {dn.name: dn.formula for dn in defined_names}
13+
14+
assert names_dict == {
15+
"AddingValues": "SUM(sheet1!$K$5:$K$6)",
16+
"DefinedRange": "sheet1!$A$5:$D$7",
17+
"NamedConstant": "3.4",
18+
}

src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ pub use data::{FastExcelColumn, FastExcelSeries};
1616
use error::ErrorContext;
1717
pub use error::{FastExcelError, FastExcelErrorKind, FastExcelResult};
1818
pub use types::{
19-
ColumnInfo, ColumnNameFrom, DType, DTypeCoercion, DTypeFrom, DTypes, ExcelReader, ExcelSheet,
20-
ExcelTable, IdxOrName, LoadSheetOrTableOptions, SelectedColumns, SheetVisible, SkipRows,
19+
ColumnInfo, ColumnNameFrom, DType, DTypeCoercion, DTypeFrom, DTypes, DefinedName, ExcelReader,
20+
ExcelSheet, ExcelTable, IdxOrName, LoadSheetOrTableOptions, SelectedColumns, SheetVisible,
21+
SkipRows,
2122
};
2223

2324
/// Reads an excel file and returns an object allowing to access its sheets, tables, and a bit of metadata.
@@ -72,6 +73,7 @@ fn _fastexcel(m: &Bound<'_, PyModule>) -> PyResult<()> {
7273
m.add_function(wrap_pyfunction!(py_read_excel, m)?)?;
7374
m.add_class::<ColumnInfo>()?;
7475
m.add_class::<ColumnInfoNoDtype>()?;
76+
m.add_class::<DefinedName>()?;
7577
m.add_class::<CellError>()?;
7678
m.add_class::<CellErrors>()?;
7779
m.add_class::<ExcelSheet>()?;

src/types/excelreader/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ impl ExcelSheets {
5858
Ok(names.into_iter().map(String::as_str).collect())
5959
}
6060

61+
fn defined_names(&mut self) -> FastExcelResult<Vec<DefinedName>> {
62+
let defined_names = match self {
63+
Self::File(sheets) => sheets.defined_names(),
64+
Self::Bytes(sheets) => sheets.defined_names(),
65+
}
66+
.to_vec()
67+
.into_iter()
68+
.map(|(name, formula)| DefinedName { name, formula })
69+
.collect();
70+
Ok(defined_names)
71+
}
72+
6173
#[cfg(feature = "python")]
6274
fn supports_by_ref(&self) -> bool {
6375
matches!(
@@ -100,6 +112,13 @@ impl ExcelSheets {
100112
}
101113
}
102114

115+
#[derive(Debug)]
116+
#[cfg_attr(feature = "python", pyclass(name = "DefinedName"))]
117+
pub struct DefinedName {
118+
pub name: String,
119+
pub formula: String,
120+
}
121+
103122
/// Options for loading a sheet or table.
104123
#[derive(Debug)]
105124
pub struct LoadSheetOrTableOptions {
@@ -338,6 +357,10 @@ impl ExcelReader {
338357
pub fn table_names(&mut self, sheet_name: Option<&str>) -> FastExcelResult<Vec<&str>> {
339358
self.sheets.table_names(sheet_name)
340359
}
360+
361+
pub fn defined_names(&mut self) -> FastExcelResult<Vec<DefinedName>> {
362+
self.sheets.defined_names()
363+
}
341364
}
342365

343366
impl TryFrom<&[u8]> for ExcelReader {

src/types/excelreader/python.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use arrow_array::RecordBatch;
22
use pyo3::{Bound, IntoPyObjectExt, PyAny, PyResult, Python, pymethods, types::PyString};
33

4-
use super::ExcelReader;
4+
use super::{DefinedName, ExcelReader};
5+
56
use crate::{
67
ExcelSheet,
78
data::{ExcelSheetData, record_batch_from_data_and_columns},
@@ -199,6 +200,11 @@ impl ExcelReader {
199200
self.sheets.table_names(sheet_name).into_pyresult()
200201
}
201202

203+
#[pyo3(name = "defined_names")]
204+
pub(crate) fn py_defined_names(&mut self) -> PyResult<Vec<DefinedName>> {
205+
self.defined_names().into_pyresult()
206+
}
207+
202208
#[pyo3(name = "load_sheet", signature = (
203209
idx_or_name,
204210
*,
@@ -308,3 +314,16 @@ impl ExcelReader {
308314
self.sheet_names()
309315
}
310316
}
317+
318+
#[pymethods]
319+
impl DefinedName {
320+
#[getter("name")]
321+
pub fn py_name(&self) -> &str {
322+
&self.name
323+
}
324+
325+
#[getter("formula")]
326+
pub fn py_formula(&self) -> &str {
327+
&self.formula
328+
}
329+
}

src/types/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pub(crate) mod exceltable;
55
pub(crate) mod idx_or_name;
66

77
pub use dtype::{DType, DTypeCoercion, DTypes};
8-
pub use excelreader::{ExcelReader, LoadSheetOrTableOptions};
8+
pub use excelreader::{DefinedName, ExcelReader, LoadSheetOrTableOptions};
99
pub use excelsheet::{
1010
ExcelSheet, SelectedColumns, SheetVisible, SkipRows,
1111
column_info::{ColumnInfo, ColumnNameFrom, DTypeFrom},
9.67 KB
Binary file not shown.

0 commit comments

Comments
 (0)