A small Python library for writing formatted Excel files.
Project description
sheetkit
sheetkit is a compact Python library for everyday Excel automation:
format tables, write whole sheets, patch only selected cells, and read sheets or ranges
back as plain list[list[Any]].
It is for the practical middle ground between "raw openpyxl styling is too verbose"
and "I still need real .xlsx files, existing workbook updates, formulas, templates,
themes, and controlled formatting."
Use it when you need to:
- turn a Python table into a styled Excel worksheet quickly;
- read a whole sheet or a rectangular range (
"A1:B2"or((0, 0), (1, 1))); - update an existing workbook without rebuilding everything;
- patch only specific cells while preserving formulas, styles, validation, layout, and protection;
- reuse Office-like themes and formatter presets across reports.
Quick Example
from sheetkit import read_sheet, write_sheet
write_sheet(
data_table=[
["Name", "Qty", "Price"],
["Apples", 10, 1.25],
["Bananas", 7, 0.90],
],
file_name="report.xlsx",
sheet_name="Sales",
formatter={"row": {0: {"bold": True}}, "col": {}},
)
values = read_sheet("report.xlsx", sheet_name="Sales", sheet_range="A1:B2")
Custom Formatter In One Dictionary
You can format a worksheet without presets by passing a plain dictionary:
row: styles by zero-based row index.col: styles by zero-based column index.0,1, ...: explicit row/column styles.-1: default style.-2: zebra style.priority:"row"or"col"decides which side wins when both define the same cell.
from sheetkit import write_sheet
formatter = {
"priority": "row",
"row": {
0: {
"bold": True,
"align": "center",
"pattern": "solid",
"fg_color": "4472C4",
"font_color": "FFFFFF",
},
-1: {"border_bottom": 1},
-2: {"pattern": "solid", "fg_color": "F2F6FC"},
},
"col": {
1: {"num_format": "#,##0.00", "align": "right"},
},
}
write_sheet(
data_table=[["Item", "Amount"], ["Apples", 1234.5], ["Bananas", 900]],
file_name="custom.xlsx",
formatter=formatter,
header=1,
)
What it does
- writes
.xlsxfiles fromlist[list[Any]] - reads full worksheets and ranges into
list[list[Any]] - can save
.xltxExcel templates throughopenpyxl - supports both
xlsxwriterandopenpyxl - uses a logical, engine-independent formatting model
- supports row formats, column formats, zebra rows, headers, number formats, borders, fonts, fills, and alignment
- supports Excel / Office themes
- can import
.thmxtheme files - can extract formatter JSON from a manually styled Excel worksheet
- can replace, update, or patch sheets in existing workbooks through
openpyxl
Terms
This README uses three key terms:
theme: a color/font theme (typically from.thmxorpresets/themesJSON).formatter: runtimeSheetFormatSpecused bywrite_sheet(...).formatter JSON: serialized formatter file that storesrow/colspecs.
Why use it
Working with Excel formatting directly in openpyxl is powerful, but verbose and often awkward. sheetkit keeps the useful part of that power while making the common workflow much simpler:
- describe table formatting with plain Python dictionaries
- choose an engine
- save the workbook
Formatting Layers
sheetkit formatting works through three layers. Keeping this model in mind makes the API much easier to navigate.
Layer 1: Theme Presets
Theme presets define color/font foundations.
Typical sources:
- built-in JSON theme presets in
sheetkit/presets/themes - imported
.thmxthemes (export_excel_theme,export_excel_themes,import_theme,import_themes) - runtime theme resolution (
get_theme)
Layer 2: Formatter JSON
Typical sources:
- extracted from styled worksheet (
extract_formatter_from_sheet,extract_formatter_to_file) - saved/loaded as JSON (
save_formatter,load_formatter)
Layer 3: Runtime Formatter
Runtime formatter (SheetFormatSpec) is what actually styles a specific worksheet during save.
Typical builders:
build_formatter_from_theme(...)for direct theme->runtime conversion (variant=0base,variant=1..Nfrom theme variants)
Then apply it with:
write_sheet(...)
Recommended path
- Start with theme (
get_theme) or a formatter JSON (load_formatter/ extraction). - Build runtime formatter (
build_formatter_from_theme). - Save output (
write_sheet).
Goal -> Function(s) Cheat Sheet
| Goal | Use |
|---|---|
| Save a table with a ready formatter | write_sheet |
Read a sheet/range as list[list[Any]] |
read_sheet |
| Build runtime formatter from theme data | build_formatter_from_theme |
| Load theme by name/path | get_theme |
Import one .thmx theme into runtime cache |
import_theme |
Import all .thmx themes from folder into runtime cache |
import_themes |
| Load formatter JSON | load_formatter |
| Load built-in formatter preset by name | load_format_preset |
| Save formatter JSON | save_formatter |
| List built-in theme preset names | list_preset_themes |
| List built-in formatter preset names | list_preset_formatters |
| Extract formatter from styled worksheet (in-memory) | extract_formatter_from_sheet |
| Extract formatter from styled worksheet to JSON | extract_formatter_to_file |
| Extract formatter from a fully styled worksheet range | extract_formatter_range_from_sheet |
Read .thmx into memory |
load_thmx_theme |
Convert .thmx to JSON preset |
export_excel_theme |
| Normalize/resolve color values | color_to_hex, normalize_color_value |
Quick Start
from pathlib import Path
from sheetkit import build_formatter_from_theme, get_theme, write_sheet
data = [
["Name", "Price", "Discount", "Total"],
["Apples", 10.5, 0.05, 9.98],
["Bananas", 12.0, 0.10, 10.80],
["Oranges", 12.1, 0.00, 12.10],
]
formatter = build_formatter_from_theme(
get_theme("office_theme"),
types=["@", "#,##0.00", "0.00%", "#,##0.00"],
header=1,
zebra=True,
)
path = write_sheet(
data_table=data,
file_name=Path("fruits.xlsx"),
sheet_name="Invoice",
formatter=formatter,
header=1,
)
print(path)
from sheetkit import read_sheet
all_values = read_sheet("fruits.xlsx")
part_values = read_sheet("fruits.xlsx", sheet_range="A1:B2")
part_values2 = read_sheet("fruits.xlsx", sheet_range=((0, 0), (1, 1)))
Core API
write_sheet(...) -> Path
Main entry point for saving a formatted workbook.
save_formatted_xlsx(...) is kept as a backward-compatible alias.
write_sheet(
data_table: list[list[Any]],
file_name: str | PathLike,
sheet_name: str | None = None,
formatter: SheetFormatSpec | None = None,
mode: ModeLiteral = "auto",
engine: EngineLiteral = "auto",
header: int | None = None,
backup: bool = True,
patch_skip_none: bool = True,
patch_skip_formulas: bool = True,
patch_skip_locked: bool = True,
patch_range: str | tuple[tuple[int, int], tuple[int, int]] | None = None,
patch_strict_range: bool = False,
as_template: bool = False,
progressor: PercentProgress | None = None,
) -> Path
It returns the saved file path and raises exceptions on invalid input or save errors.
header behavior:
None(default): do not touch formatter indexes0/1; keep formatter header mapping as-is.0,1,2: explicit runtime override for header depth on the priority axis.
Public API Reference
All symbols exported from sheetkit/__init__.py are documented in:
This includes:
- every exported function
- parameter-by-parameter descriptions
- exported type aliases and literals
Quick export list:
write_sheetread_sheetbuild_formatter_from_themeresolve_formatter_colorsget_themeimport_theme,import_themesload_formatter,save_formatterload_format_presetlist_preset_themes,list_preset_formattersload_preset_file,save_preset_fileextract_formatter_from_sheet,extract_formatter_to_fileextract_formatter_range_from_sheet,extract_formatter_range_to_fileexport_excel_themeexport_excel_themesload_thmx_themecolor_to_hexnormalize_color_valueFormatDict,AxisFormatSpec,SheetFormatSpec,ColorInput,EngineLiteral,ModeLiteral
build_formatter_from_theme(..., color_mode="hex" | "ref") -> SheetFormatSpec
color_mode="ref" stores colors as theme references (Accent1, Accent1+40, Dark2, etc.) for readable JSON.
Use resolve_formatter_colors(...) to convert such formatter to pure HEX colors when needed for performance.
Theme import helpers
load_thmx_theme(...)import_theme(...)import_themes(...)get_theme(...)
Formatter extraction helpers
extract_formatter_from_sheet(...)extract_formatter_to_file(...)save_formatter(...)
Formatting Model
sheetkit uses a logical formatting model that is independent of the output engine.
FormatDict
A dictionary describing a single cell style.
Example:
{
"bold": True,
"align": "center",
"pattern": "solid",
"fg_color": "4472C4",
"font_color": "FFFFFF",
"border_bottom": 1,
}
AxisFormatSpec
A dictionary keyed by row or column index:
{
-1: {"border_bottom": 1},
-2: {"pattern": "solid", "fg_color": "F7F7F7"},
0: {"bold": True, "align": "center"},
}
Special keys:
-1: default format-2: second zebra format
SheetFormatSpec
A sheet formatter contains both row and column format maps:
{
"priority": "row", # or "col"
"row": {...},
"col": {...},
}
priority controls which axis wins when row and column formats define the same property:
"row": row format overrides column format;headermeans top header rows;typesapplies by columns."col": column format overrides row format;headermeans left header columns;typesapplies by rows.
If priority is missing, sheetkit treats the formatter as "row" for compatibility with existing formatter JSON.
Supported Formatting Keys
Supported logical style keys include:
align,valign,text_wrap,indent,shrink_to_fit,text_rotationborder,border_left,border_right,border_top,border_bottomborder_color,border_left_color,border_right_color,border_top_color,border_bottom_colorpattern,fg_color,bg_colorfont_name,font_size,bold,italic,underline,strike,font_colornum_formatlocked,hidden
FormatDict Reference
| Property | Type | Allowed values | Nullable |
|---|---|---|---|
align |
str |
"fill", "left", "justify", "center", "right" |
yes |
valign |
str |
"bottom", "justify", "distributed", "center", "top" |
yes |
text_wrap |
bool |
yes | |
indent |
int |
yes | |
shrink_to_fit |
bool |
yes | |
text_rotation |
int |
yes | |
border |
int | str |
0, 1, 2, "thin", "medium" |
yes |
border_color |
str | tuple |
see color input formats | yes |
border_left |
int | str |
0, 1, 2, "thin", "medium" |
yes |
border_right |
int | str |
0, 1, 2, "thin", "medium" |
yes |
border_top |
int | str |
0, 1, 2, "thin", "medium" |
yes |
border_bottom |
int | str |
0, 1, 2, "thin", "medium" |
yes |
border_left_color |
str | tuple |
see color input formats | yes |
border_right_color |
str | tuple |
see color input formats | yes |
border_top_color |
str | tuple |
see color input formats | yes |
border_bottom_color |
str | tuple |
see color input formats | yes |
pattern |
str |
"solid" |
yes |
fg_color |
str | tuple |
see color input formats | yes |
bg_color |
str | tuple |
see color input formats | yes |
font_name |
str |
yes | |
font_size |
int | float |
yes | |
bold |
bool |
yes | |
italic |
bool |
yes | |
underline |
bool | str |
False, True, "single", "double", "singleAccounting", "doubleAccounting" |
yes |
strike |
bool |
yes | |
font_color |
str | tuple |
see color input formats | yes |
num_format |
str |
Excel number format string | yes |
locked |
bool |
yes | |
hidden |
bool |
yes |
Color Input
Colors can be given in several forms.
Plain colors
- HEX:
"FFCC99","#FFCC99" - RGB:
(255, 204, 153) - RGBA:
(255, 204, 153, 255) - CSS names:
"steelblue","tomato","lightgray"
Theme color references
sheetkit also supports Excel-style theme color references:
("wisp", "accent3")"wisp:accent3""Accent1+40""Background2-20""Text1""Hyperlink"
If no theme is explicitly specified in the reference string, office_theme is used by default.
Predefined Themes
Built-in preset theme names currently included in the package:
banded, basis, berlin, circuit, damask, dividend, droplet, facet, frame, gallery, integral, ion, ion_boardroom, main_event, mesh, metropolitan, office_theme, organic, parallax, parcel, retrospect, savon, slate, slice, tete, vapor_trail, view, wisp, wood_type.
Built-in formatter preset names currently included in the package (each has both _row and _col variants):
banded, basis, berlin, circuit, damask, dividend, droplet, facet, frame, gallery, integral, ion, ion_boardroom, main_event, mesh, metropolitan, office_theme, organic, parallax, parcel, retrospect, savon, slate, slice, tete, vapor_trail, view, wisp, wood_type.
Quick access helpers:
list_preset_themes()list_preset_formatters()load_format_preset(name, priority="row" | "col")
Theme resolution order (get_theme)
get_theme(theme, auto_import=True) resolves in this order:
- explicit file path (
.jsonor.thmx) - built-in preset file in
sheetkit/presets/themes - user Office theme folder (
%APPDATA%\Microsoft\Templates\Document Themes) - Office installation theme folder (
...\Document Themes 16) - runtime cache (
THEMES)
Formatter preset loading
load_format_preset(name, priority) is intentionally strict:
- it loads only from
sheetkit/presets/formatters - it resolves
<name>_row.jsonor<name>_col.json - it does not read external/user paths
Extracting Formatters from Excel
sheetkit can also extract formatter from a worksheet that you styled manually in Excel.
This is useful when:
- you want to design the look visually in Excel first
- you want to reuse that look later from Python
- you do not want to write formatter JSON by hand
Recommended worksheet layout
For row-priority formatting, prepare a small sample block that starts at a known cell, usually A1:
- header row 1
- header row 2
- first data row style
- second zebra row style
- row and header styles are read from the first column of the sample block
- column formats currently import only
num_format sheet_nameis optional; if omitted, the active worksheet is usedcolumnsis required and defines how many sample columns to scan
For column-priority formatting, use the same idea rotated 90 degrees:
- header column 1
- header column 2
- first data column style
- second zebra column style
- column and header styles are read from the first row of the sample block
- row formats currently import only
num_format rowsis required and defines how many sample rows to scan
Example
from pathlib import Path
from sheetkit import extract_formatter_to_file
extract_formatter_to_file(
file_name=Path("styled_template.xlsx"),
json_file=Path("imported_blue.json"),
columns=6,
start_cell="A1",
header=2,
zebra=True,
priority="row",
name="imported_blue",
)
The resulting JSON file can then be loaded with sheetkit.themes.load_formatter(...).
Extracting full row/column ranges
When every row or every column has its own style, use extract_formatter_range_from_sheet(...).
It copies styles from a rectangular range rather than interpreting the range as header/base/zebra samples.
priority="row": row styles are read from the first column; columnnum_formatvalues are scanned down each column.priority="col": column styles are read from the first row; rownum_formatvalues are scanned across each row.- the last row/column style is stored as the default style
-1.
CLI Tools
Two small command-line wrappers are available in scripts.
extract_formatter
Wraps extract_formatter_to_file(...) and extract_formatter_range_to_file(...).
python scripts/extract_formatter.py <file_name> <json_file> [-m sample] -p row -cs <columns> [-sn <sheet_name>] [-sc <start_cell>] [-hr <header>] [-z | --no-zebra] [-name <name>]
python scripts/extract_formatter.py <file_name> <json_file> [-m sample] -p col -rs <rows> [-sn <sheet_name>] [-sc <start_cell>] [-hr <header>] [-z | --no-zebra] [-name <name>]
python scripts/extract_formatter.py <file_name> <json_file> -m range -p row -rs <rows> -cs <columns> [-sn <sheet_name>] [-sc <start_cell>] [-name <name>]
python scripts/extract_formatter.py <file_name> <json_file> -m range -p col -rs <rows> -cs <columns> [-sn <sheet_name>] [-sc <start_cell>] [-name <name>]
Example:
python scripts/extract_formatter.py styled_template.xlsx imported_blue.json -cs 6 -sn StyledTable -hr 2 -z -name imported_blue
python scripts/extract_formatter.py scripts/Columns.xlsx scripts/__temp__/columns_range.json -m range -p col -rs 12 -cs 7 -name columns_range
format_xlsx
Loads a formatter JSON and applies it via write_sheet(...).
Supported input formats:
.csv.xlsx.xlsif optional dependencyxlrdis installed
python scripts/format_xlsx.py <input_file> <output_file> -ft <formatter_json> [-hr 0|1|2] [--zebra | --no-zebra]
Example:
python scripts/format_xlsx.py data.csv formatted.xlsx -ft my_formatter.json -hr 1 -z
format_xlsx override behavior:
- if
-hris omitted, formatter header indexes are kept as-is; - if
--zebra/--no-zebrais omitted, formatter zebra mapping is kept as-is.
export_excel_themes
Exports Office .thmx themes to theme JSON. With -cm, it also builds self-contained formatter JSON files
from those themes and saves them into <save-folder>/formatters as both
<theme>_row.json and <theme>_col.json.
python scripts/export_excel_themes.py -of -uf -sf sheetkit/presets/themes
python scripts/export_excel_themes.py -of -sf exported_themes -cm ref
Engines and Modes
Engines
"auto": default; usesxlsxwriterfor new workbooks andopenpyxlfor existing workbooks,replace/update/patchmodes, and templates"xlsxwriter": best choice for creating new files quickly"openpyxl": required for updating existing workbooks and for.xltx
Modes
"new": create a new workbook file; raisesFileExistsErrorif the target file already exists"replace": replace a sheet in an existing workbook"update": update an existing sheet in an existing workbook"patch": patch existing sheet cells only (safe merge mode: skipsNone, formulas, and locked cells on protected sheets by default)"auto": choose mode automatically based on file existence and engine
Notes:
replace,update, andpatchare supported only withopenpyxlas_template=Trueis supported only withopenpyxl
Patch Mode Behavior
mode="patch" is designed for calculation templates where formulas/styles/protection already exist on the sheet.
- The update anchor is
A1:data_table[r][c]maps to Excel cell(r+1, c+1). data_tablesize can be smaller or larger than the existing used range.- Smaller patch: only provided cells are considered; other cells are unchanged.
- Larger patch: new rows/cells can be created below/right of the old range.
- With
patch_skip_none=True(default),Nonemeans "skip cell", not "clear cell". - With
patch_skip_formulas=True(default), formula cells are not overwritten. - With
patch_skip_locked=True(default), locked cells on protected sheets are not overwritten. - Optional
patch_range="D2:E7"changes the patch anchor toD2. - You can also pass zero-based tuple bounds:
patch_range=((0, 0), (1, 1))is equivalent toA1:B2. - With
mode="auto"and an existing workbook,patch_rangeselects patch behavior instead of replacing the sheet. - With
patch_strict_range=False(default), out-of-range patch values are silently ignored. - With
patch_strict_range=True, sheetkit raises an error if data does not fit intopatch_range.
Example:
write_sheet(
data_table=[
[None], # do not touch A1
[123], # write A2
[456], # write A3
],
file_name="report.xlsx",
sheet_name="Calc",
formatter={"row": {}, "col": {}},
engine="openpyxl",
mode="patch",
)
write_sheet(
data_table=[[11], [21], [32]],
file_name="report.xlsx",
sheet_name="Calc",
formatter={"row": {}, "col": {}},
engine="openpyxl",
mode="patch",
patch_range="D2:D7", # writes D2, D3, D4
)
write_sheet(
data_table=[[11, 12], [21, 22]],
file_name="report.xlsx",
sheet_name="Calc",
formatter={"row": {}, "col": {}},
engine="openpyxl",
mode="patch",
patch_range=((0, 0), (1, 1)), # writes A1:B2
)
Merge Rules
When both row and column styles define the same property, formatter priority decides which axis wins:
priority="row": row formatting has priority over column formatting.priority="col": column formatting has priority over row formatting.
This is important when combining:
- default column formats
- explicit column formats
- default row formats
- explicit row formats
Examples
- 01_basic_usage.py — minimal workbook creation
- 02_head_and_num_columns.py — headers, zebra rows, number formats
- 03_styled_columns.py — manual formatter and theme colors in columns
- 04_styled_rows.py — manual formatter and row-specific styling
- 05_add_update_sheet.py — updating an existing sheet with
openpyxl - 06_save_as_template.py — saving
.xltxtemplates - 07_extract_formatter_from_sheet.py — extracting formatter from a styled Excel worksheet
- 08_read_sheet.py — reading full sheet and ranges (
"A1:B2"/((0,0),(1,1)))
Current Status
The project is already usable and tested for its core workflow:
- type checking with
mypyhas been cleaned up in the codebase - automated tests cover formatter logic, themes, color references, and workbook save/update flows
At the same time, the library is still evolving, and we keep refining the API surface and documentation.
Development
Current checks used in this repository:
python -m mypy sheetkit --config-file mypy.ini
python -m pytest -q
Release flow (local + TestPyPI + PyPI) is documented in docs/release.md.
License
MIT
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 sheetkit-0.1.1.tar.gz.
File metadata
- Download URL: sheetkit-0.1.1.tar.gz
- Upload date:
- Size: 112.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d81c21e08fb9c2150bb4d8b31369efc8fa167cb990bee46f1224c21a0c40b8ed
|
|
| MD5 |
508ae49a7d421765073651dadcbc4ee1
|
|
| BLAKE2b-256 |
218075322adda6c012546b0e597226cff1aeab7169a243b0c18160ae3d6f33d7
|
File details
Details for the file sheetkit-0.1.1-py3-none-any.whl.
File metadata
- Download URL: sheetkit-0.1.1-py3-none-any.whl
- Upload date:
- Size: 156.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6941467f571175c33c09e9d0dc6b0533d5c569220bc68a0f9f1e402821d33e9b
|
|
| MD5 |
5bc33e60471347739b15498d85543db3
|
|
| BLAKE2b-256 |
92410b127ff50ddb7a0cca03086a738f29168e2a9cf342b6899749c4025783e0
|