Microsoft Excel — Python
Companion skill to ms-office-python (parent). For other areas see: ms-office-word-python, ms-office-powerpoint-python, ms-office-graph-python, ms-office-enterprise-sso-python, ms-office-security-python.
Overview
.xlsx (Office Open XML, OOXML) is a zip of XML parts. Python can produce and consume it without Office installed using pure-Python libraries. Where Excel itself is required (formula recalculation, COM automation, fidelity-perfect rendering), the codepath is Windows/macOS-only and depends on a local Office install. The 2026 ecosystem state is mature for .xlsx (openpyxl + xlsxwriter + pandas), legacy for .xls (xlrd<2.0), and niche for .xlsb (pyxlsb, read-only).
Formula injection (CWE-1236) is the single most common security mistake in Python Excel code — covered above as HARD-RULE 1. XXE in OOXML parsing is the second; defusedxml is the family-wide remedy.
Library Selection
| Library |
Purpose |
Status (2026-05) |
OS support |
When to use |
When NOT to use |
openpyxl |
Read + write .xlsx / .xlsm |
Active |
All |
Default for any new .xlsx work |
Massive workbooks (use read_only=True mode); when you need full Excel fidelity (use Excel/LibreOffice) |
xlsxwriter |
Write-only .xlsx |
Active |
All |
Generating large .xlsx from scratch (faster than openpyxl write) |
Editing an existing file (it can't) |
pandas (with openpyxl / xlsxwriter backends) |
DataFrame I/O |
Active |
All |
Tabular data round-tripping; analytical workflows |
Cell-level formatting / formulas / charts |
xlrd (<2.0) |
Read .xls legacy |
Active (legacy code path only) |
All |
Reading .xls (Office 97-2003) files |
Reading .xlsx — xlrd >= 2.0 removed .xlsx support |
pyxlsb |
Read .xlsb (binary) |
Active |
All |
Reading binary Excel format produced by some enterprise workflows |
Writing .xlsb — read-only |
xlwings |
Drive Excel application via COM |
Active |
Windows / macOS only (requires Excel) |
Interactive Excel automation, recalculation, full-fidelity workflows |
Linux, headless containers, CI runners |
pywin32 (win32com.client) |
Low-level Windows COM |
Active |
Windows only |
Niche COM automation patterns not covered by xlwings |
Cross-platform code, server contexts (Service-broker COM is fragile) |
msoffcrypto-tool |
Decrypt password-protected workbooks |
Active |
All |
Reading encrypted .xlsx (and other Office formats) |
Encryption (not its purpose) |
pycel |
Pure-Python formula evaluator |
Active |
All |
Computing formula results without Excel; targeted recalculation |
Full workbook fidelity (limited formula coverage) |
formulas |
Formula evaluator (alternate) |
Active |
All |
Same as pycel; different coverage profile |
Same as pycel |
defusedxml |
XXE defence for stdlib XML |
Active |
All |
Any XML parsing path you control (third-party libs may not honour it) |
(always include) |
Install Commands
RHEL 9 / AlmaLinux 9 / Rocky 9
sudo dnf install -y python3.12 python3-pip python3-devel gcc-c++ libxml2-devel libxslt-devel
python3 -m pip install --upgrade pip
python3 -m pip install openpyxl xlsxwriter pandas msoffcrypto-tool defusedxml
# Optional, legacy:
# python3 -m pip install 'xlrd<2.0' # .xls only
# Optional, binary:
# python3 -m pip install pyxlsb
Debian 12 / Ubuntu 24.04
sudo apt update
sudo apt install -y python3.12 python3-pip python3-dev build-essential libxml2-dev libxslt1-dev
python3 -m pip install --upgrade pip
python3 -m pip install openpyxl xlsxwriter pandas msoffcrypto-tool defusedxml
# Same optional packages as RHEL.
Windows 11
winget install --id Python.Python.3.12 -e --silent
python -m pip install --upgrade pip
python -m pip install openpyxl xlsxwriter pandas msoffcrypto-tool defusedxml
# Optional, requires Excel installed:
# python -m pip install xlwings pywin32
Capability Matrix
| Feature |
openpyxl |
xlsxwriter |
pandas |
xlwings |
pyxlsb |
Read .xlsx |
yes |
no |
yes (engine=openpyxl) |
yes (Excel required) |
no |
Write .xlsx |
yes |
yes |
yes (engine=openpyxl/xlsxwriter) |
yes (Excel required) |
no |
Read .xls |
no |
no |
yes (engine=xlrd<2.0) |
yes (Excel required) |
no |
Read .xlsb |
no |
no |
yes (engine=pyxlsb) |
yes (Excel required) |
yes |
| Read encrypted |
partial (with msoffcrypto-tool preprocessor) |
no |
partial (same) |
yes (Excel required) |
no |
| Calculate formulas |
no (reads cached values only) |
no |
no |
yes (live Excel) |
no |
| Charts |
yes (basic) |
yes (rich) |
partial (via openpyxl) |
yes |
no |
| Conditional formatting |
yes |
yes |
partial |
yes |
no |
| Streaming / large file |
yes (read_only=True / write_only=True) |
yes (write only) |
yes (chunksize for read) |
no |
yes (streaming reader) |
| Cross-platform |
yes |
yes |
yes |
NO (Win/Mac) |
yes |
Decision Sections
Read-vs-write performance
- Reading a large file (>50 MB):
openpyxl.load_workbook(path, read_only=True, data_only=True) — streams rows lazily, ~10x memory reduction. data_only=True returns cached values (formula results) instead of formula text.
- Writing a large file from scratch:
xlsxwriter outperforms openpyxl by 2-5x. Use Workbook(path, {'constant_memory': True}) to drop in-memory row state after each worksheet.write_row call.
- Editing an existing large file: openpyxl is the only pure-Python option (xlsxwriter is write-only). Accept the memory cost or split the file.
Formula handling
openpyxl and xlsxwriter write formula strings but DO NOT evaluate them. The workbook ships with stale cached values unless something recalculates. Options:
- Open in Excel/LibreOffice to force recalculation (one-time, manual).
- Set
workbook.calc_properties.fullCalcOnLoad = True (xlsxwriter) — instructs Excel to recalculate when next opened.
- Use
xlwings — drives a live Excel instance to recalculate (Windows/macOS only).
- Use
pycel or formulas — pure-Python evaluators with partial coverage; verify against your specific formula set.
Shipping a workbook with stale cached values to an end user is a common bug — make this an explicit decision, not an oversight.
Encrypted workbooks
import io, msoffcrypto, openpyxl
with open("encrypted.xlsx", "rb") as fh:
decrypted = io.BytesIO()
office_file = msoffcrypto.OfficeFile(fh)
office_file.load_key(password="secret") # PASSWORD MUST COME FROM A VAULT, NOT A LITERAL
office_file.decrypt(decrypted)
decrypted.seek(0)
workbook = openpyxl.load_workbook(decrypted, read_only=True)
The password is the input most likely to leak — see ms-office-security-python for vault patterns.
Canonical Pattern (modified C3)
Read a .xlsx file, transform a column, write to a new file — the most common task in the family.
# CONFIDENCE: minimal viable pattern — production hardening notes in the Security Hardening section below; full references/ guide planned (v1.1).
import openpyxl
def safe_cell(value):
if isinstance(value, str) and value and value[0] in ("=", "+", "-", "@", "\t", "\r"):
return "'" + value
return value
wb = openpyxl.load_workbook("input.xlsx", read_only=False, data_only=True)
ws = wb.active
header = [cell.value for cell in next(ws.iter_rows(min_row=1, max_row=1))]
out = openpyxl.Workbook()
out_ws = out.active
out_ws.append(header)
for row in ws.iter_rows(min_row=2, values_only=True):
transformed = [safe_cell(v.upper() if isinstance(v, str) else v) for v in row]
out_ws.append(transformed)
out.save("output.xlsx")
Three things this pattern enforces: explicit data_only choice, formula-injection sanitization on every string write, no global state. References cover streaming-for-large-files, conditional formatting, and chart embedding.
Security Hardening
See ms-office-security-python for the consolidated checklist. Area-specific items:
- Sanitize every user-controlled string written to a cell against formula injection (HARD-RULE 1).
- For untrusted
.xlsx input, use openpyxl.load_workbook(path, read_only=True, data_only=True) and pair with defusedxml to neutralize XXE / billion-laughs payloads inside the embedded XML.
- NEVER pass user-controlled paths to
os.path.expanduser / os.path.expandvars then to load_workbook — path traversal risk; canonicalize and confine to an explicit base directory.
- When
openpyxl ingests a .xlsm, it strips macros silently. If the workflow must preserve macros, choose xlwings (live Excel) or document the macro-strip as intentional in code comments.
- Decrypt passwords come from a vault / Managed Identity / OS credential store — never from source, env vars containing plaintext, or config files in git.
- For large untrusted files, set a hard
MAX_ROWS / MAX_COLS budget BEFORE iterating — pathological files can exhaust memory through column-sparse-row patterns even in read_only mode.
- When converting Excel to CSV or JSON, never blindly stringify cells — strip control characters (
\x00-\x08, \x0b, \x0c, \x0e-\x1f) and serialize numbers / dates / booleans with explicit type handling. CSV downstream consumers (esp. Excel reopening the CSV) suffer formula injection if you skip sanitization a second time.
- Audit log every read of an encrypted workbook (filename + actor + timestamp). Forensically, the act of decryption is more important than the cell contents.
- Pin
lxml and openpyxl versions explicitly. lxml has had several XXE CVEs; floating versions cause regressions.
- Workbook properties (
creator, last_modified_by, description, custom doc properties) carry user-identifiable data — strip or sanitize before publishing externally.
Selection Cheatsheet
- "Read a .xlsx file" →
openpyxl.load_workbook(path, read_only=True, data_only=True)
- "Write a .xlsx file from scratch, performance matters" →
xlsxwriter.Workbook(path, {'constant_memory': True})
- "Round-trip a DataFrame" →
pandas.read_excel(path, engine='openpyxl') / df.to_excel(path, engine='openpyxl')
- "Decrypt a password-protected file" →
msoffcrypto-tool → BytesIO → openpyxl
- "Run actual Excel to recalc formulas" →
xlwings (Windows/macOS only)
- "Compute formulas without Excel" →
pycel / formulas (partial coverage; verify)
- "Read .xls (legacy)" →
xlrd<2.0
- "Read .xlsb (binary)" →
pyxlsb
Gotchas
openpyxl and xlsxwriter do NOT calculate formulas — the workbook will contain stale cached values until something recalculates (Excel, LibreOffice, xlwings, pycel). Plan for this.
xlwings is Windows/macOS-only AND requires a local Excel install. Headless Linux CI cannot use it.
xlrd removed .xlsx support at v2.0 (2020). If pip install xlrd resolves to >=2.0, expect failures on .xlsx.
openpyxl's data_only=True returns cached values — if the file was written by a non-Excel library, those values may be missing or stale.
pandas.read_excel engine selection is opinionated: .xlsx → openpyxl, .xls → xlrd<2.0, .xlsb → pyxlsb. Override explicitly when the inference is wrong.
- COM automation via
pywin32 in a service / scheduled-task context is fragile — Office prefers a desktop session. Prefer xlwings for interactive contexts and Graph + .xlsx file paths for server contexts.
.xlsm round-trips through openpyxl strip macros (HARD-RULE in ms-office-word-python for .docm — same applies here in spirit). State this in code or refuse to process .xlsm without explicit user intent.
- Encrypted workbooks routed through
msoffcrypto-tool produce an in-memory BytesIO — large files consume RAM proportional to file size. Stream-decrypt is not supported in 2026-05.
Update Triggers (per Codex M-1 — alf will scan these)
- Major version bump of:
openpyxl, xlsxwriter, pandas, xlrd, lxml, msoffcrypto-tool.
- New OOXML /
.xlsx schema change announced by Microsoft (rare but happens).
- CVE published against
lxml or openpyxl (XXE-class).
- Annual review on: 2027-05-22.
See Also
| Need |
Skill |
| Word / Markdown / PDF conversions |
ms-office-word-python |
| PowerPoint generation |
ms-office-powerpoint-python |
| Sending an Excel as Outlook attachment |
ms-office-graph-python |
| Hardening / validator / checklist |
ms-office-security-python |
| Reading enterprise-encrypted workbooks with cert-based DRM |
python-auth-security + IRM/RMS coverage (not in v1) |
| Dependency CVE scanning |
dep-currency-check |
1---2name: ms-office-excel-python3description: Use when reading, writing, transforming, decrypting, or converting Excel files from Python — .xlsx (openpyxl, xlsxwriter, pandas), .xls legacy (xlrd <2.0), .xlsb (pyxlsb), encrypted workbooks (msoffcrypto-tool), Excel automation on Windows/macOS with Office installed (xlwings, pywin32 COM), or formula evaluation without Excel (pycel, formulas). Covers formula injection (CWE-1236) sanitization, XXE defence for parsed XML, and large-file streaming patterns. Part of the ms-office-python-* skill family.4---56# Microsoft Excel — Python78Companion skill to `ms-office-python` (parent). For other areas see: `ms-office-word-python`, `ms-office-powerpoint-python`, `ms-office-graph-python`, `ms-office-enterprise-sso-python`, `ms-office-security-python`.910<HARD-RULE>11Sanitize cell content against formula injection (CWE-1236). When writing user-controlled values into a workbook, strip or escape leading `=`, `+`, `-`, `@`, `\t`, `\r`. Excel interprets a leading equals sign as a formula; combined with `WEBSERVICE()`, `HYPERLINK()`, or `DDE()`, this is a data-exfiltration vector when the workbook is opened by an end user.12```python13def safe_cell(value):14 if isinstance(value, str) and value and value[0] in ("=", "+", "-", "@", "\t", "\r"):15 return "'" + value # leading apostrophe forces text interpretation16 return value17```18</HARD-RULE>1920<HARD-RULE>21`xlrd >= 2.0` cannot read `.xlsx`. Pin `xlrd<2.0` for `.xls` legacy ONLY and use `openpyxl` or `pandas[excel]` for `.xlsx`. Mixing the two in one project is the most common Excel-on-Python regression — engine selection in pandas defaults shifted in 1.2.0, and silent fallbacks have been removed. Greenfield code: `openpyxl` (read/write) or `xlsxwriter` (write-only, faster).22</HARD-RULE>2324---2526## Overview2728`.xlsx` (Office Open XML, OOXML) is a zip of XML parts. Python can produce and consume it without Office installed using pure-Python libraries. Where Excel itself is required (formula recalculation, COM automation, fidelity-perfect rendering), the codepath is Windows/macOS-only and depends on a local Office install. The 2026 ecosystem state is mature for `.xlsx` (openpyxl + xlsxwriter + pandas), legacy for `.xls` (xlrd<2.0), and niche for `.xlsb` (pyxlsb, read-only).2930Formula injection (CWE-1236) is the single most common security mistake in Python Excel code — covered above as HARD-RULE 1. XXE in OOXML parsing is the second; `defusedxml` is the family-wide remedy.3132## Library Selection3334| Library | Purpose | Status (2026-05) | OS support | When to use | When NOT to use |35|---|---|---|---|---|---|36| `openpyxl` | Read + write `.xlsx` / `.xlsm` | Active | All | Default for any new `.xlsx` work | Massive workbooks (use `read_only=True` mode); when you need full Excel fidelity (use Excel/LibreOffice) |37| `xlsxwriter` | Write-only `.xlsx` | Active | All | Generating large `.xlsx` from scratch (faster than openpyxl write) | Editing an existing file (it can't) |38| `pandas` (with `openpyxl` / `xlsxwriter` backends) | DataFrame I/O | Active | All | Tabular data round-tripping; analytical workflows | Cell-level formatting / formulas / charts |39| `xlrd` (<2.0) | Read `.xls` legacy | Active (legacy code path only) | All | Reading `.xls` (Office 97-2003) files | Reading `.xlsx` — `xlrd >= 2.0` removed `.xlsx` support |40| `pyxlsb` | Read `.xlsb` (binary) | Active | All | Reading binary Excel format produced by some enterprise workflows | Writing `.xlsb` — read-only |41| `xlwings` | Drive Excel application via COM | Active | Windows / macOS only (requires Excel) | Interactive Excel automation, recalculation, full-fidelity workflows | Linux, headless containers, CI runners |42| `pywin32` (`win32com.client`) | Low-level Windows COM | Active | Windows only | Niche COM automation patterns not covered by xlwings | Cross-platform code, server contexts (Service-broker COM is fragile) |43| `msoffcrypto-tool` | Decrypt password-protected workbooks | Active | All | Reading encrypted `.xlsx` (and other Office formats) | Encryption (not its purpose) |44| `pycel` | Pure-Python formula evaluator | Active | All | Computing formula results without Excel; targeted recalculation | Full workbook fidelity (limited formula coverage) |45| `formulas` | Formula evaluator (alternate) | Active | All | Same as pycel; different coverage profile | Same as pycel |46| `defusedxml` | XXE defence for stdlib XML | Active | All | Any XML parsing path you control (third-party libs may not honour it) | (always include) |4748## Install Commands4950### RHEL 9 / AlmaLinux 9 / Rocky 95152```bash53sudo dnf install -y python3.12 python3-pip python3-devel gcc-c++ libxml2-devel libxslt-devel54python3 -m pip install --upgrade pip55python3 -m pip install openpyxl xlsxwriter pandas msoffcrypto-tool defusedxml56# Optional, legacy:57# python3 -m pip install 'xlrd<2.0' # .xls only58# Optional, binary:59# python3 -m pip install pyxlsb60```6162### Debian 12 / Ubuntu 24.046364```bash65sudo apt update66sudo apt install -y python3.12 python3-pip python3-dev build-essential libxml2-dev libxslt1-dev67python3 -m pip install --upgrade pip68python3 -m pip install openpyxl xlsxwriter pandas msoffcrypto-tool defusedxml69# Same optional packages as RHEL.70```7172### Windows 117374```powershell75winget install --id Python.Python.3.12 -e --silent76python -m pip install --upgrade pip77python -m pip install openpyxl xlsxwriter pandas msoffcrypto-tool defusedxml78# Optional, requires Excel installed:79# python -m pip install xlwings pywin3280```8182## Capability Matrix8384| Feature | openpyxl | xlsxwriter | pandas | xlwings | pyxlsb |85|---|---|---|---|---|---|86| Read `.xlsx` | yes | no | yes (engine=openpyxl) | yes (Excel required) | no |87| Write `.xlsx` | yes | yes | yes (engine=openpyxl/xlsxwriter) | yes (Excel required) | no |88| Read `.xls` | no | no | yes (engine=xlrd<2.0) | yes (Excel required) | no |89| Read `.xlsb` | no | no | yes (engine=pyxlsb) | yes (Excel required) | yes |90| Read encrypted | partial (with msoffcrypto-tool preprocessor) | no | partial (same) | yes (Excel required) | no |91| Calculate formulas | no (reads cached values only) | no | no | yes (live Excel) | no |92| Charts | yes (basic) | yes (rich) | partial (via openpyxl) | yes | no |93| Conditional formatting | yes | yes | partial | yes | no |94| Streaming / large file | yes (`read_only=True` / `write_only=True`) | yes (write only) | yes (`chunksize` for read) | no | yes (streaming reader) |95| Cross-platform | yes | yes | yes | NO (Win/Mac) | yes |9697## Decision Sections9899### Read-vs-write performance100101- **Reading a large file** (>50 MB): `openpyxl.load_workbook(path, read_only=True, data_only=True)` — streams rows lazily, ~10x memory reduction. `data_only=True` returns cached values (formula results) instead of formula text.102- **Writing a large file from scratch**: `xlsxwriter` outperforms openpyxl by 2-5x. Use `Workbook(path, {'constant_memory': True})` to drop in-memory row state after each `worksheet.write_row` call.103- **Editing an existing large file**: openpyxl is the only pure-Python option (xlsxwriter is write-only). Accept the memory cost or split the file.104105### Formula handling106107`openpyxl` and `xlsxwriter` write formula strings but **DO NOT evaluate them**. The workbook ships with stale cached values unless something recalculates. Options:1081091. **Open in Excel/LibreOffice** to force recalculation (one-time, manual).1102. **Set `workbook.calc_properties.fullCalcOnLoad = True` (xlsxwriter)** — instructs Excel to recalculate when next opened.1113. **Use `xlwings`** — drives a live Excel instance to recalculate (Windows/macOS only).1124. **Use `pycel` or `formulas`** — pure-Python evaluators with partial coverage; verify against your specific formula set.113114Shipping a workbook with stale cached values to an end user is a common bug — make this an explicit decision, not an oversight.115116### Encrypted workbooks117118```python119import io, msoffcrypto, openpyxl120with open("encrypted.xlsx", "rb") as fh:121 decrypted = io.BytesIO()122 office_file = msoffcrypto.OfficeFile(fh)123 office_file.load_key(password="secret") # PASSWORD MUST COME FROM A VAULT, NOT A LITERAL124 office_file.decrypt(decrypted)125decrypted.seek(0)126workbook = openpyxl.load_workbook(decrypted, read_only=True)127```128129The password is the input most likely to leak — see `ms-office-security-python` for vault patterns.130131## Canonical Pattern (modified C3)132133**Read a `.xlsx` file, transform a column, write to a new file** — the most common task in the family.134135```python136# CONFIDENCE: minimal viable pattern — production hardening notes in the Security Hardening section below; full references/ guide planned (v1.1).137import openpyxl138139def safe_cell(value):140 if isinstance(value, str) and value and value[0] in ("=", "+", "-", "@", "\t", "\r"):141 return "'" + value142 return value143144wb = openpyxl.load_workbook("input.xlsx", read_only=False, data_only=True)145ws = wb.active146header = [cell.value for cell in next(ws.iter_rows(min_row=1, max_row=1))]147out = openpyxl.Workbook()148out_ws = out.active149out_ws.append(header)150for row in ws.iter_rows(min_row=2, values_only=True):151 transformed = [safe_cell(v.upper() if isinstance(v, str) else v) for v in row]152 out_ws.append(transformed)153out.save("output.xlsx")154```155156Three things this pattern enforces: explicit `data_only` choice, formula-injection sanitization on every string write, no global state. References cover streaming-for-large-files, conditional formatting, and chart embedding.157158## Security Hardening159160See `ms-office-security-python` for the consolidated checklist. Area-specific items:161162- Sanitize every user-controlled string written to a cell against formula injection (HARD-RULE 1).163- For untrusted `.xlsx` input, use `openpyxl.load_workbook(path, read_only=True, data_only=True)` and pair with `defusedxml` to neutralize XXE / billion-laughs payloads inside the embedded XML.164- NEVER pass user-controlled paths to `os.path.expanduser` / `os.path.expandvars` then to `load_workbook` — path traversal risk; canonicalize and confine to an explicit base directory.165- When `openpyxl` ingests a `.xlsm`, it strips macros silently. If the workflow must preserve macros, choose `xlwings` (live Excel) or document the macro-strip as intentional in code comments.166- Decrypt passwords come from a vault / Managed Identity / OS credential store — never from source, env vars containing plaintext, or config files in git.167- For large untrusted files, set a hard `MAX_ROWS` / `MAX_COLS` budget BEFORE iterating — pathological files can exhaust memory through column-sparse-row patterns even in `read_only` mode.168- When converting Excel to CSV or JSON, never blindly stringify cells — strip control characters (`\x00`-`\x08`, `\x0b`, `\x0c`, `\x0e`-`\x1f`) and serialize numbers / dates / booleans with explicit type handling. CSV downstream consumers (esp. Excel reopening the CSV) suffer formula injection if you skip sanitization a second time.169- Audit log every read of an encrypted workbook (filename + actor + timestamp). Forensically, the act of decryption is more important than the cell contents.170- Pin `lxml` and `openpyxl` versions explicitly. `lxml` has had several XXE CVEs; floating versions cause regressions.171- Workbook properties (`creator`, `last_modified_by`, `description`, custom doc properties) carry user-identifiable data — strip or sanitize before publishing externally.172173## Selection Cheatsheet174175- "Read a .xlsx file" → `openpyxl.load_workbook(path, read_only=True, data_only=True)`176- "Write a .xlsx file from scratch, performance matters" → `xlsxwriter.Workbook(path, {'constant_memory': True})`177- "Round-trip a DataFrame" → `pandas.read_excel(path, engine='openpyxl')` / `df.to_excel(path, engine='openpyxl')`178- "Decrypt a password-protected file" → `msoffcrypto-tool` → BytesIO → openpyxl179- "Run actual Excel to recalc formulas" → `xlwings` (Windows/macOS only)180- "Compute formulas without Excel" → `pycel` / `formulas` (partial coverage; verify)181- "Read .xls (legacy)" → `xlrd<2.0`182- "Read .xlsb (binary)" → `pyxlsb`183184## Gotchas185186- `openpyxl` and `xlsxwriter` do NOT calculate formulas — the workbook will contain stale cached values until something recalculates (Excel, LibreOffice, xlwings, pycel). Plan for this.187- `xlwings` is Windows/macOS-only AND requires a local Excel install. Headless Linux CI cannot use it.188- `xlrd` removed `.xlsx` support at v2.0 (2020). If `pip install xlrd` resolves to >=2.0, expect failures on `.xlsx`.189- `openpyxl`'s `data_only=True` returns cached values — if the file was written by a non-Excel library, those values may be missing or stale.190- `pandas.read_excel` engine selection is opinionated: `.xlsx` → openpyxl, `.xls` → xlrd<2.0, `.xlsb` → pyxlsb. Override explicitly when the inference is wrong.191- COM automation via `pywin32` in a service / scheduled-task context is fragile — Office prefers a desktop session. Prefer `xlwings` for interactive contexts and Graph + .xlsx file paths for server contexts.192- `.xlsm` round-trips through `openpyxl` strip macros (HARD-RULE in `ms-office-word-python` for .docm — same applies here in spirit). State this in code or refuse to process .xlsm without explicit user intent.193- Encrypted workbooks routed through `msoffcrypto-tool` produce an in-memory `BytesIO` — large files consume RAM proportional to file size. Stream-decrypt is not supported in 2026-05.194195## Update Triggers (per Codex M-1 — alf will scan these)196197- Major version bump of: `openpyxl`, `xlsxwriter`, `pandas`, `xlrd`, `lxml`, `msoffcrypto-tool`.198- New OOXML / `.xlsx` schema change announced by Microsoft (rare but happens).199- CVE published against `lxml` or `openpyxl` (XXE-class).200- Annual review on: 2027-05-22.201202## See Also203204| Need | Skill |205|---|---|206| Word / Markdown / PDF conversions | `ms-office-word-python` |207| PowerPoint generation | `ms-office-powerpoint-python` |208| Sending an Excel as Outlook attachment | `ms-office-graph-python` |209| Hardening / validator / checklist | `ms-office-security-python` |210| Reading enterprise-encrypted workbooks with cert-based DRM | `python-auth-security` + IRM/RMS coverage (not in v1) |211| Dependency CVE scanning | `dep-currency-check` |