Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,42 @@ the Pydantic adapter layer.
- Added a regression test for unsupported annotated declarations to prevent
native Python annotations from slipping through the workbook schema path

### Changed

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

### Compatibility Notes

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

### Release Summary

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

## [2.2.2] - 2026-04-03

Expand Down
13 changes: 13 additions & 0 deletions MIGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ Additional top-level module guidance:
- `excelalchemy.header_models` is internal and should not be imported in application code
- `docs/public-api.md` summarizes stable public modules, compatibility modules, and internal modules

## Import Inspection Names

The 2.2 line also clarifies the recommended names for inspecting import-run
state from the facade:

- prefer `worksheet_table` over `df`
- prefer `header_table` over `header_df`
- prefer `cell_error_map` over `cell_errors`
- prefer `row_error_map` over `row_errors`

The old names still work as compatibility aliases in the 2.x line, but new
code should use the clearer names above.

## Recommended Upgrade Checklist

1. Upgrade your Python runtime to 3.12+.
Expand Down
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,44 @@ pip install "ExcelAlchemy[minio]"
Practical examples live in the repository:

- [`examples/annotated_schema.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/annotated_schema.py)
- [`examples/employee_import_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/employee_import_workflow.py)
- [`examples/create_or_update_import.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/create_or_update_import.py)
- [`examples/date_and_range_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/date_and_range_fields.py)
- [`examples/selection_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/selection_fields.py)
- [`examples/custom_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/custom_storage.py)
- [`examples/export_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/export_workflow.py)
- [`examples/minio_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/minio_storage.py)
- [`examples/fastapi_upload.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/fastapi_upload.py)
- [`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md)

