Skip to content

Commit

Permalink
feat(python): Support writing to file objects from write_excel (#20638
Browse files Browse the repository at this point in the history
)
  • Loading branch information
alexander-beedie authored Jan 10, 2025
1 parent 06362a0 commit 8abd261
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
4 changes: 2 additions & 2 deletions py-polars/polars/dataframe/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -3106,8 +3106,8 @@ def write_excel(
Parameters
----------
workbook : {str, Workbook}
String name or path of the workbook to create, BytesIO object to write
into, or an open `xlsxwriter.Workbook` object that has not been closed.
String name or path of the workbook to create, BytesIO object, file opened
in binary-mode, or an `xlsxwriter.Workbook` object that has not been closed.
If None, writes to a `dataframe.xlsx` workbook in the working directory.
worksheet : {str, Worksheet}
Name of target worksheet or an `xlsxwriter.Worksheet` object (in which
Expand Down
22 changes: 15 additions & 7 deletions py-polars/polars/io/spreadsheet/_write_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from collections.abc import Sequence
from io import BytesIO
from os import PathLike
from pathlib import Path
from typing import TYPE_CHECKING, Any, overload

Expand Down Expand Up @@ -594,13 +595,20 @@ def _xl_setup_workbook(
if isinstance(workbook, BytesIO):
wb, ws, can_close = Workbook(workbook, workbook_options), None, True
else:
file = Path("dataframe.xlsx" if workbook is None else workbook)
wb = Workbook(
(file if file.suffix else file.with_suffix(".xlsx"))
.expanduser()
.resolve(strict=False),
workbook_options,
)
if workbook is None:
file = Path("dataframe.xlsx")
elif isinstance(workbook, str):
file = Path(workbook)
else:
file = workbook

if isinstance(file, PathLike):
file = (
(file if file.suffix else file.with_suffix(".xlsx"))
.expanduser()
.resolve(strict=False)
)
wb = Workbook(file, workbook_options)
ws, can_close = None, True

if ws is None:
Expand Down
33 changes: 32 additions & 1 deletion py-polars/tests/unit/io/test_spreadsheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from collections import OrderedDict
from datetime import date, datetime
from io import BytesIO
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable

import pytest
Expand All @@ -16,7 +17,6 @@

if TYPE_CHECKING:
from collections.abc import Sequence
from pathlib import Path

from polars._typing import ExcelSpreadsheetEngine, SchemaDict, SelectorType

Expand Down Expand Up @@ -859,6 +859,37 @@ def test_excel_write_compound_types(engine: ExcelSpreadsheetEngine) -> None:
]


@pytest.mark.parametrize("engine", ["xlsx2csv", "openpyxl", "calamine"])
def test_excel_write_to_file_object(
engine: ExcelSpreadsheetEngine, tmp_path: Path
) -> None:
tmp_path.mkdir(exist_ok=True)

df = pl.DataFrame({"x": ["aaa", "bbb", "ccc"], "y": [123, 456, 789]})

# write to bytesio
xls = BytesIO()
df.write_excel(xls, worksheet="data")
assert_frame_equal(df, pl.read_excel(xls, engine=engine))

# write to file path
path = Path(tmp_path).joinpath("test_write_path.xlsx")
df.write_excel(path, worksheet="data")
assert_frame_equal(df, pl.read_excel(xls, engine=engine))

# write to file path (as string)
path = Path(tmp_path).joinpath("test_write_path_str.xlsx")
df.write_excel(str(path), worksheet="data")
assert_frame_equal(df, pl.read_excel(xls, engine=engine))

# write to file object
path = Path(tmp_path).joinpath("test_write_file_object.xlsx")
with path.open("wb") as tgt:
df.write_excel(tgt, worksheet="data")
with path.open("rb") as src:
assert_frame_equal(df, pl.read_excel(src, engine=engine))


@pytest.mark.parametrize("engine", ["xlsx2csv", "openpyxl", "calamine"])
def test_excel_read_no_headers(engine: ExcelSpreadsheetEngine) -> None:
df = pl.DataFrame(
Expand Down

0 comments on commit 8abd261

Please sign in to comment.