openpyxl-toolkit
openpyxl formatting without the boilerplate.
Purpose
It is our opinion that formatting and styling worksheets with openpyxl is clunky and non-intuitive. We wanted to build something to make the process less painful and the end result more readable.
For example, openpyxl stores a cell's style as an immutable object. This means
changing one attribute results in building an entirely new Font object and
assigning it, which drops the size, the color, and anything else that was set:
>>> from copy import copy
>>> from openpyxl import Workbook
>>> from openpyxl.styles import Font
>>> wb = Workbook()
>>> ws = wb.active
>>> ws["A1"] = "My cell text"
>>> ws["A1"].font = Font(size=14, color="FF1D3557") # openpyxl wants ARGB
>>> ws["A1"].font = Font(bold=True) # the size and the color are now gone
If you try ws["A1"].font.bold = True, you get Style objects are immutable and cannot be changed. Reassign the style with a copy. So if you wanted to modify
just one part of a font, you would have to make a copy, modify it, and assign
it:
>>> font = copy(ws["A1"].font)
>>> font.bold = True
>>> ws["A1"].font = font
This toolkit allows you to change attributes one by one without having to copy previously set attributes.
>>> from openpyxl_toolkit import WorksheetToolkit
>>> toolkit = WorksheetToolkit(ws)
>>> toolkit.set_font(cells="A1", size=14, color="#1d3557")
>>> toolkit.set_font(cells="A1", bold=True) # size and color are not discarded
Install
pip install openpyxl-toolkit
Python 3.10 or newer, and openpyxl 3.1 or newer.
Methods
Cell formatting
| Method | What it sets |
|---|---|
set_alignment |
horizontal, vertical, text_rotation, wrap_text, shrink_to_fit, indent, reading_order |
set_border |
style and color, on any of six sides |
set_outside_border |
one border around the edge of a block, leaving the inside alone |
set_fill |
fill_type, start_color, end_color |
set_font |
name, size, bold, italic, underline, strike, color |
set_number_format |
how a value is displayed: currency, percent, dates, decimal places |
Row and column formatting
| Method | What it sets |
|---|---|
set_column_width / set_row_height |
an explicit size; 0 hides the column or row |
set_column_best_fit |
a column width that fits the widest cell |
merge_cells / unmerge_cells |
a merged range |
Sheet-level controls
| Method | What it sets |
|---|---|
freeze_panes |
rows above and columns left of a cell stay visible |
set_autofilter |
Excel's filter controls on a range |
set_gridline_visibility |
whether the grid between cells is drawn on screen |
set_sheet_visibility |
whether the sheet is shown, hidden, or hidden from the unhide list too |
set_tab_color |
the color of the sheet's tab |
set_zoom_scale |
the zoom level when the reader opens the file: 10 to 400 |
>>> toolkit.set_tab_color("#1d3557")
>>> toolkit.set_gridline_visibility(visible=False)
>>> toolkit.set_autofilter(cells="A1:C3")
>>> toolkit.set_sheet_visibility(state="hidden")
Each method returns the toolkit, so calls can chain. Most parameters are keyword-only.
Anything not named is left as is. None is not the same as leaving out an
argument: set_font(color=None) clears the color, while omitting color
keeps whatever was there.
Cell selection
On every method that formats cells, there are 2 methods of selecting which cells the action should be applied to:
cellsrowsandcolumns
>>> toolkit.set_fill(cells="A1:C3", fill_type="solid", start_color="#f4f6f8")
>>> toolkit.set_fill(cells="A:C", fill_type="solid", start_color="#f4f6f8")
>>> toolkit.set_fill(rows=[1, 2], columns=[1, 2], intersections_only=True,
... fill_type="solid", start_color="#ffff00")
cells takes a block ("A1:C3"), whole columns ("B:D"), whole rows ("2:5")
or one cell ("C3"). Case does not matter, a reversed range such as "C3:A1"
is normalized, and an unbounded side is filled in from the used range, so
"B:B" means column B as far as the sheet goes (rather than all 1,048,576
rows).
rows and columns take lists.
Given both rows and columns, intersections_only decides what they mean:
| Cells formatted | |
|---|---|
intersections_only=True (default) |
where the rows and the columns cross — a block |
intersections_only=False |
every cell in those rows and every cell in those columns — a cross |
Giving both cells and rows/columns raises.
Column letters and numbers
Because column letters can be hard to work with, we have provided a pair of
functions for conversion. openpyxl has its own versions in openpyxl.utils, but
under names that are more difficult to remember.
>>> from openpyxl_toolkit import column_letter, column_index
>>> column_letter(3) # 'C'
>>> column_index("C") # 3
>>> toolkit.set_column_width(width=18, columns=[column_index("D")])
Type hints
The package ships py.typed. Alignment names, border styles, fill patterns and
underline styles are Literal types, so an editor completes them and a checker
catches a typo before the code runs:
error: Argument "horizontal" to "set_alignment" has incompatible type "Literal['centre']"
openpyxl rejects the same typo, but only once the call runs; the Literal types
move it into the editor. Elsewhere the annotations really are stricter than
openpyxl is at runtime, which matters more than it sounds: openpyxl accepts
bold="no" and stores True, and name=42 and stores "42".
License
MIT.
Release files for openpyxl-toolkit 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openpyxl_toolkit-0.4.0.tar.gz | 68.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openpyxl_toolkit-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.8 kB
Release files / openpyxl_toolkit-0.4.0.tar.gz
| Download URL | openpyxl_toolkit-0.4.0.tar.gz |
|---|---|
| Size | 68.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f0c83b72bcafc5e96959c223240b29475887cdb30b280d213877c6459d0d0050
|
|
BLAKE2b-256 checksum How to use checksums |
4b72057cf460b198ed90b6302b4a20879ffb56f3c237c9030ee619dcc0f6e3e3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / openpyxl_toolkit-0.4.0-py3-none-any.whl
| Download URL | openpyxl_toolkit-0.4.0-py3-none-any.whl |
|---|---|
| Size | 37.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
018d92a0e420c066fe63881c6685616694e18d280f8389fad13eb9e2344504e1
|
|
BLAKE2b-256 checksum How to use checksums |
2a739a369b7211583720185062b6b69680de540597356addfdea97fb1bdd28d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log