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
11 changes: 7 additions & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
types: [opened, synchronize, reopened]

env:
MIN_PYTHON_VERSION: "3.9"
MIN_PYTHON_VERSION: "3.10"

defaults:
run:
Expand Down Expand Up @@ -61,7 +61,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
os: ["ubuntu-latest", "macos-14", "windows-latest"]
steps:
- uses: actions/checkout@v5
Expand All @@ -86,7 +86,7 @@ jobs:
matrix:
# Only testing the build on the smallest supported Python version
# since we're building abi3 wheels
python-version: ["3.9"]
python-version: ["3.10"]
os: ["ubuntu-latest", "macos-14", "windows-latest"]
architecture: [x86-64, aarch64]
exclude:
Expand All @@ -101,7 +101,10 @@ jobs:
run: |
TARGET=${{ matrix.os == 'macos-14' && (matrix.architecture == 'aarch64' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin') || (matrix.architecture == 'aarch64' && 'aarch64-unknown-linux-gnu' || null) }}
echo "target=$TARGET" >> $GITHUB_OUTPUT

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: build (fast)
uses: PyO3/maturin-action@v1
with:
Expand Down
26 changes: 13 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9"]
python-version: ["3.10"]
architecture: [x86-64, aarch64]
steps:
- uses: actions/checkout@v5
Expand All @@ -33,7 +33,7 @@ jobs:
runs-on: macos-14
strategy:
matrix:
python-version: ["3.9"]
python-version: ["3.10"]
architecture: [x86-64, aarch64]
steps:
- uses: actions/checkout@v5
Expand All @@ -55,7 +55,7 @@ jobs:
strategy:
matrix:
# amd64 only for windows, as no arm64 runners are available
python-version: ["3.9"]
python-version: ["3.10"]
steps:
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
Expand Down Expand Up @@ -102,34 +102,34 @@ jobs:
- uses: rust-lang/crates-io-auth-action@v1
id: auth

- name: Download Linux 3.9 wheels for x86-64
- name: Download Linux 3.10 wheels for x86-64
uses: actions/download-artifact@v6
with:
name: "wheels-linux-python-3.9-x86-64"
name: "wheels-linux-python-3.10-x86-64"
path: wheels-linux

- name: Download Linux 3.9 wheels for aarch64
- name: Download Linux 3.10 wheels for aarch64
uses: actions/download-artifact@v6
with:
name: "wheels-linux-python-3.9-aarch64"
name: "wheels-linux-python-3.10-aarch64"
path: wheels-linux

- name: Download MacOS 3.9 wheels for x86-64
- name: Download MacOS 3.10 wheels for x86-64
uses: actions/download-artifact@v6
with:
name: "wheels-macos-python-3.9-x86-64"
name: "wheels-macos-python-3.10-x86-64"
path: wheels-macos

- name: Download MacOS 3.9 wheels for aarch64
- name: Download MacOS 3.10 wheels for aarch64
uses: actions/download-artifact@v6
with:
name: "wheels-macos-python-3.9-aarch64"
name: "wheels-macos-python-3.10-aarch64"
path: wheels-macos

- name: Download Windows 3.9 wheels
- name: Download Windows 3.10 wheels
uses: actions/download-artifact@v6
with:
name: "wheels-windows-python-3.9"
name: "wheels-windows-python-3.10"
path: wheels-windows

- name: Download sdist
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ polars-core = { version = ">=0.50", default-features = false, features = [
"dtype-datetime",
"dtype-duration",
], optional = true }
pyo3 = { version = "^0.26", features = ["abi3-py39"], optional = true }
pyo3 = { version = "^0.26", features = ["abi3-py310"], optional = true }
pyo3-arrow = { version = "^0.14", default-features = false, optional = true }
pyo3-log = { version = "^0.13.2", optional = true }

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ df = pl.DataFrame(table) # Zero-copy via PyCapsule, no pyarrow needed

You'll need:
1. **[Rust](https://rustup.rs/)** - Rust stable or nightly
2. **[uv](https://docs.astral.sh/uv/getting-started/installation/)** - Fast Python package manager (will install Python 3.9+ automatically)
2. **[uv](https://docs.astral.sh/uv/getting-started/installation/)** - Fast Python package manager (will install Python 3.10+ automatically)
3. **[git](https://git-scm.com/)** - For version control
4. **[make](https://www.gnu.org/software/make/)** - For running development commands

Expand Down
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "fastexcel"
description = "A fast excel file reader for Python, written in Rust"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
Expand All @@ -16,7 +16,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down Expand Up @@ -68,7 +67,7 @@ module-name = "fastexcel._fastexcel"
features = ["__maturin"]

[tool.mypy]
python_version = "3.9"
python_version = "3.10"
follow_imports = "silent"
ignore_missing_imports = true
# A few custom options
Expand All @@ -84,11 +83,11 @@ log_cli_level = "INFO"

[tool.ruff]
line-length = 100
target-version = "py39"
target-version = "py310"

[tool.ruff.lint]
# Enable Pyflakes `E` and `F` codes by default.
select = ["E", "F", "I", "Q", "FA102"]
select = ["E", "F", "I", "Q", "FA102", "UP"]

[tool.uv]
# this ensures that `uv run` doesn't actually build the package; a `make`
Expand Down
35 changes: 15 additions & 20 deletions python/fastexcel/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
from __future__ import annotations

import sys
import typing
from typing import TYPE_CHECKING, Callable, Literal

if sys.version_info < (3, 10):
from typing_extensions import TypeAlias
else:
from typing import TypeAlias
from collections.abc import Callable
from typing import TYPE_CHECKING, Literal, TypeAlias

if TYPE_CHECKING:
import pandas as pd
Expand Down Expand Up @@ -101,7 +96,7 @@ def visible(self) -> SheetVisible:
"""The visibility of the sheet"""
return self._sheet.visible

def to_arrow(self) -> "pa.RecordBatch":
def to_arrow(self) -> pa.RecordBatch:
"""Converts the sheet to a pyarrow `RecordBatch`

Requires the `pyarrow` extra to be installed.
Expand All @@ -112,7 +107,7 @@ def to_arrow(self) -> "pa.RecordBatch":
)
return self._sheet.to_arrow()

def to_arrow_with_errors(self) -> "tuple[pa.RecordBatch, CellErrors | None]":
def to_arrow_with_errors(self) -> tuple[pa.RecordBatch, CellErrors | None]:
"""Converts the sheet to a pyarrow `RecordBatch` with error information.

Stores the positions of any values that cannot be parsed as the specified type and were
Expand All @@ -129,7 +124,7 @@ def to_arrow_with_errors(self) -> "tuple[pa.RecordBatch, CellErrors | None]":
return (rb, None)
return (rb, cell_errors)

def to_pandas(self) -> "pd.DataFrame":
def to_pandas(self) -> pd.DataFrame:
"""Converts the sheet to a Pandas `DataFrame`.

Requires the `pandas` extra to be installed.
Expand All @@ -139,7 +134,7 @@ def to_pandas(self) -> "pd.DataFrame":
# (see https://pandas.pydata.org/docs/reference/api/pandas.api.interchange.from_dataframe.html)
return self.to_arrow().to_pandas()

def to_polars(self) -> "pl.DataFrame":
def to_polars(self) -> pl.DataFrame:
"""Converts the sheet to a Polars `DataFrame`.

Uses the Arrow PyCapsule Interface for zero-copy data exchange.
Expand Down Expand Up @@ -225,7 +220,7 @@ def specified_dtypes(self) -> DTypeMap | None:
"""The dtypes specified for the table"""
return self._table.specified_dtypes

def to_arrow(self) -> "pa.RecordBatch":
def to_arrow(self) -> pa.RecordBatch:
"""Converts the table to a pyarrow `RecordBatch`

Requires the `pyarrow` extra to be installed.
Expand All @@ -236,7 +231,7 @@ def to_arrow(self) -> "pa.RecordBatch":
)
return self._table.to_arrow()

def to_pandas(self) -> "pd.DataFrame":
def to_pandas(self) -> pd.DataFrame:
"""Converts the table to a Pandas `DataFrame`.

Requires the `pandas` extra to be installed.
Expand All @@ -246,7 +241,7 @@ def to_pandas(self) -> "pd.DataFrame":
# (see https://pandas.pydata.org/docs/reference/api/pandas.api.interchange.from_dataframe.html)
return self.to_arrow().to_pandas()

def to_polars(self) -> "pl.DataFrame":
def to_polars(self) -> pl.DataFrame:
"""Converts the table to a Polars `DataFrame`.

Uses the Arrow PyCapsule Interface for zero-copy data exchange.
Expand Down Expand Up @@ -328,7 +323,7 @@ def load_sheet(
| None = None,
dtypes: DType | DTypeMap | None = None,
eager: Literal[True] = ...,
) -> "pa.RecordBatch": ...
) -> pa.RecordBatch: ...

def load_sheet(
self,
Expand All @@ -347,7 +342,7 @@ def load_sheet(
| None = None,
dtypes: DType | DTypeMap | None = None,
eager: bool = False,
) -> "ExcelSheet | pa.RecordBatch":
) -> ExcelSheet | pa.RecordBatch:
"""Loads a sheet by index or name.

:param idx_or_name: The index (starting at 0) or the name of the sheet to load.
Expand Down Expand Up @@ -469,7 +464,7 @@ def load_table(
| None = None,
dtypes: DType | DTypeMap | None = None,
eager: Literal[True] = ...,
) -> "pa.RecordBatch": ...
) -> pa.RecordBatch: ...

def load_table(
self,
Expand All @@ -488,7 +483,7 @@ def load_table(
| None = None,
dtypes: DType | DTypeMap | None = None,
eager: bool = False,
) -> "ExcelTable | pa.RecordBatch":
) -> ExcelTable | pa.RecordBatch:
"""Loads a table by name.

:param name: The name of the table to load.
Expand Down Expand Up @@ -574,7 +569,7 @@ def load_sheet_eager(
dtype_coercion: Literal["coerce", "strict"] = "coerce",
use_columns: list[str] | list[int] | str | None = None,
dtypes: DType | DTypeMap | None = None,
) -> "pa.RecordBatch":
) -> pa.RecordBatch:
"""Loads a sheet eagerly by index or name.

For xlsx files, this will be faster and more memory-efficient, as it will use
Expand Down Expand Up @@ -672,7 +667,7 @@ def read_excel(source: Path | str | bytes) -> ExcelReader:

:param source: The path to a file or its content as bytes
"""
if isinstance(source, (str, Path)):
if isinstance(source, str | Path):
source = expanduser(source)
return ExcelReader(_read_excel(source))

Expand Down
9 changes: 5 additions & 4 deletions python/fastexcel/_fastexcel.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

import typing
from typing import TYPE_CHECKING, Callable, Literal
from collections.abc import Callable
from typing import TYPE_CHECKING, Literal

if TYPE_CHECKING:
import pyarrow as pa
Expand Down Expand Up @@ -89,12 +90,12 @@ class _ExcelSheet:
@property
def visible(self) -> SheetVisible:
"""The visibility of the sheet"""
def to_arrow(self) -> "pa.RecordBatch":
def to_arrow(self) -> pa.RecordBatch:
"""Converts the sheet to a pyarrow `RecordBatch`

Requires the `pyarrow` extra to be installed.
"""
def to_arrow_with_errors(self) -> "tuple[pa.RecordBatch, CellErrors]":
def to_arrow_with_errors(self) -> tuple[pa.RecordBatch, CellErrors]:
"""Converts the sheet to a pyarrow `RecordBatch` with error information.

Stores the positions of any values that cannot be parsed as the specified type and were
Expand Down Expand Up @@ -148,7 +149,7 @@ class _ExcelTable:
@property
def specified_dtypes(self) -> DTypeMap | None:
"""The dtypes specified for the table"""
def to_arrow(self) -> "pa.RecordBatch":
def to_arrow(self) -> pa.RecordBatch:
"""Converts the table to a pyarrow `RecordBatch`

Requires the `pyarrow` extra to be installed.
Expand Down
Loading