Skip to main content

openpyxl-toolkit

PyPI Python versions CI License

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.

Choosing cells

On every method that formats cells, there are 2 methods of selecting which cells the action should be applied to:

  1. cells
  2. rows and columns
>>> 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

rows and columns take numbers, so there is a pair for converting either way.

>>> 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")])

openpyxl has its own pair of methods in openpyxl.utils, but they run off the end of the grid. get_column_letter(16385) returns "XFE", and a workbook using that column will not open. These stop at XFD, column 16,384.

Methods

Method What it sets
set_font name, size, bold, italic, underline, strike, color
set_fill fill_type, start_color, end_color
set_alignment horizontal, vertical, text_rotation, wrap_text, shrink_to_fit, indent, reading_order
set_number_format how a value is displayed: currency, percent, dates, decimal places
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_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
freeze_panes rows above and columns left of a cell stay visible
set_zoom_scale the zoom level when the reader opens the file: 10 to 400

Each method returns the toolkit, so calls 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.

Fitting columns

set_column_best_fit measures every character at its own width in the font being used, so a column of i's does not come out as wide as a column of W's. Excel's own formula turns the total into a width:

width = (pixels of text + 5 padding pixels) / max digit width
>>> toolkit.set_column_best_fit(padding=1.5, min_width=9)

Built-in metrics cover Aptos, Arial, Calibri, Cambria, Courier New, Futura, Garamond, Georgia, Inter, Open Sans, Palatino, Roboto, Segoe UI, Tahoma, Times New Roman and Verdana. Helvetica, Book Antiqua, Selawik, Arimo, Carlito, Tinos, Cousine, Caladea and the Liberation faces are recognized too, each sharing the table of the face it matches advance for advance.

Any other face is measured with Verdana's character widths. Verdana is the widest of them, so a face with no metrics errs wide: a column that is too narrow hides what it holds, while one that is too wide only looks untidy. If that is not what you want for a particular column, set its width directly with set_column_width, or open an issue to have the face added to the table.

Four things to note:

  • A wrapped cell is skipped. Wrapping exists so text conforms to the column, so sizing the column to the unwrapped line would guarantee it never wraps. Pass ignore_wrapped=False to override this behavior.
  • Only dates and times are rendered as Excel displays them. Other number formats, including the ones set_number_format writes, are measured as the value is stored, so a currency column can come out narrower than it needs to be. Use min_width or set the column width manually in these cases.
  • Formula cells are skipped by default. Set those column widths directly, or pass ignore_formulas=False to override this behavior.
  • A cell merged across columns is skipped. ignore_merged=False measures it, widening the one column that holds the value to fit text the reader sees spread across the merge. A merge running down a single column is measured either way.

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.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openpyxl-toolkit 0.3.0
File Size Uploaded
openpyxl_toolkit-0.3.0.tar.gz 63.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openpyxl-toolkit 0.3.0
File Interpreter ABI Platform
openpyxl_toolkit-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 99.4 kB

Release files / openpyxl_toolkit-0.3.0.tar.gz

Download URL openpyxl_toolkit-0.3.0.tar.gz
Size 63.6 kB
Tags Source
SHA-256 checksum
How to use checksums
d1db15ce72ea385bcf359efe07fd0fa1975c2ae4360db1e9275469620ff4f63b
BLAKE2b-256 checksum
How to use checksums
8a14b12a947a0e3cd320aee2df9d377aab227b94cf36b6cd49af48cd433cb301
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

Release files / openpyxl_toolkit-0.3.0-py3-none-any.whl

Download URL openpyxl_toolkit-0.3.0-py3-none-any.whl
Size 35.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
116807fe95ff348375fe6fdaf1ba17715dd44341fc79dd98bfb0f5b45442ac4b
BLAKE2b-256 checksum
How to use checksums
043fbbc21683d884a00c99dcc19c873cb2f81e9f094c8fc01e94ddf1842f6755
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

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page