Compatibility with openpyxl
fastpyxl is a drop-in replacement for openpyxl >= 3.0.0 on Python >= 3.11.
Users on openpyxl 3.x should be able to change imports only:
# before
from openpyxl import Workbook, load_workbook
# after
from fastpyxl import Workbook, load_workbookNo other code changes are required for supported openpyxl 3.x usage.
Supported baseline
| Constraint | Value |
|---|---|
| openpyxl API surface | >= 3.0.0 |
| Python | >= 3.11 |
| CI enforcement pin | openpyxl 3.1.5 |
The public claim is compatibility with the openpyxl 3.x API family starting at 3.0.0. Continuous integration pins the current openpyxl 3.1.x release used for benchmarks and runs the dedicated compatibility suite against that pin.
Intentional non-goals
fastpyxl does not aim to preserve:
- APIs removed before openpyxl 3.0
- Behaviour only present in openpyxl 2.x
- Legacy load options such as
use_iterators(replaced byread_only) - Historical dual worksheet implementations (
IterableWorksheetvsWorksheet); read-only mode usesReadOnlyWorksheet
Running the compatibility suite
The openpyxl drop-in contract is enforced by tests under fastpyxl/tests/compat, marked openpyxl_compat. CI runs them in a dedicated job; locally:
uv sync --all-groups
uv run pytest fastpyxl/tests/compat -m openpyxl_compatopenpyxl==3.1.5 is installed via the dev dependency group.
Documenting intentional divergences
When fastpyxl deliberately differs from openpyxl, document that difference as a reviewed xfail (or an allowlist entry in the API-surface tests) rather than allowing silent drift. Prefer:
- A short comment next to the
xfail/ allowlist entry explaining why - A note in this page if the divergence affects end users
- A changelog entry when the divergence is introduced or removed
vba_archive filtering (keep_vba)
With keep_vba=True, openpyxl 3.1.5 copies the entire package into Workbook.vba_archive. fastpyxl instead stores a filtered VBA cache: only macro-relevant parts (xl/vba*, VML drawings, ActiveX, ctrlProps, customUI, EMF media, plus [Content_Types].xml and _rels/.rels).
That filtered cache is enough for load→edit→save of macros; the writer already applies the same ARC_VBA allowlist on save. Filtering avoids cloning unused worksheet/XML parts on large .xlsm files.
Callers that inspect non-VBA members of wb.vba_archive can opt into openpyxl-identical behaviour with the fastpyxl-specific escape hatch:
wb = load_workbook("macros.xlsm", keep_vba=True) # filtered cache (default)
wb = load_workbook("macros.xlsm", keep_vba="full") # full package mirror
wb = load_workbook("macros.xlsm", keep_vba=False) # no vba_archiveTrue / False remain drop-in compatible with openpyxl’s bool API. keep_vba="full" is fastpyxl-only.
Dual-load formulas and caches (keep_formula_cache)
Excel sheet XML often stores both a formula (<f>) and a last-calculated cache (<v>) on the same cell. openpyxl (and default fastpyxl) keep only one or the other via data_only.
fastpyxl adds an opt-in dual-load mode that keeps both in a single parse:
wb = load_workbook("book.xlsx", keep_formula_cache=True)
assert wb.data_only is False
c = wb.active["D2"]
c.value # formula text, e.g. "='Sheet2'!D5"
c.cached_value # cached result from the file, or NoneCompatibility constraints:
load_workbook(path)andload_workbook(path, data_only=True)remain unchanged (and openpyxl-compatible) for.value,.data_type, iteration, and save output. In those modescell.cached_valueis alwaysNone.data_onlystays a bool; do not pass"both".data_only=Trueandkeep_formula_cache=Truetogether raiseValueError.- Assigning
.valueclears.cached_value(the cache is stale). - On standard worksheets, caches are stored in a sheet-level side map (
ws._formula_caches) rather than a per-cell slot, so default-mode loads do not pay a pointer per cell. Only the live sheet cell at a coordinate may read or write that map entry; detached helpers such as WriteOnlyCell are ignored.read_onlycells keep instance storage because they are ephemeral and must not accumulate a sheet-wide map. - Save still writes empty
<v>for formula cells in v1; emitting real caches is deferred.
keep_formula_cache is fastpyxl-only.
Indexed / on-demand reads (access)
Default load_workbook and read_only=True keep their openpyxl semantics. fastpyxl adds an opt-in indexed read strategy for sparse / random access:
wb = load_workbook("book.xlsx", access="indexed")
assert wb.read_only is True
print(wb.active["Z100000"].value)
wb.close()Compatibility constraints:
access=None(default) is unchanged and openpyxl-compatible.access="indexed"implies read-only worksheets;read_only=Truemay be passed together and is accepted.- Unknown
accessvalues raiseValueError. - Misses return
EMPTY_CELL(no densifying writes). - Styles stay eagerly loaded; only sheet rows and shared strings are indexed.
- Like
read_only, indexed worksheets do not bind merges, comments, tables, drawings, or other worksheet relationship metadata.
access is fastpyxl-only.
Environment variables
Prefer the FASTPYXL_* names:
| Setting | Preferred | Legacy alias (deprecated) |
|---|---|---|
| Use lxml when available | FASTPYXL_LXML |
OPENPYXL_LXML |
| Use defusedxml when available | FASTPYXL_DEFUSEDXML |
OPENPYXL_DEFUSEDXML |
| Keep VBA on load (tests) | FASTPYXL_KEEP_VBA |
OPENPYXL_KEEP_VBA |
Using a legacy OPENPYXL_* name still works for one release cycle but emits a DeprecationWarning. Migrate to the FASTPYXL_* equivalent.
Removal plan for inherited compatibility cruft
The openpyxl lineage left several deprecated call sites and shims. With the 3.0+ contract published, removals proceed in phases:
Phase 1 — warn (current)
- Emit
DeprecationWarningfor legacyOPENPYXL_*environment aliases - Keep deprecated openpyxl 3.x workbook helpers (get_sheet_by_name, get_sheet_names, remove_sheet, get_index, create_named_range) and
Worksheet.merged_cell_rangeswhile they remain part of the openpyxl 3.x surface that callers still use - Clean obsolete comments/docstrings that refer to pre-3.0 dual implementations (
IterableWorksheet,use_iterators)
Phase 2 — remove aliases
- Drop
OPENPYXL_*environment aliases after at least one released version has carried the Phase 1 warnings - Refactor
reader/excel.pyso production code does not import test helpers such asKEEP_VBAfromfastpyxl.tests
Phase 3 — major-version API trim
- Remove deprecated openpyxl 3.x methods/properties only in a major release (v2.0), with release notes listing replacements (
wb[sheetname],wb.sheetnames,wb.remove(),wb.index(), etc.) - Revisit type-checker carve-outs in
pyproject.tomlthat exist only for legacy Python 2 / transitional code once that code is gone
Deprecated APIs that openpyxl itself still exposes in 3.x remain available in fastpyxl v1.x so import-only migrations keep working. They are not a promise of indefinite support beyond the next major version.