Map Excel cells to Python objects using a DSL
Project description
excel-cell-mapper
A Python library for mapping Excel spreadsheet cells to Python dictionaries using a declarative DSL.
Instead of writing boilerplate openpyxl code to extract every cell manually, you define a schema once and let ExcelMapper do the rest.
Features
- Declarative DSL — describe your mapping as a plain Python dict
- Flexible cell references — single cells, ranges, cross-sheet references
- Rich schema patterns — static keys, dynamic keys, nested dicts, list ranges, and table schemas
- Type conversion — Excel values automatically become
int,float,bool,str,datetime - Configurable empty cell handling — keep as
None, omit from output, or replace with"" - Custom transformers — post-process any cell value with your own function
- Multiple input sources — file path,
bytes, or anyBinaryIOstream - Context manager support — works with
withstatements
Requirements
- Python 3.13+
- openpyxl 3.1.5+
Installation
pip install excel-cell-mapper
Or with uv:
uv add excel-cell-mapper
Quick Start
Given an Excel file like:
| A | B | |
|---|---|---|
| 1 | Name | Alice |
| 2 | Age | 30 |
| 3 | alice@example.com |
from excel_cell_mapper import ExcelMapper
with ExcelMapper("form.xlsx") as mapper:
result = mapper.map({
"name": "B1",
"age": "B2",
"email": "B3",
})
# result == {"name": "Alice", "age": 30, "email": "alice@example.com"}
Schema Patterns
Static key mapping
Map fixed field names to cell references:
mapper.map({
"company": "B1",
"address": "B2",
"phone": "B3",
})
Nested dict
Build hierarchical output by nesting schemas:
mapper.map({
"company": "B1",
"contact": {
"name": "B3",
"email": "B4",
"phone": "B5",
},
})
Dynamic keys
When both key and value are cell references, the key is read from the spreadsheet at runtime:
# Cell A1 contains "Revenue", B1 contains 1_200_000
mapper.map({
"A1": "B1", # → {"Revenue": 1200000}
"A2": "B2",
"A3": "B3",
})
List from a range
Wrap a range string in a list to flatten the cells into a Python list:
mapper.map({
"tags": ["A1:A5"],
})
# → {"tags": ["alpha", "beta", "gamma", ...]}
Table mapping
Use $range and $schema to iterate rows (or columns) and produce a list of dicts:
mapper.map({
"orders": {
"$range": "A2:C6",
"$schema": {"id": 0, "product": 1, "quantity": 2},
},
})
# → {"orders": [{"id": 1, "product": "Widget", "quantity": 10}, ...]}
Set "$direction": "column" to iterate columns instead of rows.
Set "$skip_empty": True to silently drop all-empty rows/columns.
Cross-sheet references
Prefix any cell or range with the sheet name and !:
mapper.map({
"summary": "Summary!B2",
"detail": {
"$range": "Detail!A2:C20",
"$schema": {"sku": 0, "qty": 1, "price": 2},
},
})
Constructor Options
ExcelMapper(
source, # str | Path | bytes | BinaryIO
*,
default_sheet=None, # str | int | None
empty_cell="none", # "none" | "omit" | "empty"
date_format="datetime", # "datetime" | "iso" | "local"
transform=None, # Callable[[CellValue, CellContext], object] | None
)
| Option | Default | Description |
|---|---|---|
default_sheet |
None (first sheet) |
Sheet name or 0-based index used when a cell reference has no sheet qualifier |
empty_cell |
"none" |
How empty cells appear in output: "none" → None, "omit" → key excluded, "empty" → "" |
date_format |
"datetime" |
Date cell output: "datetime" → datetime object, "iso" → ISO 8601 string, "local" → locale string |
transform |
None |
A function (value, context) -> object applied to every cell value before it enters the result |
API Reference
mapper.map(schema, *, sheet=None) -> dict
Resolve schema against the workbook and return a dict.
sheet overrides default_sheet for this call only.
mapper.map_many(schemas, *, sheet=None) -> dict[str, dict]
Run multiple schemas in one call:
results = mapper.map_many({
"header": {"title": "A1", "date": "A2"},
"body": {"$range": "A5:C20", "$schema": {"col1": 0, "col2": 1, "col3": 2}},
})
mapper.get_cell(cell_ref) -> CellValue
Read a single cell value directly, bypassing any schema:
value = mapper.get_cell("Sheet2!D7")
mapper.get_range(range_ref) -> list[list[CellValue]]
Read a range as a 2-D list of rows:
grid = mapper.get_range("A1:C3")
# [[row0col0, row0col1, row0col2], [row1col0, ...], ...]
mapper.get_sheet_names() -> list[str]
Return all sheet names in workbook order.
Custom Transformers
The transform option lets you post-process every resolved cell value.
The callback receives the raw value and a CellContext with location metadata:
from excel_cell_mapper import ExcelMapper, CellContext
def trim_strings(value, ctx: CellContext):
if isinstance(value, str):
return value.strip()
return value
with ExcelMapper("report.xlsx", transform=trim_strings) as mapper:
result = mapper.map({"title": "B1", "notes": "B2"})
CellContext fields:
| Field | Type | Description |
|---|---|---|
cell_ref |
str |
Bare cell reference, e.g. "B1" |
sheet_name |
str |
Name of the sheet |
col_index |
int |
0-based column index |
row_index |
int |
0-based row index |
Error Handling
All exceptions inherit from ExcelMapperError:
| Exception | Raised when |
|---|---|
CellNotFoundError |
Cell reference is out of bounds |
SheetNotFoundError |
Referenced sheet does not exist |
InvalidSchemaError |
Schema structure is malformed |
ParseError |
Excel file cannot be parsed |
from excel_cell_mapper import ExcelMapper, SheetNotFoundError, CellNotFoundError
try:
result = mapper.map({"value": "MissingSheet!A1"})
except SheetNotFoundError as e:
print(f"Sheet not found: {e}")
except CellNotFoundError as e:
print(f"Cell out of range: {e}")
Type Conversion
Excel cell types are automatically converted to Python types:
| Excel type | Python type |
|---|---|
| Integer | int |
| Decimal | float |
| Text | str |
| Boolean | bool |
| Date / Time | datetime.datetime (or str if date_format is set) |
| Empty | None (or "" / omitted — see empty_cell) |
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file excel_cell_mapper-0.1.0.tar.gz.
File metadata
- Download URL: excel_cell_mapper-0.1.0.tar.gz
- Upload date:
- Size: 37.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e36cc637b6a1fbcd195ef67c03146ea18e2ad162e9b94a7246197fb78f490ef
|
|
| MD5 |
504e240f843468ae6f3372012c679e89
|
|
| BLAKE2b-256 |
f3abbdcda0b8640c441506a8684ef3fedd15ee02cdeee1b1a7a9fa1fbf4d33c1
|
File details
Details for the file excel_cell_mapper-0.1.0-py3-none-any.whl.
File metadata
- Download URL: excel_cell_mapper-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ceade887a49878384f6bba472b7603ca3c497225e885b2693c6b36c0b2ff157
|
|
| MD5 |
f4c29ff2b0f0fa083568910e6e548226
|
|
| BLAKE2b-256 |
defd59e2b0b285d69f704248d1622cb4412ea454606fa4f0cec19d968a01d354
|