Skip to content

Commit 999b62e

Browse files
authored
Merge pull request #63 from RayCarterLab/v2.2
release: prepare ExcelAlchemy 2.2.4
2 parents 6990613 + 9415da9 commit 999b62e

48 files changed

Lines changed: 1468 additions & 343 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,42 @@ the Pydantic adapter layer.
2020
- Added a regression test for unsupported annotated declarations to prevent
2121
native Python annotations from slipping through the workbook schema path
2222

23+
### Changed
24+
25+
- Clarified `FieldMetaInfo` as a compatibility facade over layered metadata
26+
objects instead of treating it as the primary internal metadata model
27+
- Moved more core consumers and built-in codecs onto the layered metadata
28+
objects (`declared`, `runtime`, `presentation`, and `constraints`)
29+
- Continued reducing the effective responsibility carried by the flat
30+
`FieldMetaInfo` compatibility surface in the 2.x implementation
31+
- Concentrated necessary dynamic typing boundaries into explicit aliases in the
32+
codec and metadata layers instead of leaving ad hoc `Any` usage scattered
33+
across the codebase
34+
- Replaced a number of remaining loose `Any` annotations in the runtime path
35+
with more explicit `object` or workbook-boundary aliases where the behavior
36+
was already concrete
37+
- Added smoke coverage for the repository examples so the annotated schema and
38+
custom storage examples are exercised directly in tests
39+
2340
### Compatibility Notes
2441

2542
- No public import or export workflow API was removed in this release
2643
- Valid `ExcelFieldCodec` and `CompositeExcelFieldCodec` declarations continue
2744
to work unchanged
2845
- Unsupported native annotations with `ExcelMeta(...)` now fail early with the
2946
intended `ProgrammaticError`
47+
- `FieldMeta(...)` and `ExcelMeta(...)` remain the stable public metadata entry
48+
points while internal metadata continues to consolidate behind them
3049

3150
### Release Summary
3251

3352
- unsupported annotated declarations now fail with the intended error again
3453
- codec resolution is stricter and easier to reason about
3554
- the validation fix is protected by an explicit integration regression test
55+
- metadata internals continue to move toward layered objects rather than a flat
56+
central record
57+
- runtime typing boundaries are more explicit without changing the public API
58+
- repository examples now have direct smoke coverage in the test suite
3659

3760
## [2.2.2] - 2026-04-03
3861

MIGRATIONS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ Additional top-level module guidance:
124124
- `excelalchemy.header_models` is internal and should not be imported in application code
125125
- `docs/public-api.md` summarizes stable public modules, compatibility modules, and internal modules
126126

127+
## Import Inspection Names
128+
129+
The 2.2 line also clarifies the recommended names for inspecting import-run
130+
state from the facade:
131+
132+
- prefer `worksheet_table` over `df`
133+
- prefer `header_table` over `header_df`
134+
- prefer `cell_error_map` over `cell_errors`
135+
- prefer `row_error_map` over `row_errors`
136+
137+
The old names still work as compatibility aliases in the 2.x line, but new
138+
code should use the clearer names above.
139+
127140
## Recommended Upgrade Checklist
128141

129142
1. Upgrade your Python runtime to 3.12+.

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,44 @@ pip install "ExcelAlchemy[minio]"
178178
Practical examples live in the repository:
179179

180180
- [`examples/annotated_schema.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/annotated_schema.py)
181+
- [`examples/employee_import_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/employee_import_workflow.py)
182+
- [`examples/create_or_update_import.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/create_or_update_import.py)
183+
- [`examples/date_and_range_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/date_and_range_fields.py)
184+
- [`examples/selection_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/selection_fields.py)
181185
- [`examples/custom_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/custom_storage.py)
186+
- [`examples/export_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/export_workflow.py)
187+
- [`examples/minio_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/minio_storage.py)
182188
- [`examples/fastapi_upload.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/fastapi_upload.py)
189+
- [`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md)
190+
191+
If you want the recommended reading order, start with
192+
[`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md).
183193

184194
## Public API Boundaries
185195

186196
If you want to know which modules are stable public entry points versus
187197
compatibility shims or internal modules, see
188198
[`docs/public-api.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/public-api.md).
189199

