paper-xlsx
vs openpyxl
High-level changes relative to openpyxl 3.1.5, and why.
paper-xlsx is a fork of openpyxl 3.1.5. It keeps the openpyxl import name and
object model, while preserve mode changes how loaded editable workbooks are
validated and saved. Paper changes that path to preserve content openpyxl
does not model.
openpyxl regenerates the entire file on save, so content it does not model is degraded or dropped. As upstream's tutorial notes:
openpyxl does currently not read all possible items in an Excel file so shapes will be lost from existing files if they are opened and saved with the same name.
Each of these was verified against openpyxl 3.1.5 before designing the fork:
data_only=Truethen save destroys formulas — a sheet with 3 formulas round-trips to 0.insert_rowsanddelete_rowsmove cells without updating formulas, defined names, or chart ranges, so downstreamSUMranges point at the wrong cells.- Written formulas carry stale or empty cached values.
- VBA is stripped from
.xlsmfiles unlesskeep_vba=True. - Shapes, textboxes,
mc:AlternateContent, chartextLst, sparklines, and x14 validations are lost on a plain load and save.
High-level changes
- Preserve mode.
load_workbook(path)retains the original archive bytes. A dirty ledger records edits, and save splices dirty ranges into the original parts instead of regenerating. Untouched parts copy through byte-identical; a no-op save is byte-identical. - Typed error taxonomy.
openpyxl.errors.PaperRefusaland subclasses (AmbiguousTargetError,TargetNotFoundError,UnsupportedStructureError,BoundaryViolationError,RelationshipPolicyError,OracleUnavailableError,OracleTimeoutError). Guarded helpers validate before mutating; a save refusal writes no destination file. - Guarded delivery. Path saves validate ZIP consistency and use
fsync-before-rename.
preserve=Falsekeeps the stock openpyxl save path. - Inspection and editing.
wb.search(...),ws.allowed_values(cell),openpyxl.preserve.scan_errors(), anddiff_workbooks()return targeted structured data. Structural edits (insert_rows,delete_rows, sheet renames, range moves) rewrite dependent formulas, defined names, print areas, table ranges, and chart references when supported, or refuse. Row and column insertions and deletions return anAddressRemap. - Targeted editing helpers.
copy_format(),chart.repoint(),append_table_row(),replace_image(), and explicit pivot-refresh consent. - Formula state. Editing a formula or one of its inputs strips affected stale cached results and requests recalculation on open. Style-only edits keep their caches.
- The oracle.
openpyxl.oracleuses a headless, profile-isolated LibreOffice forrecalc(),certify(),evaluate(), andevaluate_many(). It works on temporary copies and never overwrites the source. - Compatibility and installation. Preserve mode is the default for
editable OOXML workbooks;
read_only=Trueandpreserve=Falsekeep stock behavior.paper-xlsx-doctorverifies the installed distribution owns theopenpyxlimport tree.
At a glance
| A real workbook, edited and saved | stock openpyxl 3.1.5 | paper-xlsx preserve mode |
|---|---|---|
Shapes, textboxes, mc:AlternateContent | dropped without warning | copied through byte-identical |
| Charts | regenerated from the model; chart extLst and auxiliary parts lost | untouched charts copied through byte-identical; title and series-range edits spliced |
| Sparklines, x14 validations/formatting | dropped (load-time warning) | copied through byte-identical |
VBA project in .xlsm | stripped unless keep_vba=True | survives |
data_only=True, then save | every formula replaced by its cached value | refused unless explicitly allowed |
insert_rows / delete_rows | cells move, references do not update | references rewritten (or edit refused); AddressRemap returned |
| Formula caches after an edit | cached results can be cleared; formulas are not calculated | affected caches invalidated; recalculation requested on open |
| Unsafe or ambiguous operation | best guess | typed PaperRefusal, file unchanged |