Skip to content

Commit a60e0ff

Browse files
committed
Add import preflight workflow
1 parent b9708b3 commit a60e0ff

14 files changed

Lines changed: 1286 additions & 10 deletions

docs/getting-started.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,14 @@ These two documents explain:
193193
If you are integrating ExcelAlchemy into a web backend, the recommended public
194194
result surface is:
195195

196+
- `ImportPreflightResult`
196197
- `ImportResult`
197198
- `alchemy.cell_error_map`
198199
- `alchemy.row_error_map`
199200

200201
These objects let you return:
201202

203+
- a lightweight preflight summary before import
202204
- a high-level import summary
203205
- row-level error summaries
204206
- cell-level coordinates for UI highlighting
@@ -208,3 +210,44 @@ See:
208210
- [`docs/result-objects.md`](result-objects.md)
209211
- [`docs/api-response-cookbook.md`](api-response-cookbook.md)
210212
- [`examples/fastapi_reference/README.md`](../examples/fastapi_reference/README.md)
213+
214+
## 9. Use Preflight Before Import When You Need A Quick Structural Check
215+
216+
Use `preflight_import(...)` when you want a fast answer to:
217+
218+
- does the configured sheet exist
219+
- do the headers match the schema
220+
- does the workbook look structurally importable
221+
- about how many rows would a later import process
222+
223+
Use `import_data(...)` when you want the full workflow:
224+
225+
- row validation
226+
- create / update callback execution
227+
- cell and row error maps
228+
- result workbook rendering and upload
229+
230+
Typical backend flow:
231+
232+
```python
233+
preflight = alchemy.preflight_import('employees.xlsx')
234+
235+
if not preflight.is_valid:
236+
return {
237+
'preflight': preflight.to_api_payload(),
238+
}
239+
240+
result = await alchemy.import_data('employees.xlsx', 'employees-result.xlsx')
241+
242+
return {
243+
'preflight': preflight.to_api_payload(),
244+
'result': result.to_api_payload(),
245+
'cell_errors': alchemy.cell_error_map.to_api_payload(),
246+
'row_errors': alchemy.row_error_map.to_api_payload(),
247+
}
248+
```
249+
250+
Keep this distinction in mind:
251+
252+
- preflight is lightweight and structural
253+
- import is the full validation and execution path

docs/public-api.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ These modules are the recommended import paths for application code:
3434
this additive public surface.
3535
- `excelalchemy.results`
3636
Structured import result models such as `ImportResult`,
37-
`ValidateResult`, and `ValidateHeaderResult`.
37+
`ValidateResult`, `ValidateHeaderResult`, `ImportPreflightResult`, and
38+
`ImportPreflightStatus`.
3839
- `excelalchemy.exceptions`
3940
Stable exception module for `ConfigError`, `ExcelCellError`,
4041
`ExcelRowError`, and `ProgrammaticError`.
@@ -52,6 +53,9 @@ These modules are the recommended import paths for application code:
5253
- `ExcelAlchemy.import_data(..., on_event=...)`
5354
The additive public hook for synchronous import lifecycle events during one
5455
import run.
56+
- `ExcelAlchemy.preflight_import(...)`
57+
The additive public hook for lightweight structural validation before full
58+
import execution.
5559
- import inspection names:
5660
Prefer `worksheet_table`, `header_table`, `cell_error_map`, and
5761
`row_error_map` when reading import-run state from the facade.

docs/result-objects.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ If you want copyable success / failure / header-invalid response shapes, see
1414

1515
The most important public result objects are:
1616

17+
- `ImportPreflightResult`
1718
- `ImportResult`
1819
- `CellErrorMap`
1920
- `RowIssueMap`
@@ -302,6 +303,100 @@ response = {
302303
}
303304
```
304305

306+
## `ImportPreflightResult`
307+
308+
`ImportPreflightResult` is the high-level summary of one lightweight structural
309+
preflight run.
310+
311+
Useful fields include:
312+
313+
- `status`
314+
Overall status such as `VALID`, `HEADER_INVALID`, `SHEET_MISSING`, or
315+
`STRUCTURE_INVALID`.
316+
- `sheet_name`
317+
The configured worksheet name used for preflight.
318+
- `sheet_exists`
319+
Whether the configured worksheet was found.
320+
- `has_merged_header`
321+
Whether the header block was detected as merged when readable.
322+
- `estimated_row_count`
323+
Estimated number of data rows for a later import run.
324+
- `structural_issue_codes`
325+
Stable machine-readable codes for non-header structural failures.
326+
327+
Typical usage:
328+
329+
```python
330+
result = alchemy.preflight_import('employees.xlsx')
331+
332+
if result.is_valid:
333+
...
334+
```
335+
336+
Use preflight when you need a quick structural gate before the real import.
337+
338+
Practical cases:
339+
340+
- reject uploads that are missing the target sheet
341+
- stop early when headers do not match the schema
342+
- show a lightweight “looks importable” response before running row validation
343+
344+
Do not treat preflight as a replacement for `import_data(...)`.
345+
346+
Preflight does not do:
347+
348+
- row-level validation
349+
- create / update execution
350+
- cell or row error collection
351+
- result workbook generation
352+
353+
Useful helpers:
354+
355+
- `is_valid`
356+
- `is_header_invalid`
357+
- `is_sheet_missing`
358+
- `is_structure_invalid`
359+
- `to_api_payload()`
360+
361+
Example payload:
362+
363+
```json
364+
{
365+
"status": "HEADER_INVALID",
366+
"is_valid": false,
367+
"is_header_invalid": true,
368+
"is_sheet_missing": false,
369+
"is_structure_invalid": false,
370+
"sheet": {
371+
"name": "Sheet1",
372+
"exists": true,
373+
"has_merged_header": false
374+
},
375+
"summary": {
376+
"estimated_row_count": 3,
377+
"structural_issue_codes": []
378+
},
379+
"header_issues": {
380+
"is_required_missing": true,
381+
"missing_required": ["Age"],
382+
"missing_primary": [],
383+
"unrecognized": ["Unexpected Column"],
384+
"duplicated": []
385+
}
386+
}
387+
```
388+
389+
Simple workflow:
390+
391+
```python
392+
preflight = alchemy.preflight_import('employees.xlsx')
393+
394+
if not preflight.is_valid:
395+
return {'preflight': preflight.to_api_payload()}
396+
397+
result = await alchemy.import_data('employees.xlsx', 'employees-result.xlsx')
398+
```
399+
305400
This gives you:
306401

307402
- a stable top-level import summary

0 commit comments

Comments
 (0)