200+
## Import Inspection Names
201+
202+
When you inspect import-run state from the facade, prefer the clearer 2.2 names:
203+
204+
- `alchemy.worksheet_table`
205+
- `alchemy.header_table`
206+
- `alchemy.cell_error_map`
207+
- `alchemy.row_error_map`
208+
209+
The older aliases:
210+
211+
- `alchemy.df`
212+
- `alchemy.header_df`
213+
- `alchemy.cell_errors`
214+
- `alchemy.row_errors`
215+
216+
still work in the 2.x line as compatibility paths, but new application code
217+
should use the clearer names above.
218+
190219
## Locale-Aware Workbook Output
191220

192221
`locale` affects workbook-facing display text such as:
@@ -316,7 +345,7 @@ The short version:
316345
| Topic | v1-style risk | Current v2 design |
317346
| --- | --- | --- |
318347
| Field access | Tight coupling to `__fields__` / `ModelField` | Adapter over `model_fields` |
319-
| Metadata ownership | Excel metadata mixed with validation internals | `FieldMetaInfo` owns Excel metadata |
348+
| Metadata ownership | Excel metadata mixed with validation internals | `FieldMetaInfo` is a compatibility facade over layered Excel metadata |
320349
| Validation integration | Deep reliance on internals | Adapter + explicit runtime validation |
321350
| Upgrade path | Brittle | Layered |
322351

README_cn.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ pip install ExcelAlchemy
102102
pip install "ExcelAlchemy[minio]"
103103
```
104104

105+
## 示例
106+
107+
仓库里有一组更贴近实际接入的示例:
108+
109+
- [`examples/annotated_schema.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/annotated_schema.py)
110+
- [`examples/employee_import_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/employee_import_workflow.py)
111+
- [`examples/create_or_update_import.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/create_or_update_import.py)
112+
- [`examples/date_and_range_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/date_and_range_fields.py)
113+
- [`examples/selection_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/selection_fields.py)
114+
- [`examples/custom_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/custom_storage.py)
115+
- [`examples/export_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/export_workflow.py)
116+
- [`examples/minio_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/minio_storage.py)
117+
- [`examples/fastapi_upload.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/fastapi_upload.py)
118+
- [`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md)
119+
120+
如果你想按推荐顺序来阅读,建议先看
121+
[`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md)
122+
105123
## 快速开始
106124

