Skip to content

Commit 6990613

Browse files
authored
Merge pull request #62 from RayCarterLab/v2.2
release: prepare ExcelAlchemy 2.2.3
2 parents 33e2aa3 + 9533324 commit 6990613

5 files changed

Lines changed: 141 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,37 @@ 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.2.2] - Unreleased
7+
## [2.2.3] - Unreleased
8+
9+
This release continues the stable 2.x line with a focused validation fix in
10+
the Pydantic adapter layer.
11+
12+
### Fixed
13+
14+
- Restored explicit `ProgrammaticError` handling for unsupported
15+
`Annotated[..., Field(...), ExcelMeta(...)]` declarations that use native
16+
Python types instead of `ExcelFieldCodec` subclasses
17+
- Tightened codec resolution in the Pydantic adapter so unsupported
18+
declarations fail at the codec resolution boundary instead of being treated
19+
as valid runtime metadata
20+
- Added a regression test for unsupported annotated declarations to prevent
21+
native Python annotations from slipping through the workbook schema path
22+
23+
### Compatibility Notes
24+
25+
- No public import or export workflow API was removed in this release
26+
- Valid `ExcelFieldCodec` and `CompositeExcelFieldCodec` declarations continue
27+
to work unchanged
28+
- Unsupported native annotations with `ExcelMeta(...)` now fail early with the
29+
intended `ProgrammaticError`
30+
31+
### Release Summary
32+
33+
- unsupported annotated declarations now fail with the intended error again
34+
- codec resolution is stricter and easier to reason about
35+
- the validation fix is protected by an explicit integration regression test
36+
37+
## [2.2.2] - 2026-04-03
838

939
This release continues the stable 2.x line with stronger developer ergonomics,
1040
clearer public API guidance, and better release-time smoke coverage.

docs/releases/2.2.3.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# 2.2.3 Release Checklist
2+
3+
This checklist is intended for the `2.2.3` release on top of the stable 2.x
4+
line.
5+
6+
## Purpose
7+
8+
- publish the next stable 2.x bugfix release of ExcelAlchemy
9+
- present `2.2.3` as a focused validation-correctness release
10+
- restore the intended failure mode for unsupported annotated declarations
11+
- ship regression coverage for the declaration guard in the Pydantic adapter
12+
13+
## Release Positioning
14+
15+
`2.2.3` should be presented as a small, correctness-focused release:
16+
17+
- the public import and export workflow API stays stable
18+
- valid codec-based declarations continue to work unchanged
19+
- unsupported native annotations with `ExcelMeta(...)` now fail early with the
20+
intended `ProgrammaticError`
21+
- the validation path is safer and easier to reason about
22+
23+
## Before Tagging
24+
25+
1. Confirm the intended version in `src/excelalchemy/__init__.py`.
26+
2. Review the `2.2.3` section in `CHANGELOG.md`.
27+
3. Confirm the Pydantic adapter fix in
28+
`src/excelalchemy/helper/pydantic.py`.
29+
4. Confirm the regression test in
30+
`tests/integration/test_excelalchemy_workflows.py`.
31+
32+
## Local Verification
33+
34+
Run these commands from the repository root:
35+
36+
```bash
37+
uv sync --extra development
38+
uv run ruff check src/excelalchemy/helper/pydantic.py \
39+
tests/integration/test_excelalchemy_workflows.py
40+
uv run pyright
41+
uv run pytest tests/integration/test_excelalchemy_workflows.py -q
42+
rm -rf dist
43+
uv build
44+
uvx twine check dist/*
45+
```
46+
47+
## GitHub Release Steps
48+
49+
1. Push the release commit to the default branch.
50+
2. In GitHub Releases, draft a new release.
51+
3. Create a new tag: `v2.2.3`.
52+
4. Use the `2.2.3` section from `CHANGELOG.md` as the release notes base.
53+
5. Publish the release and monitor the `Upload Python Package` workflow.
54+
55+
## Release Focus
56+
57+
When reviewing the final release notes, make sure they communicate these three
58+
themes clearly:
59+
60+
- unsupported annotated declarations now fail with the intended error again
61+
- codec resolution in the Pydantic adapter is stricter and more explicit
62+
- the fix is protected by a regression test
63+
64+
## Recommended Release Messaging
65+
66+
Prefer wording that emphasizes stability and correctness:
67+
68+
- "continues the stable 2.x line"
69+
- "restores the intended ProgrammaticError path"
70+
- "tightens codec resolution in the Pydantic adapter"
71+
- "adds regression coverage for unsupported annotated declarations"
72+
73+
## Done When
74+
75+
- the tag `v2.2.3` is published
76+
- the GitHub Release notes clearly describe the validation fix
77+
- the regression test passes in CI
78+
- the published package version matches the release tag

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.2'
3+
__version__ = '2.2.3'
44
from excelalchemy._primitives.constants import CharacterSet, DataRangeOption, DateFormat, Option
55
from excelalchemy._primitives.deprecation import ExcelAlchemyDeprecationWarning
66
from excelalchemy._primitives.identity import (

src/excelalchemy/helper/pydantic.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
from excelalchemy.metadata import FieldMetaInfo, extract_declared_field_metadata
1616

1717

18+
def _resolve_excel_codec_type(annotation: object) -> type[ExcelFieldCodec]:
19+
if isinstance(annotation, type):
20+
if issubclass(annotation, ExcelFieldCodec):
21+
return annotation
22+
unsupported = repr(cast(object, annotation))
23+
else:
24+
unsupported = str(annotation)
25+
raise ProgrammaticError(msg(MessageKey.VALUE_TYPE_DECLARATION_UNSUPPORTED, value_type=unsupported))
26+
27+
1828
@dataclass(frozen=True)
1929
class PydanticFieldAdapter:
2030
"""Provide a stable view over one Pydantic field."""
@@ -34,9 +44,9 @@ def excel_codec(self) -> type[ExcelFieldCodec]:
3444
args = [arg for arg in get_args(annotation) if arg is not type(None)]
3545
if len(args) != 1:
3646
raise ProgrammaticError(msg(MessageKey.UNSUPPORTED_FIELD_TYPE_DECLARATION, annotation=annotation))
37-
return cast(type[ExcelFieldCodec], args[0])
47+
return _resolve_excel_codec_type(args[0])
3848

39-
return cast(type[ExcelFieldCodec], annotation)
49+
return _resolve_excel_codec_type(annotation)
4050

4151
@property
4252
def value_type(self) -> type[ExcelFieldCodec]:

tests/integration/test_excelalchemy_workflows.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,25 @@ class AnnotatedImporter(BaseModel):
493493
template.startswith('data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,')
494494
)
495495

496+
async def test_annotated_native_python_type_with_excelmeta_raises_programmatic_error(self):
497+
class UnsupportedAnnotatedImporter(BaseModel):
498+
name: Annotated[str, Field(min_length=3), ExcelMeta(label='Name', order=1)]
499+
500+
config = ImporterConfig(
501+
UnsupportedAnnotatedImporter,
502+
creator=self.creator,
503+
minio=cast(Minio, self.minio),
504+
)
505+
506+
with self.assertRaises(ProgrammaticError) as cm:
507+
ExcelAlchemy(config)
508+
509+
self.assertEqual(
510+
str(cm.exception),
511+
'Field definitions must use an ExcelFieldCodec or CompositeExcelFieldCodec subclass; '
512+
"<class 'str'> is not supported",
513+
)
514+
496515
async def test_passing_non_config_object_raises_config_error(self):
497516
class NotImporterConfigModel(BaseModel):
498517
name: str = FieldMeta(label='姓名')

0 commit comments

Comments
 (0)