Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
py
${{ env.pythonLocation }}

- run: pip install pre-commit
- run: pip install pre-commit mypy
if: steps.cache-py.outputs.cache-hit != 'true'

- run: pip install .
Expand Down
15 changes: 12 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,20 @@ repos:
rev: 24.1.1
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
- repo: local
hooks:
- id: mypy
exclude: ^tests/.*$
name: mypy
entry: mypy
language: python
pass_filenames: false
- id: mypy-stubtest
name: mypy-stubtest
entry: stubtest
args:
- python_calamine
language: python
pass_filenames: false
- repo: local
hooks:
- id: rust-linting
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dynamic = ["version"]
dev = [
"maturin~=1.0",
"pre-commit~=4.3",
"mypy~=1.18.2",
"pytest~=9.0",
"pandas[excel]~=2.2",
]
Expand All @@ -36,6 +37,7 @@ profile = "black"

[tool.mypy]
python_version = "3.10"
packages = ["python_calamine"]
ignore_missing_imports = false
disallow_untyped_defs = true
check_untyped_defs = true
Expand Down
10 changes: 9 additions & 1 deletion python/python_calamine/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from ._python_calamine import (
CalamineError,
CalamineSheet,
CalamineTable,
CalamineWorkbook,
PasswordError,
SheetMetadata,
SheetTypeEnum,
SheetVisibleEnum,
TableNotFound,
TablesNotLoaded,
TablesNotSupported,
WorkbookClosed,
WorksheetNotFound,
XmlError,
Expand All @@ -16,14 +20,18 @@
__all__ = (
"CalamineError",
"CalamineSheet",
"CalamineTable",
"CalamineWorkbook",
"PasswordError",
"SheetMetadata",
"SheetTypeEnum",
"SheetVisibleEnum",
"TableNotFound",
"TablesNotLoaded",
"TablesNotSupported",
"WorkbookClosed",
"WorksheetNotFound",
"XmlError",
"ZipError",
"WorkbookClosed",
"load_workbook",
)
148 changes: 135 additions & 13 deletions python/python_calamine/_python_calamine.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Some documentations from upstream under MIT License. See authors in https://github.com/tafia/calamine
from __future__ import annotations

import contextlib
import datetime
import enum
import os
Expand All @@ -23,34 +23,57 @@ class SheetTypeEnum(enum.Enum):
@typing.final
class SheetVisibleEnum(enum.Enum):
Visible = ...
"""Visible."""
Hidden = ...
"""Hidden."""
VeryHidden = ...
"""The sheet is hidden and cannot be displayed using the user interface. It is supported only by Excel formats."""

@typing.final
class SheetMetadata:
name: str
"""Name of sheet."""
typ: SheetTypeEnum
"""Type of sheet.

Only Excel formats support this. Default value for ODS is `WorkSheet`.
"""
visible: SheetVisibleEnum
"""Visible of sheet."""

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

@typing.final
class CalamineSheet:
name: str
@property
def height(self) -> int: ...
def height(self) -> int:
"""Get the row height of a sheet data.

The height is defined as the number of rows between the start and end positions.
"""

@property
def width(self) -> int: ...
def width(self) -> int:
"""Get the column width of a sheet data.

The width is defined as the number of columns between the start and end positions.
"""

@property
def total_height(self) -> int: ...
@property
def total_width(self) -> int: ...
@property
def start(self) -> tuple[int, int] | None: ...
def start(self) -> tuple[int, int] | None:
"""Get top left cell position of a sheet data."""

@property
def end(self) -> tuple[int, int] | None: ...
def end(self) -> tuple[int, int] | None:
"""Get bottom right cell position of a sheet data."""

def to_python(
self, skip_empty_area: bool = True, nrows: int | None = None
) -> list[
Expand Down Expand Up @@ -102,34 +125,96 @@ class CalamineSheet:
"""

@typing.final
class CalamineWorkbook(contextlib.AbstractContextManager):
class CalamineTable:
name: str
"""Get the name of the table."""
sheet: str
"""Get the name of the parent worksheet for a table."""
columns: list[str]
"""Get the header names of the table columns.

In Excel table headers can be hidden but the table will still have
column header names.
"""
@property
def height(self) -> int:
"""Get the row height of a table data.

The height is defined as the number of rows between the start and end positions.
"""

@property
def width(self) -> int:
"""Get the column width of a table data.

The width is defined as the number of columns between the start and end positions.
"""

@property
def start(self) -> tuple[int, int] | None:
"""Get top left cell position of a table data."""

@property
def end(self) -> tuple[int, int] | None:
"""Get bottom right cell position of a table data."""

def to_python(
self,
) -> list[
list[
int
| float
| str
| bool
| datetime.time
| datetime.date
| datetime.datetime
| datetime.timedelta
]
]:
"""Retunrning data from table as list of lists."""

@typing.final
class CalamineWorkbook:
path: str | None
"""Path to file. `None` if bytes was loaded."""
sheet_names: list[str]
"""All sheet names of this workbook, in workbook order."""
sheets_metadata: list[SheetMetadata]
"""All sheets metadata of this workbook, in workbook order."""
table_names: list[str] | None
"""All table names of this workbook."""
@classmethod
def from_object(
cls, path_or_filelike: str | os.PathLike | ReadBuffer
cls, path_or_filelike: str | os.PathLike | ReadBuffer, load_tables: bool = False
) -> "CalamineWorkbook":
"""Determining type of pyobject and reading from it.

Args:
path_or_filelike (str | os.PathLike | ReadBuffer): path to file or IO (must imlpement read/seek methods).
load_tables (bool): load Excel tables (supported for XLSX only).
"""

@classmethod
def from_path(cls, path: str | os.PathLike) -> "CalamineWorkbook":
def from_path(
cls, path: str | os.PathLike, load_tables: bool = False
) -> "CalamineWorkbook":
"""Reading file from path.

Args:
path (str | os.PathLike): path to file.
load_tables (bool): load Excel tables (supported for XLSX only).
"""

@classmethod
def from_filelike(cls, filelike: ReadBuffer) -> "CalamineWorkbook":
def from_filelike(
cls, filelike: ReadBuffer, load_tables: bool = False
) -> "CalamineWorkbook":
"""Reading file from IO.

Args:
filelike : IO (must imlpement read/seek methods).
load_tables (bool): load Excel tables (supported for XLSX only).
"""

def close(self) -> None:
Expand Down Expand Up @@ -177,18 +262,55 @@ class CalamineWorkbook(contextlib.AbstractContextManager):
WorksheetNotFound: If worksheet not found in workbook.
"""

def get_table_by_name(self, name: str) -> CalamineTable:
"""Get table by name.

Args:
name(str): name of table

Returns:
CalamineTable

Raises:
WorkbookClosed: If workbook already closed.
WorksheetNotFound: If worksheet not found in workbook.
"""

class CalamineError(Exception): ...
class PasswordError(CalamineError): ...
class WorksheetNotFound(CalamineError): ...
class XmlError(CalamineError): ...
class ZipError(CalamineError): ...
class WorkbookClosed(CalamineError): ...
class TablesNotLoaded(CalamineError): ...
class TablesNotSupported(CalamineError): ...
class TableNotFound(CalamineError): ...

def load_workbook(
path_or_filelike: str | os.PathLike | ReadBuffer,
path_or_filelike: str | os.PathLike | ReadBuffer, load_tables: bool = False
) -> CalamineWorkbook:
"""Determining type of pyobject and reading from it.

Args:
path_or_filelike (str | os.PathLike | ReadBuffer): path to file or IO (must imlpement read/seek methods).
load_tables (bool): load Excel tables (supported for XLSX only).
"""

__all__ = [
"CalamineError",
"CalamineSheet",
"CalamineTable",
"CalamineWorkbook",
"PasswordError",
"SheetMetadata",
"SheetTypeEnum",
"SheetVisibleEnum",
"TableNotFound",
"TablesNotLoaded",
"TablesNotSupported",
"WorkbookClosed",
"WorksheetNotFound",
"XmlError",
"ZipError",
"load_workbook",
]
19 changes: 14 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
use pyo3::prelude::*;

mod types;
mod utils;
use crate::types::{
CalamineError, CalamineSheet, CalamineWorkbook, CellValue, Error, PasswordError, SheetMetadata,
SheetTypeEnum, SheetVisibleEnum, WorkbookClosed, WorksheetNotFound, XmlError, ZipError,
CalamineError, CalamineSheet, CalamineTable, CalamineWorkbook, CellValue, Error, PasswordError,
SheetMetadata, SheetTypeEnum, SheetVisibleEnum, TableNotFound, TablesNotLoaded,
TablesNotSupported, WorkbookClosed, WorksheetNotFound, XmlError, ZipError,
};

#[pyfunction]
fn load_workbook(py: Python, path_or_filelike: Py<PyAny>) -> PyResult<CalamineWorkbook> {
CalamineWorkbook::from_object(py, path_or_filelike)
#[pyo3(signature = (path_or_filelike, load_tables=false))]
fn load_workbook(
py: Python,
path_or_filelike: Py<PyAny>,
load_tables: bool,
) -> PyResult<CalamineWorkbook> {
CalamineWorkbook::from_object(py, path_or_filelike, load_tables)
}

#[pymodule(gil_used = false)]
Expand All @@ -20,11 +25,15 @@ fn _python_calamine(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<SheetMetadata>()?;
m.add_class::<SheetTypeEnum>()?;
m.add_class::<SheetVisibleEnum>()?;
m.add_class::<CalamineTable>()?;
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>())?;
m.add("TablesNotSupported", py.get_type::<TablesNotSupported>())?;
m.add("TablesNotLoaded", py.get_type::<TablesNotLoaded>())?;
m.add("TableNotFound", py.get_type::<TableNotFound>())?;
m.add("WorkbookClosed", py.get_type::<WorkbookClosed>())?;
Ok(())
}
Loading