107125
```python
@@ -192,6 +210,24 @@ alchemy = ExcelAlchemy(ExporterConfig(Importer, storage=InMemoryExcelStorage()))
192210

193211
如果你希望使用内置 Minio 实现,推荐显式传入 `storage=MinioStorageGateway(...)`,而不是再把 Minio 配置散落到门面层。
194212

213+
## 导入结果状态查看命名
214+
215+
如果你需要从 facade 上查看一次导入后的中间状态,推荐使用 2.2 这套更清晰的命名:
216+
217+
- `alchemy.worksheet_table`
218+
- `alchemy.header_table`
219+
- `alchemy.cell_error_map`
220+
- `alchemy.row_error_map`
221+
222+
旧别名:
223+
224+
- `alchemy.df`
225+
- `alchemy.header_df`
226+
- `alchemy.cell_errors`
227+
- `alchemy.row_errors`
228+
229+
在 2.x 里仍然可用,用于兼容旧代码;但新代码建议统一使用前面这组更明确的名字。
230+
195231
## 为什么这样设计
196232

197233
### 为什么去掉 pandas
@@ -210,7 +246,7 @@ alchemy = ExcelAlchemy(ExporterConfig(Importer, storage=InMemoryExcelStorage()))
210246
Excel 元数据不应该深绑到 Pydantic 内部结构上。
211247
所以现在的分层是:
212248

213-
- `FieldMetaInfo` 负责 Excel 元数据
249+
- `FieldMetaInfo` 是对外兼容 façade,内部再组合声明层、运行时绑定层、展示层和导入约束层
214250
- `helper/pydantic.py` 只做适配
215251
- 真正的业务校验仍然由 ExcelAlchemy 控制
216252

docs/architecture.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ flowchart LR
9999

100100
`src/excelalchemy/metadata.py`
101101

102-
- owns Excel field metadata
103-
- exposes workbook comment fragments
102+
- exposes `FieldMeta(...)` / `ExcelMeta(...)` as the stable public entry points
103+
- keeps `FieldMetaInfo` as a compatibility facade for the 2.x line
104+
- splits the real metadata state into declaration, runtime binding,
105+
workbook presentation, and import-constraint layers
104106
- keeps runtime metadata separate from validation backend internals
105107

106108
### Pydantic Integration

docs/public-api.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ These modules are the recommended import paths for application code:
3434
The recommended backend configuration pattern in the 2.x line.
3535
- `ExcelArtifact`
3636
The recommended return shape when you need bytes, base64, or data URLs.
37+
- import inspection names:
38+
Prefer `worksheet_table`, `header_table`, `cell_error_map`, and
39+
`row_error_map` when reading import-run state from the facade.
3740

3841
## Compatibility Modules In 2.x
3942

@@ -92,3 +95,10 @@ direction is:
9295
and `excelalchemy.codecs`
9396
- backend integration through `ExcelStorage`
9497
- internal orchestration and helper modules treated as implementation details
98+
99+
For import-run state naming, the long-term direction is also:
100+
101+
- clear facade inspection names such as `worksheet_table`, `header_table`,
102+
`cell_error_map`, and `row_error_map`
103+
- older aliases such as `df`, `header_df`, `cell_errors`, and `row_errors`
104+
retained only as 2.x compatibility paths

docs/releases/2.2.3.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ line.
99
- present `2.2.3` as a focused validation-correctness release
1010
- restore the intended failure mode for unsupported annotated declarations
1111
- ship regression coverage for the declaration guard in the Pydantic adapter
12+
- continue consolidating the remaining runtime typing gray areas
13+
- add direct smoke coverage for repository examples
1214

1315
## Release Positioning
1416

@@ -19,6 +21,8 @@ line.
1921
- unsupported native annotations with `ExcelMeta(...)` now fail early with the
2022
intended `ProgrammaticError`
2123
- the validation path is safer and easier to reason about
24+
- runtime typing boundaries are more explicit in core codec and metadata paths
25+
- repository examples now have direct smoke coverage in tests
2226

2327
## Before Tagging
2428

@@ -28,6 +32,8 @@ line.
2832
`src/excelalchemy/helper/pydantic.py`.
2933
4. Confirm the regression test in
3034
`tests/integration/test_excelalchemy_workflows.py`.
35+
5. Confirm the example smoke tests in
36+
`tests/integration/test_examples_smoke.py`.
3137

3238
## Local Verification
3339

@@ -36,9 +42,11 @@ Run these commands from the repository root:
3642
```bash
3743
uv sync --extra development
3844
uv run ruff check src/excelalchemy/helper/pydantic.py \
39-
tests/integration/test_excelalchemy_workflows.py
45+
tests/integration/test_excelalchemy_workflows.py \
46+
tests/integration/test_examples_smoke.py
4047
uv run pyright
41-
uv run pytest tests/integration/test_excelalchemy_workflows.py -q
48+
uv run pytest tests/integration/test_excelalchemy_workflows.py \
49+
tests/integration/test_examples_smoke.py -q
4250
rm -rf dist
4351
uv build
4452
uvx twine check dist/*
@@ -60,6 +68,8 @@ themes clearly:
6068
- unsupported annotated declarations now fail with the intended error again
6169
- codec resolution in the Pydantic adapter is stricter and more explicit
6270
- the fix is protected by a regression test
71+
- runtime typing boundaries are more explicit and less ad hoc
72+
- repository examples are exercised directly in the test suite
6373

6474
## Recommended Release Messaging
6575

@@ -69,6 +79,8 @@ Prefer wording that emphasizes stability and correctness:
6979
- "restores the intended ProgrammaticError path"
7080
- "tightens codec resolution in the Pydantic adapter"
7181
- "adds regression coverage for unsupported annotated declarations"
82+
- "continues tightening runtime typing boundaries without breaking the public API"
83+
- "adds smoke coverage for repository examples"
7284

7385
## Done When
7486

examples/README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Examples
2+
3+
These examples are organized as a recommended learning path rather than a flat list.
4+
5+
## Recommended Reading Order
6+
7+
1. `annotated_schema.py`
8+
- Start here if you want to learn the declaration style first.
9+
- Shows the modern `Annotated[..., Field(...), ExcelMeta(...)]` pattern.
10+
2. `employee_import_workflow.py`
11+
- Read this next if you want to understand the core import story.
12+
- Shows template generation, workbook upload, import execution, and result reporting.
13+
3. `create_or_update_import.py`
14+
- Read this after the basic import flow.
15+
- Shows `ImporterConfig.for_create_or_update(...)` with `is_data_exist`, `creator`, and `updater`.
16+
4. `export_workflow.py`
17+
- Read this once the import flow is clear.
18+
- Shows artifact generation, export uploads, and a custom storage-backed export task.
19+
5. `custom_storage.py`
20+
- Read this when you want to implement your own `ExcelStorage`.
21+
- Keeps the example minimal and focused on the protocol boundary.
22+
6. `date_and_range_fields.py`
23+
- Read this if you want to understand workbook-friendly date, date range, number range, and money fields.
24+
7. `selection_fields.py`
25+
- Read this if your domain uses approval forms, assignments, ownership trees, or selection-heavy templates.
26+
8. `minio_storage.py`
27+
- Read this if you need the built-in Minio path in the current 2.x line.
28+
- This reflects the current 2.x compatibility-based Minio path rather than a future 3.x-only storage story.
29+
9. `fastapi_upload.py`
30+
- Read this last as an integration sketch.
31+
- It is useful once the import and storage examples already make sense.
32+
33+
## By Goal
34+
35+
- Learn the declaration style:
36+
- `annotated_schema.py`
37+
- Learn the core import flow:
38+
- `employee_import_workflow.py`
39+
- `create_or_update_import.py`
40+
- Learn export and storage integration:
41+
- `export_workflow.py`
42+
- `custom_storage.py`
43+
- `minio_storage.py`
44+
- Learn field families:
45+
- `date_and_range_fields.py`
46+
- `selection_fields.py`
47+
- Learn web integration:
48+
- `fastapi_upload.py`
49+
50+
## Storage and Backend Integration
51+
52+
- `custom_storage.py`
53+
- Shows a minimal custom `ExcelStorage` implementation for export uploads.
54+
- `export_workflow.py`
55+
- Shows a realistic export flow with artifact generation and upload.
56+
- `minio_storage.py`
57+
- Shows the built-in Minio-backed storage path currently available in the 2.x line.
58+
- `fastapi_upload.py`
59+
- Shows a FastAPI integration sketch for template download and workbook import.
60+
61+
## How To Run
62+
63+
Run examples from the repository root:
64+
65+
```bash
66+
uv run python examples/annotated_schema.py
67+
uv run python examples/employee_import_workflow.py
68+
uv run python examples/create_or_update_import.py
69+
uv run python examples/date_and_range_fields.py
70+
uv run python examples/selection_fields.py
71+
uv run python examples/custom_storage.py
72+
uv run python examples/export_workflow.py
73+
uv run python examples/minio_storage.py
74+
```
75+
76+
If you want to try the FastAPI sketch, install FastAPI first and then run your
77+
preferred ASGI server against `examples.fastapi_upload:app`.
78+
79+
## Notes
80+
81+
- The examples intentionally use in-memory storage so they stay self-contained.
82+
- They are meant to show the recommended public API shape for the stable 2.x
83+
line.
84+
- If you want a production backend, prefer `storage=...` with
85+
`MinioStorageGateway` or your own `ExcelStorage` implementation.
86+
- The built-in `minio_storage.py` example reflects the current 2.x Minio path,
87+
which still uses the compatibility configuration fields under the hood.
88+
- The smoke tests in `tests/integration/test_examples_smoke.py` cover the main
89+
example entry points directly.

examples/annotated_schema.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
"""Minimal example that uses Annotated + ExcelMeta declarations."""
22

3-
from __future__ import annotations
4-
53
from typing import Annotated
64

75
from pydantic import BaseModel, Field
86

9-
from excelalchemy import Email, ExcelAlchemy, ExcelMeta, ImporterConfig, Number
7+
from excelalchemy import Email, ExcelAlchemy, ExcelMeta, ImporterConfig, Number, String
108

119

1210
class EmployeeImporter(BaseModel):
13-
full_name: Annotated[str, Field(min_length=2), ExcelMeta(label='Full name', order=1, hint='Use the legal name')]
11+
full_name: Annotated[
12+
String,
13+
Field(min_length=2),
14+
ExcelMeta(label='Full name', order=1, hint='Use the legal name'),
15+
]
1416
age: Annotated[Number, Field(ge=18), ExcelMeta(label='Age', order=2)]
1517
work_email: Annotated[
1618
Email,

0 commit comments

Comments
 (0)