Skip to content

Commit

Permalink
feat: add support sheets metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
dimastbk committed Aug 22, 2023
1 parent 0e43e60 commit 0320503
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ name = "python_calamine"
crate-type = ["cdylib"]

[dependencies]
calamine = {version = "0.21.2", features = ["dates", "chrono"]}
calamine = {git = "https://github.com/tafia/calamine.git", rev = "a54bd9cddc61a4a30475c4454efbbb2eecf9f975", features = ["dates"]}
pyo3 = {version = "0.19.0", features = ["extension-module", "chrono"]}
chrono = {version = "0.4.26", features = ["serde"]}
pyo3-file = "0.7.0"
Expand Down
6 changes: 6 additions & 0 deletions python/python_calamine/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
CalamineError,
CalamineSheet,
CalamineWorkbook,
SheetMetadata,
SheetTypeEnum,
SheetVisibleEnum,
load_workbook,
)

__all__ = (
"CalamineError",
"CalamineSheet",
"CalamineWorkbook",
"SheetMetadata",
"SheetTypeEnum",
"SheetVisibleEnum",
"load_workbook",
)
24 changes: 24 additions & 0 deletions python/python_calamine/_python_calamine.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import enum
from datetime import date, datetime, time
from os import PathLike
from typing import Protocol
Expand All @@ -10,6 +11,27 @@ class ReadBuffer(Protocol):
def seek(self) -> int: ...
def read(self) -> bytes: ...

class SheetTypeEnum(enum.Enum):
WorkSheet = ...
DialogSheet = ...
MacroSheet = ...
ChartSheet = ...
VBA = ...

class SheetVisibleEnum(enum.Enum):
Visible = ...
Hidden = ...
VeryHidden = ...

class SheetMetadata:
name: str
typ: SheetTypeEnum
visible: SheetVisibleEnum

def __init__(
self, name: str, typ: SheetTypeEnum, visible: SheetVisibleEnum
) -> None: ...

