Skip to content

Commit 1800abd

Browse files
committed
feat(duckdb): add to_xlsx implementation
1 parent 705aa16 commit 1800abd

File tree

2 files changed

+126
-4
lines changed

2 files changed

+126
-4
lines changed

ibis/backends/duckdb/__init__.py

+72-4
Original file line numberDiff line numberDiff line change
@@ -1104,9 +1104,20 @@ def read_xlsx(
11041104
11051105
See Also
11061106
--------
1107-
[DuckDB's `excel` extension docs](https://duckdb.org/docs/stable/extensions/excel.html)
1107+
[DuckDB's `excel` extension docs for reading](https://duckdb.org/docs/stable/extensions/excel.html#reading-xlsx-files)
1108+
1109+
Examples
1110+
--------
1111+
>>> import os
1112+
>>> import ibis
1113+
>>> t = ibis.memtable({"a": [1, 2, 3], "b": ["a", "b", "c"]})
1114+
>>> con = ibis.duckdb.connect()
1115+
>>> con.to_xlsx(t, "/tmp/test.xlsx", header=True)
1116+
>>> assert os.path.exists("/tmp/test.xlsx")
1117+
>>> t = con.read_xlsx("/tmp/test.xlsx")
1118+
>>> t.columns
1119+
('a', 'b')
11081120
"""
1109-
path = str(path)
11101121
table_name = util.gen_name("read_xlsx")
11111122

11121123
if sheet:
@@ -1119,13 +1130,70 @@ def read_xlsx(
11191130
sg.to_identifier(key).eq(sge.convert(val)) for key, val in kwargs.items()
11201131
]
11211132

1122-
self._load_extensions(["excel"])
1133+
self.load_extension("excel")
11231134
self._create_temp_view(
11241135
table_name,
1125-
sg.select(STAR).from_(self.compiler.f.read_xlsx(path, *options)),
1136+
sg.select(STAR).from_(self.compiler.f.read_xlsx(str(path), *options)),
11261137
)
11271138
return self.table(table_name)
11281139

1140+
def to_xlsx(
1141+
self,
1142+
expr: ir.Table,
1143+
/,
1144+
path: str | Path,
1145+
*,
1146+
sheet: str = "Sheet1",
1147+
header: bool = False,
1148+
params: Mapping[ir.Scalar, Any] | None = None,
1149+
**kwargs: Any,
1150+
):
1151+
"""Write a table to an Excel file.
1152+
1153+
Parameters
1154+
----------
1155+
expr
1156+
Ibis table expression to write to an excel file.
1157+
path
1158+
Excel output path.
1159+
sheet
1160+
The name of the sheet to write to, eg 'Sheet3'.
1161+
header
1162+
Whether to include the column names as the first row.
1163+
params
1164+
Additional Ibis expression parameters to pass to the backend's
1165+
write function.
1166+
kwargs
1167+
Additional arguments passed to the backend's write function.
1168+
1169+
Notes
1170+
-----
1171+
Requires DuckDB >= 1.2.0.
1172+
1173+
See Also
1174+
--------
1175+
[DuckDB's `excel` extension docs for writing](https://duckdb.org/docs/stable/extensions/excel.html#writing-xlsx-files)
1176+
1177+
Examples
1178+
--------
1179+
>>> import os
1180+
>>> import ibis
1181+
>>> t = ibis.memtable({"a": [1, 2, 3], "b": ["a", "b", "c"]})
1182+
>>> con = ibis.duckdb.connect()
1183+
>>> con.to_xlsx(t, "/tmp/test.xlsx")
1184+
>>> os.path.exists("/tmp/test.xlsx")
1185+
True
1186+
"""
1187+
self._run_pre_execute_hooks(expr)
1188+
query = self.compile(expr, params=params)
1189+
kwargs["sheet"] = sheet
1190+
kwargs["header"] = header
1191+
args = ["FORMAT 'xlsx'"]
1192+
args.extend(f"{k.upper()} {v!r}" for k, v in kwargs.items())
1193+
copy_cmd = f"COPY ({query}) TO {str(path)!r} ({', '.join(args)})"
1194+
self.load_extension("excel")
1195+
self.con.execute(copy_cmd).fetchall()
1196+
11291197
def attach(
11301198
self, path: str | Path, name: str | None = None, read_only: bool = False
11311199
) -> None:

ibis/expr/types/core.py

+54
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,60 @@ def to_parquet(
700700
self, path, params=params, **kwargs
701701
)
702702

703+
def to_xlsx(
704+
self,
705+
path: str | Path,
706+
/,
707+
*,
708+
sheet: str = "Sheet1",
709+
header: bool = False,
710+
params: Mapping[ir.Scalar, Any] | None = None,
711+
**kwargs: Any,
712+
):
713+
"""Write a table to an Excel file.
714+
715+
Parameters
716+
----------
717+
expr
718+
Ibis table expression to write to an excel file.
719+
path
720+
Excel output path.
721+
sheet
722+
The name of the sheet to write to, eg 'Sheet3'.
723+
header
724+
Whether to include the column names as the first row.
725+
params
726+
Additional Ibis expression parameters to pass to the backend's
727+
write function.
728+
kwargs
729+
Additional arguments passed to the backend's write function.
730+
731+
Notes
732+
-----
733+
Requires DuckDB >= 1.2.0.
734+
735+
See Also
736+
--------
737+
[DuckDB's `excel` extension docs for writing](https://duckdb.org/docs/stable/extensions/excel.html#writing-xlsx-files)
738+
739+
Examples
740+
--------
741+
>>> import os
742+
>>> import ibis
743+
>>> con = ibis.duckdb.connect()
744+
>>> t = con.create_table(
745+
... "t",
746+
... ibis.memtable({"a": [1, 2, 3], "b": ["a", "b", "c"]}),
747+
... temp=True,
748+
... )
749+
>>> t.to_xlsx("/tmp/test.xlsx")
750+
>>> os.path.exists("/tmp/test.xlsx")
751+
True
752+
"""
753+
self._find_backend(use_default=True).to_xlsx(
754+
self, path, sheet=sheet, header=header, params=params, **kwargs
755+
)
756+
703757
@experimental
704758
def to_parquet_dir(
705759
self,

0 commit comments

Comments
 (0)