If you want the recommended reading order, start with
[`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md).

## Public API Boundaries

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

## Import Inspection Names

When you inspect import-run state from the facade, prefer the clearer 2.2 names:

- `alchemy.worksheet_table`
- `alchemy.header_table`
- `alchemy.cell_error_map`
- `alchemy.row_error_map`

The older aliases:

- `alchemy.df`
- `alchemy.header_df`
- `alchemy.cell_errors`
- `alchemy.row_errors`

still work in the 2.x line as compatibility paths, but new application code
should use the clearer names above.

## Locale-Aware Workbook Output

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

Expand Down
38 changes: 37 additions & 1 deletion README_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ pip install ExcelAlchemy
pip install "ExcelAlchemy[minio]"
```

## 示例

仓库里有一组更贴近实际接入的示例:

- [`examples/annotated_schema.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/annotated_schema.py)
- [`examples/employee_import_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/employee_import_workflow.py)
- [`examples/create_or_update_import.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/create_or_update_import.py)
- [`examples/date_and_range_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/date_and_range_fields.py)
- [`examples/selection_fields.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/selection_fields.py)
- [`examples/custom_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/custom_storage.py)
- [`examples/export_workflow.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/export_workflow.py)
- [`examples/minio_storage.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/minio_storage.py)
- [`examples/fastapi_upload.py`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/fastapi_upload.py)
- [`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md)

如果你想按推荐顺序来阅读,建议先看
[`examples/README.md`](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md)。

## 快速开始

```python
Expand Down Expand Up @@ -192,6 +210,24 @@ alchemy = ExcelAlchemy(ExporterConfig(Importer, storage=InMemoryExcelStorage()))

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

## 导入结果状态查看命名

如果你需要从 facade 上查看一次导入后的中间状态,推荐使用 2.2 这套更清晰的命名:

- `alchemy.worksheet_table`
- `alchemy.header_table`
- `alchemy.cell_error_map`
- `alchemy.row_error_map`

旧别名:

- `alchemy.df`
- `alchemy.header_df`
- `alchemy.cell_errors`
- `alchemy.row_errors`

在 2.x 里仍然可用,用于兼容旧代码;但新代码建议统一使用前面这组更明确的名字。

## 为什么这样设计

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

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

Expand Down
6 changes: 4 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ flowchart LR

`src/excelalchemy/metadata.py`

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

### Pydantic Integration
Expand Down
10 changes: 10 additions & 0 deletions docs/public-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ These modules are the recommended import paths for application code:
The recommended backend configuration pattern in the 2.x line.
- `ExcelArtifact`
The recommended return shape when you need bytes, base64, or data URLs.
- import inspection names:
Prefer `worksheet_table`, `header_table`, `cell_error_map`, and
`row_error_map` when reading import-run state from the facade.

## Compatibility Modules In 2.x

Expand Down Expand Up @@ -92,3 +95,10 @@ direction is:
and `excelalchemy.codecs`
- backend integration through `ExcelStorage`
- internal orchestration and helper modules treated as implementation details

For import-run state naming, the long-term direction is also:

- clear facade inspection names such as `worksheet_table`, `header_table`,
`cell_error_map`, and `row_error_map`
- older aliases such as `df`, `header_df`, `cell_errors`, and `row_errors`
retained only as 2.x compatibility paths
16 changes: 14 additions & 2 deletions docs/releases/2.2.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ line.
- present `2.2.3` as a focused validation-correctness release
- restore the intended failure mode for unsupported annotated declarations
- ship regression coverage for the declaration guard in the Pydantic adapter
- continue consolidating the remaining runtime typing gray areas
- add direct smoke coverage for repository examples

## Release Positioning

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

## Before Tagging

Expand All @@ -28,6 +32,8 @@ line.
`src/excelalchemy/helper/pydantic.py`.
4. Confirm the regression test in
`tests/integration/test_excelalchemy_workflows.py`.
5. Confirm the example smoke tests in
`tests/integration/test_examples_smoke.py`.

## Local Verification

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

## Recommended Release Messaging

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

## Done When

Expand Down
89 changes: 89 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Examples

These examples are organized as a recommended learning path rather than a flat list.

## Recommended Reading Order

1. `annotated_schema.py`
- Start here if you want to learn the declaration style first.
- Shows the modern `Annotated[..., Field(...), ExcelMeta(...)]` pattern.
2. `employee_import_workflow.py`
- Read this next if you want to understand the core import story.
- Shows template generation, workbook upload, import execution, and result reporting.
3. `create_or_update_import.py`
- Read this after the basic import flow.
- Shows `ImporterConfig.for_create_or_update(...)` with `is_data_exist`, `creator`, and `updater`.
4. `export_workflow.py`
- Read this once the import flow is clear.
- Shows artifact generation, export uploads, and a custom storage-backed export task.
5. `custom_storage.py`
- Read this when you want to implement your own `ExcelStorage`.
- Keeps the example minimal and focused on the protocol boundary.
6. `date_and_range_fields.py`
- Read this if you want to understand workbook-friendly date, date range, number range, and money fields.
7. `selection_fields.py`
- Read this if your domain uses approval forms, assignments, ownership trees, or selection-heavy templates.
8. `minio_storage.py`
- Read this if you need the built-in Minio path in the current 2.x line.
- This reflects the current 2.x compatibility-based Minio path rather than a future 3.x-only storage story.
9. `fastapi_upload.py`
- Read this last as an integration sketch.
- It is useful once the import and storage examples already make sense.

## By Goal

- Learn the declaration style:
- `annotated_schema.py`
- Learn the core import flow:
- `employee_import_workflow.py`
- `create_or_update_import.py`
- Learn export and storage integration:
- `export_workflow.py`
- `custom_storage.py`
- `minio_storage.py`
- Learn field families:
- `date_and_range_fields.py`
- `selection_fields.py`
- Learn web integration:
- `fastapi_upload.py`

## Storage and Backend Integration

- `custom_storage.py`
- Shows a minimal custom `ExcelStorage` implementation for export uploads.
- `export_workflow.py`
- Shows a realistic export flow with artifact generation and upload.
- `minio_storage.py`
- Shows the built-in Minio-backed storage path currently available in the 2.x line.
- `fastapi_upload.py`
- Shows a FastAPI integration sketch for template download and workbook import.

## How To Run

Run examples from the repository root:

```bash
uv run python examples/annotated_schema.py
uv run python examples/employee_import_workflow.py
uv run python examples/create_or_update_import.py
uv run python examples/date_and_range_fields.py
uv run python examples/selection_fields.py
uv run python examples/custom_storage.py
uv run python examples/export_workflow.py
uv run python examples/minio_storage.py
```

If you want to try the FastAPI sketch, install FastAPI first and then run your
preferred ASGI server against `examples.fastapi_upload:app`.

## Notes

- The examples intentionally use in-memory storage so they stay self-contained.
- They are meant to show the recommended public API shape for the stable 2.x
line.
- If you want a production backend, prefer `storage=...` with
`MinioStorageGateway` or your own `ExcelStorage` implementation.
- The built-in `minio_storage.py` example reflects the current 2.x Minio path,
which still uses the compatibility configuration fields under the hood.
- The smoke tests in `tests/integration/test_examples_smoke.py` cover the main
example entry points directly.
10 changes: 6 additions & 4 deletions examples/annotated_schema.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
"""Minimal example that uses Annotated + ExcelMeta declarations."""

from __future__ import annotations

from typing import Annotated

from pydantic import BaseModel, Field

from excelalchemy import Email, ExcelAlchemy, ExcelMeta, ImporterConfig, Number
from excelalchemy import Email, ExcelAlchemy, ExcelMeta, ImporterConfig, Number, String


class EmployeeImporter(BaseModel):
full_name: Annotated[str, Field(min_length=2), ExcelMeta(label='Full name', order=1, hint='Use the legal name')]
full_name: Annotated[
String,
Field(min_length=2),
ExcelMeta(label='Full name', order=1, hint='Use the legal name'),
]
age: Annotated[Number, Field(ge=18), ExcelMeta(label='Age', order=2)]
work_email: Annotated[
Email,
Expand Down
Loading
Loading