Skip to content

Commit dd2e665

Browse files
committed
release: v2.3
1 parent a60e0ff commit dd2e665

9 files changed

Lines changed: 177 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,64 @@ All notable changes to this project will be documented in this file.
44

55
The format is inspired by Keep a Changelog and versioned according to PEP 440.
66

7+
## [2.3.0] - 2026-04-23
8+
9+
This release continues the stable 2.x line with a more complete import
10+
workflow: clearer template guidance before upload, lightweight structural
11+
preflight before execution, synchronous lifecycle visibility during import, and
12+
compact remediation-oriented payloads after failures.
13+
14+
### Added
15+
16+
- Added additive template UX metadata support through `hint=` and
17+
`example_value=` so generated header comments can provide clearer workbook
18+
input guidance
19+
- Added `ExcelAlchemy.preflight_import(...)` for lightweight structural import
20+
validation before full import execution
21+
- Added `ImportPreflightResult` and `ImportPreflightStatus` as stable public
22+
preflight result types
23+
- Added additive `on_event=` support on `ExcelAlchemy.import_data(...)` for
24+
synchronous import lifecycle callbacks
25+
- Added `build_frontend_remediation_payload(...)` for compact retry-oriented
26+
remediation payloads alongside the existing import result surfaces
27+
- Added a dedicated `WorksheetNotFoundError` exception so sheet-missing
28+
preflight classification does not rely on backend-specific message parsing
29+
30+
### Changed
31+
32+
- Extended the import workflow so applications can combine template guidance,
33+
preflight validation, lifecycle event observation, and remediation payloads
34+
without replacing the existing full import API
35+
- Kept `ExcelAlchemy.import_data(...)` as the full validation and execution
36+
path while clarifying that `preflight_import(...)` is structural only
37+
- Updated storage-backed workbook reading so preflight maps only explicit
38+
worksheet-missing failures to `SHEET_MISSING` and re-raises unrelated
39+
storage/runtime failures
40+
- Refined template comment rendering for single-staff guidance formatting
41+
- Expanded contract coverage for:
42+
- preflight header validation
43+
- missing and extra field handling
44+
- row-count estimation
45+
- import lifecycle event payloads
46+
- remediation payload behavior
47+
48+
### Documentation
49+
50+
- Updated `README.md`, `README-pypi.md`, and onboarding docs to describe the
51+
additive template guidance metadata
52+
- Updated `docs/getting-started.md` with practical preflight usage guidance and
53+
a `preflight -> import` workflow example
54+
- Updated `docs/public-api.md` and `docs/result-objects.md` to document
55+
`preflight_import(...)`, `ImportPreflightResult`, lifecycle callbacks, and
56+
remediation payload helpers
57+
- Updated `docs/architecture.md`, `docs/domain-model.md`, examples, and
58+
reference-app guidance to reflect the broader import workflow story
59+
- Added design plans under `plans/` for:
60+
- template UX metadata v1
61+
- job-friendly import lifecycle events v1
62+
- import preflight v1
63+
- front-end remediation payload v1
64+
765
## [2.2.8] - 2026-04-05
866

967
This release continues the stable 2.x line with a clearer integration reading

README-pypi.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ ExcelAlchemy turns Pydantic models into typed workbook contracts:
1010
- render workbook-facing output in `zh-CN` or `en`
1111
- keep storage pluggable through `ExcelStorage`
1212

13-
The current stable release is `2.2.8`, which continues the 2.x line with a clearer integration roadmap, stronger import-failure payload smoke verification, and more direct install-time validation of the FastAPI reference app.
13+
The current stable release is `2.3.0`, which continues the 2.x line with a
14+
more complete import workflow: clearer template guidance before upload,
15+
lightweight structural preflight before execution, synchronous lifecycle
16+
visibility during import, and remediation-oriented payloads after failures.
1417

