Initial implementation of generic Excel-to-DB import tool

Supports .xls and .xlsx, Oracle and PostgreSQL via SQLAlchemy.
Includes CLI (run/inspect/generate-config), YAML config, auto schema
detection, and append/replace/upsert modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-13 11:31:47 +02:00
commit 8f7399de58
26 changed files with 663 additions and 0 deletions
+84
View File
@@ -0,0 +1,84 @@
import io
from pathlib import Path
import pandas as pd
import pytest
from excel_import.reader import ExcelReader
from excel_import.config import SheetConfig
@pytest.fixture
def xlsx_file(tmp_path: Path) -> Path:
path = tmp_path / "test.xlsx"
df = pd.DataFrame({
"Artikelnummer": ["A001", "A002", "A003"],
"Bezeichnung": ["Widget", "Gadget", None],
"Preis": [9.99, 14.50, 0.99],
})
df.to_excel(path, index=False)
return path
def test_sheet_names(xlsx_file: Path):
reader = ExcelReader(xlsx_file)
assert reader.sheet_names() == ["Sheet1"]
def test_read_basic(xlsx_file: Path):
reader = ExcelReader(xlsx_file)
df = reader.read(SheetConfig(sheet=0, target_table="t"))
assert len(df) == 3
assert list(df.columns) == ["Artikelnummer", "Bezeichnung", "Preis"]
def test_read_drops_empty_rows(tmp_path: Path):
path = tmp_path / "empty_rows.xlsx"
df = pd.DataFrame({"A": ["x", None, "y"], "B": [1, None, 3]})
df.to_excel(path, index=False)
reader = ExcelReader(path)
result = reader.read(SheetConfig(sheet=0, target_table="t"))
assert len(result) == 2
def test_read_column_rename(xlsx_file: Path):
from excel_import.config import ColumnMapping
cfg = SheetConfig(
sheet=0,
target_table="t",
columns=[
ColumnMapping(source="Artikelnummer", target="art_nr"),
ColumnMapping(source="Bezeichnung", target="bez"),
ColumnMapping(source="Preis", target="preis"),
],
)
reader = ExcelReader(xlsx_file)
df = reader.read(cfg)
assert "art_nr" in df.columns
assert "Artikelnummer" not in df.columns
def test_read_column_skip(xlsx_file: Path):
from excel_import.config import ColumnMapping
cfg = SheetConfig(
sheet=0,
target_table="t",
columns=[
ColumnMapping(source="Preis", target="Preis", skip=True),
],
)
reader = ExcelReader(xlsx_file)
df = reader.read(cfg)
assert "Preis" not in df.columns
def test_file_not_found():
with pytest.raises(FileNotFoundError):
ExcelReader("/nonexistent/path/file.xlsx")
def test_unsupported_extension(tmp_path: Path):
f = tmp_path / "data.csv"
f.write_text("a,b\n1,2")
with pytest.raises(ValueError, match="Unsupported"):
ExcelReader(f)