class CalamineSheet:
name: str
@property
Expand Down Expand Up @@ -37,7 +59,9 @@ class CalamineSheet:
"""

class CalamineWorkbook:
path: str | None
sheet_names: list[str]
sheets_metadata: list[SheetMetadata]
@classmethod
def from_object(
cls, path_or_filelike: str | PathLike | ReadBuffer
Expand Down
8 changes: 7 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use pyo3::prelude::*;

mod types;
mod utils;
use crate::types::{CalamineError, CalamineSheet, CalamineWorkbook, CellValue};
use crate::types::{
CalamineError, CalamineSheet, CalamineWorkbook, CellValue, SheetMetadata, SheetTypeEnum,
SheetVisibleEnum,
};

#[pyfunction]
fn load_workbook(path_or_filelike: PyObject) -> PyResult<CalamineWorkbook> {
Expand All @@ -14,6 +17,9 @@ fn _python_calamine(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(load_workbook, m)?)?;
m.add_class::<CalamineWorkbook>()?;
m.add_class::<CalamineSheet>()?;
m.add_class::<SheetMetadata>()?;
m.add_class::<SheetTypeEnum>()?;
m.add_class::<SheetVisibleEnum>()?;
m.add("CalamineError", py.get_type::<CalamineError>())?;
Ok(())
}
2 changes: 1 addition & 1 deletion src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod cell;
mod sheet;
mod workbook;
pub use cell::CellValue;
pub use sheet::CalamineSheet;
pub use sheet::{CalamineSheet, SheetMetadata, SheetTypeEnum, SheetVisibleEnum};
pub use workbook::CalamineWorkbook;

create_exception!(python_calamine, CalamineError, PyException);
116 changes: 115 additions & 1 deletion src/types/sheet.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,118 @@
use calamine::{DataType, Range};
use std::fmt::Display;

use calamine::{DataType, Range, SheetType, SheetVisible};
use pyo3::class::basic::CompareOp;
use pyo3::prelude::*;

use crate::CellValue;

#[pyclass]
#[derive(Clone, Debug, PartialEq)]
pub enum SheetTypeEnum {
/// WorkSheet
WorkSheet,
/// DialogSheet
DialogSheet,
/// MacroSheet
MacroSheet,
/// ChartSheet
ChartSheet,
/// VBA module
Vba,
}

impl Display for SheetTypeEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SheetTypeEnum.{:?}", self)
}
}

impl From<SheetType> for SheetTypeEnum {
fn from(value: SheetType) -> Self {
match value {
SheetType::WorkSheet => Self::WorkSheet,
SheetType::DialogSheet => Self::DialogSheet,
SheetType::MacroSheet => Self::MacroSheet,
SheetType::ChartSheet => Self::ChartSheet,
SheetType::Vba => Self::Vba,
}
}
}

#[pyclass]
#[derive(Clone, Debug, PartialEq)]
pub enum SheetVisibleEnum {
/// Visible
Visible,
/// Hidden
Hidden,
/// The sheet is hidden and cannot be displayed using the user interface. It is supported only by Excel formats.
VeryHidden,
}

impl Display for SheetVisibleEnum {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SheetVisibleEnum.{:?}", self)
}
}

impl From<SheetVisible> for SheetVisibleEnum {
fn from(value: SheetVisible) -> Self {
match value {
SheetVisible::Visible => Self::Visible,
SheetVisible::Hidden => Self::Hidden,
SheetVisible::VeryHidden => Self::VeryHidden,
}
}
}

#[pyclass]
#[derive(Clone, PartialEq)]
pub struct SheetMetadata {
#[pyo3(get)]
name: String,
#[pyo3(get)]
typ: SheetTypeEnum,
#[pyo3(get)]
visible: SheetVisibleEnum,
}

#[pymethods]
impl SheetMetadata {
// implementation of some methods for testing
#[new]
fn py_new(name: &str, typ: SheetTypeEnum, visible: SheetVisibleEnum) -> Self {
SheetMetadata {
name: name.to_string(),
typ,
visible,
}
}

fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"SheetMetadata(name='{}', typ={}, visible={})",
self.name, self.typ, self.visible
))
}

fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> PyObject {
match op {
CompareOp::Eq => self.eq(other).into_py(py),
CompareOp::Ne => self.ne(other).into_py(py),
_ => py.NotImplemented(),
}
}
}

impl SheetMetadata {
pub fn new(name: String, typ: SheetType, visible: SheetVisible) -> Self {
let typ = SheetTypeEnum::from(typ);
let visible = SheetVisibleEnum::from(visible);
SheetMetadata { name, typ, visible }
}
}

#[pyclass]
pub struct CalamineSheet {
#[pyo3(get)]
Expand All @@ -18,6 +128,10 @@ impl CalamineSheet {

#[pymethods]
impl CalamineSheet {
fn __repr__(&self) -> PyResult<String> {
Ok(format!("CalamineSheet(name='{}')", self.name))
}

#[getter]
fn height(&self) -> usize {
self.range.height()
Expand Down
31 changes: 29 additions & 2 deletions src/types/workbook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ use pyo3::types::{PyString, PyType};
use pyo3_file::PyFileLikeObject;

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

enum SheetsEnum {
File(Sheets<BufReader<File>>),
FileLike(Sheets<Cursor<Vec<u8>>>),
}

impl SheetsEnum {
fn sheet_names(&self) -> &[String] {
fn sheets_metadata(&self) -> Vec<SheetMetadata> {
match self {
SheetsEnum::File(f) => f.sheets_metadata(),
SheetsEnum::FileLike(f) => f.sheets_metadata(),
}
.iter()
.map(|s| SheetMetadata::new(s.name.clone(), s.typ, s.visible))
.collect()
}

fn sheet_names(&self) -> Vec<String> {
match self {
SheetsEnum::File(f) => f.sheet_names(),
SheetsEnum::FileLike(f) => f.sheet_names(),
Expand Down Expand Up @@ -46,13 +56,24 @@ impl SheetsEnum {

#[pyclass]
pub struct CalamineWorkbook {
#[pyo3(get)]
path: Option<String>,
sheets: SheetsEnum,
#[pyo3(get)]
sheets_metadata: Vec<SheetMetadata>,
#[pyo3(get)]
sheet_names: Vec<String>,
}

#[pymethods]
impl CalamineWorkbook {
fn __repr__(&self) -> PyResult<String> {
match &self.path {
Some(path) => Ok(format!("CalamineWorkbook(path='{}')", path)),
None => Ok("CalamineWorkbook(path='bytes')".to_string()),
}
}

#[classmethod]
#[pyo3(name = "from_object")]
fn py_from_object(_cls: &PyType, path_or_filelike: PyObject) -> PyResult<Self> {
Expand Down Expand Up @@ -116,19 +137,25 @@ impl CalamineWorkbook {
let reader = Cursor::new(buf);
let sheets = SheetsEnum::FileLike(open_workbook_auto_from_rs(reader).map_err(err_to_py)?);
let sheet_names = sheets.sheet_names().to_owned();
let sheets_metadata = sheets.sheets_metadata().to_owned();

Ok(Self {
path: None,
sheets,
sheets_metadata,
sheet_names,
})
}

pub fn from_path(path: &str) -> PyResult<Self> {
let sheets = SheetsEnum::File(open_workbook_auto(path).map_err(err_to_py)?);
let sheet_names = sheets.sheet_names().to_owned();
let sheets_metadata = sheets.sheets_metadata().to_owned();

Ok(Self {
path: Some(path.to_string()),
sheets,
sheets_metadata,
sheet_names,
})
}
Expand Down
Binary file added tests/data/any_sheets.ods
Binary file not shown.
Binary file added tests/data/any_sheets.xls
Binary file not shown.
Binary file added tests/data/any_sheets.xlsb
Binary file not shown.
Binary file added tests/data/any_sheets.xlsx
Binary file not shown.
Loading

0 comments on commit 0320503

Please sign in to comment.