1518
[GitHub Repository](https://github.com/RayCarterLab/ExcelAlchemy) · [Full README](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/README.md) · [Getting Started](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/getting-started.md) · [Integration Roadmap](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/integration-roadmap.md) · [Result Objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md) · [API Response Cookbook](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/api-response-cookbook.md) · [Examples Showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md) · [Architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/architecture.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/MIGRATIONS.md)
1619

README.md

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@ This repository is also a design artifact.
2020
It documents a series of deliberate engineering choices: `src/` layout, Pydantic v2 migration, pandas removal,
2121
pluggable storage, `uv`-based workflows, and locale-aware workbook output.
2222

23-
The current stable release is `2.2.8`, which continues the ExcelAlchemy 2.x line with a clearer integration roadmap, stronger import-failure payload smoke verification, and more direct install-time validation of the FastAPI reference app.
23+
The current stable release is `2.3.0`, which continues the ExcelAlchemy 2.x
24+
line with a more complete import workflow: clearer template guidance before
25+
upload, lightweight structural preflight before execution, synchronous
26+
lifecycle visibility during import, and remediation-oriented payloads after
27+
failures.
2428

2529
## At a Glance
2630

2731
- Build Excel templates directly from typed Pydantic schemas
32+
- Guide users with workbook-facing input hints and examples
33+
- Run lightweight structural preflight checks before full import
2834
- Validate uploaded workbooks and write failures back to rows and cells
35+
- Observe import lifecycle progress through additive callbacks
36+
- Build remediation-oriented payloads for retry workflows
2937
- Keep storage pluggable through `ExcelStorage`
3038
- Render workbook-facing text in `zh-CN` or `en`
3139
- Stay lightweight at runtime with `openpyxl` instead of pandas
@@ -91,6 +99,89 @@ and a concrete example value.
9199
For browser downloads, prefer `template.as_bytes()` with a `Blob`, or return the bytes from your backend with
92100
`Content-Disposition: attachment`. A top-level navigation to a long `data:` URL is less reliable in modern browsers.
93101

102+
## Import Workflow
103+
104+
ExcelAlchemy is designed to work as a product-ready import layer rather than
105+
only a row-validation helper.
106+
107+
The practical workflow in the 2.x line is:
108+
109+
- generate a template with workbook-facing guidance
110+
- run `preflight_import(...)` as a lightweight structural gate
111+
- run `import_data(..., on_event=...)` for full validation and execution
112+
- build remediation-oriented payloads if the import fails
113+
114+
Use `preflight_import(...)` when you want a fast answer to:
115+
116+
- does the configured sheet exist
117+
- do the workbook headers match the schema
118+
- is the workbook structurally importable
119+
120+
Use `import_data(...)` when you want the full workflow:
121+
122+
- row validation
123+
- create / update callback execution
124+
- result workbook rendering
125+
- structured row and cell failure output
126+
127+
Short example:
128+
129+
```python
130+
from pydantic import BaseModel
131+
132+
from excelalchemy import ExcelAlchemy, Email, FieldMeta, ImporterConfig, Number, String
133+
from excelalchemy.results import build_frontend_remediation_payload
134+
135+
136+
class EmployeeImporter(BaseModel):
137+
full_name: String = FieldMeta(label='Full name', order=1, hint='Use the legal name')
138+
age: Number = FieldMeta(label='Age', order=2)
139+
work_email: Email = FieldMeta(label='Work email', order=3, example_value='alice@company.com')
140+
141+
142+
async def create_employee(row: dict[str, object], context: dict[str, object] | None) -> dict[str, object]:
143+
return row
144+
145+
146+
alchemy = ExcelAlchemy(
147+
ImporterConfig.for_create(
148+
EmployeeImporter,
149+
creator=create_employee,
150+
storage=storage,
151+
locale='en',
152+
)
153+
)
154+
155+
template = alchemy.download_template_artifact(filename='employee-template.xlsx')
156+
157+
preflight = alchemy.preflight_import('employees.xlsx')
158+
if not preflight.is_valid:
159+
response = {'preflight': preflight.to_api_payload()}
160+
else:
161+
events: list[dict[str, object]] = []
162+
result = await alchemy.import_data(
163+
'employees.xlsx',
164+
'employees-result.xlsx',
165+
on_event=events.append,
166+
)
167+
168+
response = {
169+
'result': result.to_api_payload(),
170+
'events': events,
171+
'remediation': build_frontend_remediation_payload(
172+
result=result,
173+
cell_error_map=alchemy.cell_error_map,
174+
row_error_map=alchemy.row_error_map,
175+
),
176+
}
177+
```
178+
179+
This keeps one clear separation:
180+
181+
- template and preflight help before execution
182+
- import handles real validation and persistence
183+
- remediation helps API and frontend retry flows after failure
184+
94185
## When To Use / When Not To Use / Limitations & Gotchas
95186

96187
### When To Use
@@ -271,6 +362,7 @@ Import workflow output:
271362

272363
```text
273364
Employee import workflow completed
365+
Preflight: VALID
274366
Result: SUCCESS
275367
Success rows: 1
276368
Failed rows: 0

docs/examples-showcase.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ see
3030
Best entry point if you want to understand the core story:
3131

3232
- generate a workbook template
33+
- run a lightweight structural preflight
3334
- accept a filled workbook
3435
- validate the upload
3536
- create domain rows
@@ -43,6 +44,7 @@ Fixed output:
4344

4445
```text
4546
Employee import workflow completed
47+
Preflight: VALID
4648
Result: SUCCESS
4749
Success rows: 1
4850
Failed rows: 0

docs/integration-roadmap.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ Recommended order:
4040

4141
Focus on these objects:
4242

43+
- `ImportPreflightResult`
4344
- `ImportResult`
4445
- `CellErrorMap`
4546
- `RowIssueMap`
4647

4748
Use these payload helpers directly in your API layer:
4849

50+
- `ExcelAlchemy.preflight_import(...)` when the endpoint wants a lightweight structural gate before the real import
4951
- `ImportResult.to_api_payload()`
5052
- `CellErrorMap.to_api_payload()`
5153
- `RowIssueMap.to_api_payload()`

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Use them to understand how the library is intended to be used from application c
3636
- Type: demo of the recommended declaration style.
3737

3838
- `examples/employee_import_workflow.py`
39-
- Demonstrates the basic import flow from template generation through import result handling.
39+
- Demonstrates the basic import flow from template generation through preflight, lifecycle-event observation, and import result handling.
4040
- Type: runnable workflow demo.
4141

4242
- `examples/create_or_update_import.py`

examples/employee_import_workflow.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
ExcelStorage,
1414
FieldMeta,
1515
ImporterConfig,
16+
ImportPreflightResult,
1617
ImportResult,
1718
Number,
1819
String,
@@ -82,7 +83,13 @@ async def create_employee(row: dict[str, object], context: dict[str, object] | N
8283
return row
8384

8485

85-
async def run_workflow() -> tuple[ImportResult, InMemoryImportStorage, dict[str, object], list[dict[str, object]]]:
86+
async def run_workflow() -> tuple[
87+
ImportPreflightResult,
88+
ImportResult,
89+
InMemoryImportStorage,
90+
dict[str, object],
91+
list[dict[str, object]],
92+
]:
8693
storage = InMemoryImportStorage()
8794
context: dict[str, object] = {
8895
'created_rows': [],
@@ -106,6 +113,8 @@ async def run_workflow() -> tuple[ImportResult, InMemoryImportStorage, dict[str,
106113

107114
template = alchemy.download_template_artifact(filename='employee-template.xlsx')
108115
_build_import_fixture(storage, template.as_bytes())
116+
preflight = alchemy.preflight_import('employee-import.xlsx')
117+
assert preflight.is_valid
109118

110119
def handle_import_event(event: dict[str, object]) -> None:
111120
events.append(event)
@@ -131,17 +140,18 @@ def handle_import_event(event: dict[str, object]) -> None:
131140
'employee-import-result.xlsx',
132141
on_event=handle_import_event,
133142
)
134-
return result, storage, context, events
143+
return preflight, result, storage, context, events
135144

136145

137146
def main() -> None:
138-
result, storage, context, events = asyncio.run(run_workflow())
147+
preflight, result, storage, context, events = asyncio.run(run_workflow())
139148
created_rows = context['created_rows']
140149
job_progress = context['job_progress']
141150
assert isinstance(created_rows, list)
142151
assert isinstance(job_progress, dict)
143152

144153
print('Employee import workflow completed')
154+
print(f'Preflight: {preflight.status}')
145155
print(f'Result: {result.result}')
146156
print(f'Success rows: {result.success_count}')
147157
print(f'Failed rows: {result.fail_count}')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
Employee import workflow completed
2+
Preflight: VALID
23
Result: SUCCESS
34
Success rows: 1
45
Failed rows: 0
56
Result workbook URL: None
67
Created rows: 1
78
Uploaded artifacts: []
9+
Observed events: ['started', 'header_validated', 'row_processed', 'completed']
10+
Job progress: {'status': 'completed', 'processed_rows': 1, 'total_rows': 1, 'result': 'SUCCESS', 'result_workbook_url': None}

src/excelalchemy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""A Python Library for Reading and Writing Excel Files"""
22

3-
__version__ = '2.2.8'
3+
__version__ = '2.3.0'
44
from excelalchemy._primitives.constants import CharacterSet, DataRangeOption, DateFormat, Option
55
from excelalchemy._primitives.deprecation import ExcelAlchemyDeprecationWarning
66
from excelalchemy._primitives.identity import (

0 commit comments

Comments
 (0)