Add ODF (.ods) support via odfpy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,14 @@
|
|||||||
# excel-import
|
# excel-import
|
||||||
|
|
||||||
Generisches Kommandozeilen-Tool zum Import von Excel-Dateien (`.xls` und `.xlsx`) in Oracle- und PostgreSQL-Datenbanken.
|
Generisches Kommandozeilen-Tool zum Import von Tabellendateien in Oracle- und PostgreSQL-Datenbanken.
|
||||||
|
|
||||||
|
Unterstützte Formate:
|
||||||
|
|
||||||
|
| Format | Endung | Paket |
|
||||||
|
|--------|--------|-------|
|
||||||
|
| Excel 97–2003 | `.xls` | `xlrd` |
|
||||||
|
| Excel 2007+ | `.xlsx`, `.xlsm`, `.xlsb` | `openpyxl` |
|
||||||
|
| OpenDocument (LibreOffice) | `.ods` | `odfpy` |
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -5,8 +5,17 @@ import pandas as pd
|
|||||||
from .config import SheetConfig
|
from .config import SheetConfig
|
||||||
|
|
||||||
|
|
||||||
|
_ENGINES = {
|
||||||
|
".xls": "xlrd",
|
||||||
|
".xlsx": "openpyxl",
|
||||||
|
".xlsm": "openpyxl",
|
||||||
|
".xlsb": "openpyxl",
|
||||||
|
".ods": "odf",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _engine_for(path: Path) -> str:
|
def _engine_for(path: Path) -> str:
|
||||||
return "xlrd" if path.suffix.lower() == ".xls" else "openpyxl"
|
return _ENGINES[path.suffix.lower()]
|
||||||
|
|
||||||
|
|
||||||
class ExcelReader:
|
class ExcelReader:
|
||||||
@@ -14,7 +23,7 @@ class ExcelReader:
|
|||||||
self.path = Path(path)
|
self.path = Path(path)
|
||||||
if not self.path.exists():
|
if not self.path.exists():
|
||||||
raise FileNotFoundError(f"Excel file not found: {self.path}")
|
raise FileNotFoundError(f"Excel file not found: {self.path}")
|
||||||
if self.path.suffix.lower() not in {".xls", ".xlsx", ".xlsm", ".xlsb"}:
|
if self.path.suffix.lower() not in _ENGINES:
|
||||||
raise ValueError(f"Unsupported file type: {self.path.suffix}")
|
raise ValueError(f"Unsupported file type: {self.path.suffix}")
|
||||||
|
|
||||||
def sheet_names(self) -> list[str]:
|
def sheet_names(self) -> list[str]:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ dependencies = [
|
|||||||
"pandas>=2.0",
|
"pandas>=2.0",
|
||||||
"openpyxl>=3.1",
|
"openpyxl>=3.1",
|
||||||
"xlrd>=2.0",
|
"xlrd>=2.0",
|
||||||
|
"odfpy>=1.4",
|
||||||
"sqlalchemy>=2.0",
|
"sqlalchemy>=2.0",
|
||||||
"psycopg2-binary>=2.9",
|
"psycopg2-binary>=2.9",
|
||||||
"oracledb>=2.0",
|
"oracledb>=2.0",
|
||||||
|
|||||||
@@ -72,6 +72,18 @@ def test_read_column_skip(xlsx_file: Path):
|
|||||||
assert "Preis" not in df.columns
|
assert "Preis" not in df.columns
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_ods(tmp_path: Path):
|
||||||
|
pytest.importorskip("odf")
|
||||||
|
path = tmp_path / "test.ods"
|
||||||
|
df = pd.DataFrame({"Name": ["Alice", "Bob"], "Wert": [1, 2]})
|
||||||
|
df.to_excel(path, index=False, engine="odf")
|
||||||
|
|
||||||
|
reader = ExcelReader(path)
|
||||||
|
result = reader.read(SheetConfig(sheet=0, target_table="t"))
|
||||||
|
assert len(result) == 2
|
||||||
|
assert list(result.columns) == ["Name", "Wert"]
|
||||||
|
|
||||||
|
|
||||||
def test_file_not_found():
|
def test_file_not_found():
|
||||||
with pytest.raises(FileNotFoundError):
|
with pytest.raises(FileNotFoundError):
|
||||||
ExcelReader("/nonexistent/path/file.xlsx")
|
ExcelReader("/nonexistent/path/file.xlsx")
|
||||||
|
|||||||
Reference in New Issue
Block a user