ExcelAlchemy 2.4.0__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- excelalchemy-3.0.0/PKG-INFO +166 -0
- excelalchemy-3.0.0/README-pypi.md +126 -0
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/pyproject.toml +48 -39
- excelalchemy-3.0.0/src/excelalchemy/README.md +59 -0
- excelalchemy-3.0.0/src/excelalchemy/__init__.py +99 -0
- excelalchemy-3.0.0/src/excelalchemy/adapters/__init__.py +19 -0
- {excelalchemy-2.4.0/src/excelalchemy/helper → excelalchemy-3.0.0/src/excelalchemy/adapters}/pydantic.py +226 -63
- excelalchemy-3.0.0/src/excelalchemy/adapters/pydantic_fields.py +70 -0
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/artifacts.py +1 -1
- excelalchemy-3.0.0/src/excelalchemy/codecs/__init__.py +29 -0
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/codecs/boolean.py +13 -12
- excelalchemy-3.0.0/src/excelalchemy/codecs/choice.py +277 -0
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/codecs/date.py +92 -17
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/codecs/date_range.py +96 -33
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/codecs/email.py +14 -9
- excelalchemy-2.4.0/src/excelalchemy/codecs/base.py → excelalchemy-3.0.0/src/excelalchemy/codecs/field_codec.py +27 -43
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/codecs/number.py +26 -16
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/codecs/number_range.py +54 -35
- excelalchemy-3.0.0/src/excelalchemy/codecs/phone_number.py +31 -0
- excelalchemy-2.4.0/src/excelalchemy/codecs/string.py → excelalchemy-3.0.0/src/excelalchemy/codecs/text.py +34 -12
- excelalchemy-3.0.0/src/excelalchemy/codecs/url.py +37 -0
- excelalchemy-3.0.0/src/excelalchemy/columns.py +156 -0
- excelalchemy-3.0.0/src/excelalchemy/config/__init__.py +29 -0
- excelalchemy-3.0.0/src/excelalchemy/config/exporter.py +86 -0
- excelalchemy-3.0.0/src/excelalchemy/config/importer.py +198 -0
- excelalchemy-3.0.0/src/excelalchemy/config/modes.py +19 -0
- excelalchemy-3.0.0/src/excelalchemy/config/options.py +71 -0
- {excelalchemy-2.4.0/src/excelalchemy/_primitives → excelalchemy-3.0.0/src/excelalchemy}/diagnostics.py +1 -1
- excelalchemy-2.4.0/src/excelalchemy/exceptions.py → excelalchemy-3.0.0/src/excelalchemy/errors.py +15 -10
- excelalchemy-3.0.0/src/excelalchemy/field_metadata/__init__.py +15 -0
- excelalchemy-3.0.0/src/excelalchemy/field_metadata/constraints.py +31 -0
- excelalchemy-3.0.0/src/excelalchemy/field_metadata/declaration.py +44 -0
- excelalchemy-3.0.0/src/excelalchemy/field_metadata/field.py +515 -0
- excelalchemy-3.0.0/src/excelalchemy/field_metadata/presentation.py +153 -0
- excelalchemy-3.0.0/src/excelalchemy/field_metadata/runtime.py +47 -0
- {excelalchemy-2.4.0/src/excelalchemy/i18n → excelalchemy-3.0.0/src/excelalchemy}/messages.py +194 -13
- excelalchemy-3.0.0/src/excelalchemy/policies.py +143 -0
- excelalchemy-3.0.0/src/excelalchemy/primitives/__init__.py +1 -0
- {excelalchemy-2.4.0/src/excelalchemy/_primitives → excelalchemy-3.0.0/src/excelalchemy/primitives}/constants.py +7 -6
- {excelalchemy-2.4.0/src/excelalchemy/_primitives → excelalchemy-3.0.0/src/excelalchemy/primitives}/identity.py +0 -4
- excelalchemy-3.0.0/src/excelalchemy/rendering/__init__.py +11 -0
- excelalchemy-2.4.0/src/excelalchemy/core/rendering.py → excelalchemy-3.0.0/src/excelalchemy/rendering/renderer.py +5 -5
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/rendering}/writer.py +208 -133
- excelalchemy-3.0.0/src/excelalchemy/results/__init__.py +51 -0
- excelalchemy-3.0.0/src/excelalchemy/results/import_result.py +88 -0
- excelalchemy-3.0.0/src/excelalchemy/results/issue_maps.py +456 -0
- excelalchemy-3.0.0/src/excelalchemy/results/lifecycle.py +106 -0
- excelalchemy-3.0.0/src/excelalchemy/results/preflight.py +129 -0
- excelalchemy-3.0.0/src/excelalchemy/results/remediation.py +353 -0
- excelalchemy-3.0.0/src/excelalchemy/runtime/__init__.py +22 -0
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/runtime}/executor.py +8 -9
- excelalchemy-2.4.0/src/excelalchemy/core/alchemy.py → excelalchemy-3.0.0/src/excelalchemy/runtime/facade.py +32 -60
- excelalchemy-2.4.0/src/excelalchemy/core/abstract.py → excelalchemy-3.0.0/src/excelalchemy/runtime/facade_protocol.py +4 -4
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/runtime}/import_session.py +67 -105
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/runtime}/preflight.py +7 -7
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/runtime}/rows.py +13 -10
- excelalchemy-3.0.0/src/excelalchemy/schema/__init__.py +5 -0
- excelalchemy-2.4.0/src/excelalchemy/core/schema.py → excelalchemy-3.0.0/src/excelalchemy/schema/layout.py +7 -7
- excelalchemy-3.0.0/src/excelalchemy/storage/__init__.py +5 -0
- excelalchemy-2.4.0/src/excelalchemy/core/storage_protocol.py → excelalchemy-3.0.0/src/excelalchemy/storage/base.py +6 -3
- excelalchemy-3.0.0/src/excelalchemy/storage/gateway.py +39 -0
- excelalchemy-2.4.0/src/excelalchemy/core/storage_minio.py → excelalchemy-3.0.0/src/excelalchemy/storage/minio.py +42 -42
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/util/converter.py +3 -3
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/util/file.py +4 -4
- excelalchemy-3.0.0/src/excelalchemy/worksheet/__init__.py +17 -0
- excelalchemy-3.0.0/src/excelalchemy/worksheet/header.py +27 -0
- excelalchemy-3.0.0/src/excelalchemy/worksheet/header_parser.py +81 -0
- excelalchemy-3.0.0/src/excelalchemy/worksheet/header_validator.py +76 -0
- excelalchemy-2.4.0/PKG-INFO +0 -260
- excelalchemy-2.4.0/README-pypi.md +0 -220
- excelalchemy-2.4.0/src/excelalchemy/README.md +0 -477
- excelalchemy-2.4.0/src/excelalchemy/__init__.py +0 -156
- excelalchemy-2.4.0/src/excelalchemy/_primitives/__init__.py +0 -1
- excelalchemy-2.4.0/src/excelalchemy/_primitives/deprecation.py +0 -22
- excelalchemy-2.4.0/src/excelalchemy/_primitives/header_models.py +0 -27
- excelalchemy-2.4.0/src/excelalchemy/codecs/__init__.py +0 -14
- excelalchemy-2.4.0/src/excelalchemy/codecs/money.py +0 -42
- excelalchemy-2.4.0/src/excelalchemy/codecs/multi_checkbox.py +0 -126
- excelalchemy-2.4.0/src/excelalchemy/codecs/organization.py +0 -110
- excelalchemy-2.4.0/src/excelalchemy/codecs/phone_number.py +0 -27
- excelalchemy-2.4.0/src/excelalchemy/codecs/radio.py +0 -112
- excelalchemy-2.4.0/src/excelalchemy/codecs/staff.py +0 -112
- excelalchemy-2.4.0/src/excelalchemy/codecs/tree.py +0 -89
- excelalchemy-2.4.0/src/excelalchemy/codecs/url.py +0 -33
- excelalchemy-2.4.0/src/excelalchemy/config.py +0 -409
- excelalchemy-2.4.0/src/excelalchemy/const.py +0 -3
- excelalchemy-2.4.0/src/excelalchemy/core/headers.py +0 -148
- excelalchemy-2.4.0/src/excelalchemy/core/storage.py +0 -54
- excelalchemy-2.4.0/src/excelalchemy/exc.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/header_models.py +0 -10
- excelalchemy-2.4.0/src/excelalchemy/helper/__init__.py +0 -0
- excelalchemy-2.4.0/src/excelalchemy/i18n/__init__.py +0 -23
- excelalchemy-2.4.0/src/excelalchemy/identity.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/metadata.py +0 -1040
- excelalchemy-2.4.0/src/excelalchemy/results.py +0 -880
- excelalchemy-2.4.0/src/excelalchemy/types/__init__.py +0 -15
- excelalchemy-2.4.0/src/excelalchemy/types/abstract.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/alchemy.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/field.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/header.py +0 -10
- excelalchemy-2.4.0/src/excelalchemy/types/identity.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/result.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/__init__.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/boolean.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/date.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/date_range.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/email.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/money.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/multi_checkbox.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/number.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/number_range.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/organization.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/phone_number.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/radio.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/staff.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/string.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/tree.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/types/value/url.py +0 -7
- excelalchemy-2.4.0/src/excelalchemy/util/__init__.py +0 -0
- excelalchemy-2.4.0/src/excelalchemy/util/convertor.py +0 -8
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/LICENSE +0 -0
- {excelalchemy-2.4.0/src/excelalchemy/_primitives → excelalchemy-3.0.0/src/excelalchemy/primitives}/payloads.py +0 -0
- {excelalchemy-2.4.0 → excelalchemy-3.0.0}/src/excelalchemy/py.typed +0 -0
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/util}/__init__.py +0 -0
- {excelalchemy-2.4.0/src/excelalchemy/core → excelalchemy-3.0.0/src/excelalchemy/worksheet}/table.py +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ExcelAlchemy
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Schema-driven Python library for typed Excel import/export workflows with Pydantic and locale-aware workbooks.
|
|
5
|
+
Keywords: excel,openpyxl,pydantic,minio,schema
|
|
6
|
+
Author: Ray
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pydantic[email] >=2.12, <3
|
|
23
|
+
Requires-Dist: openpyxl >=3.1.5, <4
|
|
24
|
+
Requires-Dist: pendulum >=3.2.0, <4
|
|
25
|
+
Requires-Dist: minio >=7.2.20, <8 ; extra == "development"
|
|
26
|
+
Requires-Dist: pre-commit ; extra == "development"
|
|
27
|
+
Requires-Dist: pyright==1.1.408 ; extra == "development"
|
|
28
|
+
Requires-Dist: pytest ; extra == "development"
|
|
29
|
+
Requires-Dist: coverage ; extra == "development"
|
|
30
|
+
Requires-Dist: pytest-cov ; extra == "development"
|
|
31
|
+
Requires-Dist: ruff ; extra == "development"
|
|
32
|
+
Requires-Dist: minio >=7.2.20, <8 ; extra == "minio"
|
|
33
|
+
Project-URL: Documentation, https://github.com/RayCarterLab/ExcelAlchemy#readme
|
|
34
|
+
Project-URL: Home, https://github.com/RayCarterLab/ExcelAlchemy
|
|
35
|
+
Project-URL: Issues, https://github.com/RayCarterLab/ExcelAlchemy/issues
|
|
36
|
+
Project-URL: Repository, https://github.com/RayCarterLab/ExcelAlchemy
|
|
37
|
+
Provides-Extra: development
|
|
38
|
+
Provides-Extra: minio
|
|
39
|
+
|
|
40
|
+
# ExcelAlchemy
|
|
41
|
+
|
|
42
|
+
Schema-driven Python library for typed Excel import/export workflows with
|
|
43
|
+
Pydantic and locale-aware workbooks.
|
|
44
|
+
|
|
45
|
+
ExcelAlchemy turns Pydantic models into workbook contracts:
|
|
46
|
+
|
|
47
|
+
- generate Excel templates from code
|
|
48
|
+
- validate uploaded workbooks
|
|
49
|
+
- map failures back to rows and cells
|
|
50
|
+
- return result workbooks and API-friendly error payloads
|
|
51
|
+
- keep workbook IO pluggable through `ExcelStorage`
|
|
52
|
+
|
|
53
|
+
The current stable release is ExcelAlchemy 3.0. It uses ordinary Python
|
|
54
|
+
annotations plus explicit `ExcelColumn(...)` metadata. Old 2.x field factories,
|
|
55
|
+
compatibility imports, legacy config fields, and facade aliases are not current
|
|
56
|
+
API.
|
|
57
|
+
|
|
58
|
+
[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) · [Examples](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md) · [Public API](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/public-api.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/migrations.md)
|
|
59
|
+
|
|
60
|
+
## Screenshots
|
|
61
|
+
|
|
62
|
+
### Template
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+
### Import Result
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install ExcelAlchemy
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Optional Minio-compatible storage support:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install "ExcelAlchemy[minio]"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Quick Example
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from typing import Annotated
|
|
86
|
+
|
|
87
|
+
from pydantic import BaseModel, Field
|
|
88
|
+
|
|
89
|
+
from excelalchemy import EmailCodec, ExcelAlchemy, ExcelColumn, ImporterConfig
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class EmployeeImport(BaseModel):
|
|
93
|
+
name: Annotated[str, ExcelColumn(label='Name', order=1)]
|
|
94
|
+
email: Annotated[
|
|
95
|
+
str,
|
|
96
|
+
Field(min_length=8),
|
|
97
|
+
ExcelColumn(
|
|
98
|
+
label='Email',
|
|
99
|
+
codec=EmailCodec(),
|
|
100
|
+
order=2,
|
|
101
|
+
hint='Use your work email',
|
|
102
|
+
example_value='alice@company.com',
|
|
103
|
+
),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
alchemy = ExcelAlchemy(ImporterConfig(EmployeeImport, locale='en'))
|
|
108
|
+
template = alchemy.download_template_artifact(filename='employees-template.xlsx')
|
|
109
|
+
|
|
110
|
+
excel_bytes = template.as_bytes()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Import Workflow
|
|
114
|
+
|
|
115
|
+
The shortest import path is:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
template -> preflight -> import -> remediation -> delivery
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Minimal backend sketch:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from excelalchemy.results import ImportLifecycleEvent, build_frontend_remediation_payload
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
events: list[ImportLifecycleEvent] = []
|
|
128
|
+
|
|
129
|
+
preflight = alchemy.preflight_import('employees.xlsx')
|
|
130
|
+
if preflight.is_valid:
|
|
131
|
+
result = await alchemy.import_data(
|
|
132
|
+
'employees.xlsx',
|
|
133
|
+
'employees-result.xlsx',
|
|
134
|
+
on_event=events.append,
|
|
135
|
+
)
|
|
136
|
+
payload = {
|
|
137
|
+
'result': result.to_api_payload(),
|
|
138
|
+
'cell_errors': alchemy.cell_error_map.to_api_payload(),
|
|
139
|
+
'row_errors': alchemy.row_error_map.to_api_payload(),
|
|
140
|
+
'remediation': build_frontend_remediation_payload(
|
|
141
|
+
result=result,
|
|
142
|
+
cell_error_map=alchemy.cell_error_map,
|
|
143
|
+
row_error_map=alchemy.row_error_map,
|
|
144
|
+
),
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Why ExcelAlchemy
|
|
149
|
+
|
|
150
|
+
- Pydantic v2-based schema extraction and validation
|
|
151
|
+
- `Annotated[..., ExcelColumn(...)]` declaration style
|
|
152
|
+
- workbook comments and result workbooks in `zh-CN`, `en`, or `ja`
|
|
153
|
+
- pluggable storage instead of a hard-coded backend
|
|
154
|
+
- `openpyxl`-based runtime path without pandas
|
|
155
|
+
- contract tests, Ruff, and Pyright in the development workflow
|
|
156
|
+
|
|
157
|
+
## Learn More
|
|
158
|
+
|
|
159
|
+
- [Full project README](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/README.md)
|
|
160
|
+
- [Getting started](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/getting-started.md)
|
|
161
|
+
- [Examples showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md)
|
|
162
|
+
- [Result objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md)
|
|
163
|
+
- [API response cookbook](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/api-response-cookbook.md)
|
|
164
|
+
- [Platform architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/platform-architecture.md)
|
|
165
|
+
- [Migration notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/migrations.md)
|
|
166
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# ExcelAlchemy
|
|
2
|
+
|
|
3
|
+
Schema-driven Python library for typed Excel import/export workflows with
|
|
4
|
+
Pydantic and locale-aware workbooks.
|
|
5
|
+
|
|
6
|
+
ExcelAlchemy turns Pydantic models into workbook contracts:
|
|
7
|
+
|
|
8
|
+
- generate Excel templates from code
|
|
9
|
+
- validate uploaded workbooks
|
|
10
|
+
- map failures back to rows and cells
|
|
11
|
+
- return result workbooks and API-friendly error payloads
|
|
12
|
+
- keep workbook IO pluggable through `ExcelStorage`
|
|
13
|
+
|
|
14
|
+
The current stable release is ExcelAlchemy 3.0. It uses ordinary Python
|
|
15
|
+
annotations plus explicit `ExcelColumn(...)` metadata. Old 2.x field factories,
|
|
16
|
+
compatibility imports, legacy config fields, and facade aliases are not current
|
|
17
|
+
API.
|
|
18
|
+
|
|
19
|
+
[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) · [Examples](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/examples/README.md) · [Public API](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/public-api.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/migrations.md)
|
|
20
|
+
|
|
21
|
+
## Screenshots
|
|
22
|
+
|
|
23
|
+
### Template
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
### Import Result
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install ExcelAlchemy
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Optional Minio-compatible storage support:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install "ExcelAlchemy[minio]"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Example
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from typing import Annotated
|
|
47
|
+
|
|
48
|
+
from pydantic import BaseModel, Field
|
|
49
|
+
|
|
50
|
+
from excelalchemy import EmailCodec, ExcelAlchemy, ExcelColumn, ImporterConfig
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class EmployeeImport(BaseModel):
|
|
54
|
+
name: Annotated[str, ExcelColumn(label='Name', order=1)]
|
|
55
|
+
email: Annotated[
|
|
56
|
+
str,
|
|
57
|
+
Field(min_length=8),
|
|
58
|
+
ExcelColumn(
|
|
59
|
+
label='Email',
|
|
60
|
+
codec=EmailCodec(),
|
|
61
|
+
order=2,
|
|
62
|
+
hint='Use your work email',
|
|
63
|
+
example_value='alice@company.com',
|
|
64
|
+
),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
alchemy = ExcelAlchemy(ImporterConfig(EmployeeImport, locale='en'))
|
|
69
|
+
template = alchemy.download_template_artifact(filename='employees-template.xlsx')
|
|
70
|
+
|
|
71
|
+
excel_bytes = template.as_bytes()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Import Workflow
|
|
75
|
+
|
|
76
|
+
The shortest import path is:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
template -> preflight -> import -> remediation -> delivery
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Minimal backend sketch:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from excelalchemy.results import ImportLifecycleEvent, build_frontend_remediation_payload
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
events: list[ImportLifecycleEvent] = []
|
|
89
|
+
|
|
90
|
+
preflight = alchemy.preflight_import('employees.xlsx')
|
|
91
|
+
if preflight.is_valid:
|
|
92
|
+
result = await alchemy.import_data(
|
|
93
|
+
'employees.xlsx',
|
|
94
|
+
'employees-result.xlsx',
|
|
95
|
+
on_event=events.append,
|
|
96
|
+
)
|
|
97
|
+
payload = {
|
|
98
|
+
'result': result.to_api_payload(),
|
|
99
|
+
'cell_errors': alchemy.cell_error_map.to_api_payload(),
|
|
100
|
+
'row_errors': alchemy.row_error_map.to_api_payload(),
|
|
101
|
+
'remediation': build_frontend_remediation_payload(
|
|
102
|
+
result=result,
|
|
103
|
+
cell_error_map=alchemy.cell_error_map,
|
|
104
|
+
row_error_map=alchemy.row_error_map,
|
|
105
|
+
),
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Why ExcelAlchemy
|
|
110
|
+
|
|
111
|
+
- Pydantic v2-based schema extraction and validation
|
|
112
|
+
- `Annotated[..., ExcelColumn(...)]` declaration style
|
|
113
|
+
- workbook comments and result workbooks in `zh-CN`, `en`, or `ja`
|
|
114
|
+
- pluggable storage instead of a hard-coded backend
|
|
115
|
+
- `openpyxl`-based runtime path without pandas
|
|
116
|
+
- contract tests, Ruff, and Pyright in the development workflow
|
|
117
|
+
|
|
118
|
+
## Learn More
|
|
119
|
+
|
|
120
|
+
- [Full project README](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/README.md)
|
|
121
|
+
- [Getting started](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/getting-started.md)
|
|
122
|
+
- [Examples showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md)
|
|
123
|
+
- [Result objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md)
|
|
124
|
+
- [API response cookbook](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/api-response-cookbook.md)
|
|
125
|
+
- [Platform architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/platform-architecture.md)
|
|
126
|
+
- [Migration notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/migrations.md)
|
|
@@ -63,7 +63,6 @@ exclude = [
|
|
|
63
63
|
'**/.mypy_cache',
|
|
64
64
|
'**/__pycache__',
|
|
65
65
|
'**/.pytest_cache',
|
|
66
|
-
'src/excelalchemy/types/field.py',
|
|
67
66
|
]
|
|
68
67
|
enableTypeIgnoreComments = false
|
|
69
68
|
reportAbstractUsage = false
|
|
@@ -72,48 +71,64 @@ reportCallIssue = false
|
|
|
72
71
|
reportPrivateImportUsage = false
|
|
73
72
|
reportRedeclaration = false
|
|
74
73
|
strict = [
|
|
75
|
-
'src/excelalchemy/
|
|
76
|
-
'src/excelalchemy/_primitives/deprecation.py',
|
|
77
|
-
'src/excelalchemy/_primitives/header_models.py',
|
|
78
|
-
'src/excelalchemy/_primitives/identity.py',
|
|
79
|
-
'src/excelalchemy/_primitives/payloads.py',
|
|
74
|
+
'src/excelalchemy/adapters/pydantic.py',
|
|
80
75
|
'src/excelalchemy/artifacts.py',
|
|
81
|
-
'src/excelalchemy/codecs/base.py',
|
|
82
76
|
'src/excelalchemy/codecs/boolean.py',
|
|
77
|
+
'src/excelalchemy/codecs/choice.py',
|
|
83
78
|
'src/excelalchemy/codecs/date.py',
|
|
84
79
|
'src/excelalchemy/codecs/date_range.py',
|
|
85
80
|
'src/excelalchemy/codecs/email.py',
|
|
86
|
-
'src/excelalchemy/codecs/
|
|
87
|
-
'src/excelalchemy/codecs/multi_checkbox.py',
|
|
81
|
+
'src/excelalchemy/codecs/field_codec.py',
|
|
88
82
|
'src/excelalchemy/codecs/number.py',
|
|
89
83
|
'src/excelalchemy/codecs/number_range.py',
|
|
90
|
-
'src/excelalchemy/codecs/organization.py',
|
|
91
84
|
'src/excelalchemy/codecs/phone_number.py',
|
|
92
|
-
'src/excelalchemy/codecs/
|
|
93
|
-
'src/excelalchemy/codecs/staff.py',
|
|
94
|
-
'src/excelalchemy/codecs/string.py',
|
|
95
|
-
'src/excelalchemy/codecs/tree.py',
|
|
85
|
+
'src/excelalchemy/codecs/text.py',
|
|
96
86
|
'src/excelalchemy/codecs/url.py',
|
|
97
|
-
'src/excelalchemy/
|
|
98
|
-
'src/excelalchemy/
|
|
99
|
-
'src/excelalchemy/
|
|
100
|
-
'src/excelalchemy/
|
|
101
|
-
'src/excelalchemy/
|
|
102
|
-
'src/excelalchemy/
|
|
103
|
-
'src/excelalchemy/
|
|
104
|
-
'src/excelalchemy/
|
|
105
|
-
'src/excelalchemy/
|
|
106
|
-
'src/excelalchemy/
|
|
107
|
-
'src/excelalchemy/
|
|
108
|
-
'src/excelalchemy/
|
|
109
|
-
'src/excelalchemy/
|
|
110
|
-
'src/excelalchemy/
|
|
111
|
-
'src/excelalchemy/
|
|
112
|
-
'src/excelalchemy/
|
|
113
|
-
'src/excelalchemy/
|
|
114
|
-
'src/excelalchemy/results.py',
|
|
115
|
-
'src/excelalchemy/
|
|
87
|
+
'src/excelalchemy/columns.py',
|
|
88
|
+
'src/excelalchemy/config/__init__.py',
|
|
89
|
+
'src/excelalchemy/config/exporter.py',
|
|
90
|
+
'src/excelalchemy/config/importer.py',
|
|
91
|
+
'src/excelalchemy/config/modes.py',
|
|
92
|
+
'src/excelalchemy/config/options.py',
|
|
93
|
+
'src/excelalchemy/diagnostics.py',
|
|
94
|
+
'src/excelalchemy/errors.py',
|
|
95
|
+
'src/excelalchemy/messages.py',
|
|
96
|
+
'src/excelalchemy/policies.py',
|
|
97
|
+
'src/excelalchemy/primitives/constants.py',
|
|
98
|
+
'src/excelalchemy/primitives/identity.py',
|
|
99
|
+
'src/excelalchemy/primitives/payloads.py',
|
|
100
|
+
'src/excelalchemy/rendering/renderer.py',
|
|
101
|
+
'src/excelalchemy/rendering/writer.py',
|
|
102
|
+
'src/excelalchemy/results/__init__.py',
|
|
103
|
+
'src/excelalchemy/results/import_result.py',
|
|
104
|
+
'src/excelalchemy/results/issue_maps.py',
|
|
105
|
+
'src/excelalchemy/results/lifecycle.py',
|
|
106
|
+
'src/excelalchemy/results/preflight.py',
|
|
107
|
+
'src/excelalchemy/results/remediation.py',
|
|
108
|
+
'src/excelalchemy/runtime/executor.py',
|
|
109
|
+
'src/excelalchemy/runtime/facade.py',
|
|
110
|
+
'src/excelalchemy/runtime/facade_protocol.py',
|
|
111
|
+
'src/excelalchemy/runtime/import_session.py',
|
|
112
|
+
'src/excelalchemy/runtime/preflight.py',
|
|
113
|
+
'src/excelalchemy/runtime/rows.py',
|
|
114
|
+
'src/excelalchemy/schema/layout.py',
|
|
115
|
+
'src/excelalchemy/storage/__init__.py',
|
|
116
|
+
'src/excelalchemy/storage/base.py',
|
|
117
|
+
'src/excelalchemy/storage/gateway.py',
|
|
118
|
+
'src/excelalchemy/storage/minio.py',
|
|
116
119
|
'src/excelalchemy/util/file.py',
|
|
120
|
+
'src/excelalchemy/field_metadata/__init__.py',
|
|
121
|
+
'src/excelalchemy/field_metadata/constraints.py',
|
|
122
|
+
'src/excelalchemy/field_metadata/declaration.py',
|
|
123
|
+
'src/excelalchemy/field_metadata/field.py',
|
|
124
|
+
'src/excelalchemy/field_metadata/presentation.py',
|
|
125
|
+
'src/excelalchemy/field_metadata/runtime.py',
|
|
126
|
+
'src/excelalchemy/worksheet/__init__.py',
|
|
127
|
+
'src/excelalchemy/worksheet/header.py',
|
|
128
|
+
'src/excelalchemy/worksheet/header_parser.py',
|
|
129
|
+
'src/excelalchemy/worksheet/header_validator.py',
|
|
130
|
+
'src/excelalchemy/worksheet/table.py',
|
|
131
|
+
'src/excelalchemy/adapters/pydantic_fields.py',
|
|
117
132
|
]
|
|
118
133
|
typeCheckingMode = 'basic'
|
|
119
134
|
|
|
@@ -129,12 +144,6 @@ ignore = ['E501', 'RUF001', 'RUF002', 'RUF003']
|
|
|
129
144
|
|
|
130
145
|
[tool.ruff.lint.per-file-ignores]
|
|
131
146
|
'**/__init__.py' = ['F401']
|
|
132
|
-
'src/excelalchemy/const.py' = ['RUF100']
|
|
133
|
-
'src/excelalchemy/exc.py' = ['E402', 'RUF100']
|
|
134
|
-
'src/excelalchemy/header_models.py' = ['E402', 'RUF100']
|
|
135
|
-
'src/excelalchemy/identity.py' = ['E402', 'RUF100']
|
|
136
|
-
'src/excelalchemy/types/*.py' = ['E402', 'RUF100']
|
|
137
|
-
'src/excelalchemy/types/**/*.py' = ['E402', 'RUF100']
|
|
138
147
|
|
|
139
148
|
[tool.ruff.format]
|
|
140
149
|
quote-style = 'preserve'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ExcelAlchemy Package Map
|
|
2
|
+
|
|
3
|
+
This package is being shaped for ExcelAlchemy 3.0. Use concrete responsibility
|
|
4
|
+
modules when editing code; do not add a generic `_internal` package and do not
|
|
5
|
+
restore 2.x compatibility shims.
|
|
6
|
+
|
|
7
|
+
## Public Entry Points
|
|
8
|
+
|
|
9
|
+
- `excelalchemy`: package root exports the common public API.
|
|
10
|
+
- `config/`: `ImporterConfig`, `ExporterConfig`, `ImportMode`, and normalized
|
|
11
|
+
storage options.
|
|
12
|
+
- `columns.py`: `ExcelColumn(...)` and immutable column declarations for
|
|
13
|
+
`typing.Annotated`.
|
|
14
|
+
- `codecs/`: built-in workbook codecs and codec extension base classes.
|
|
15
|
+
- `field_metadata/`: resolved Excel-facing field metadata used by the runtime.
|
|
16
|
+
- `results/`: import results, issue maps, preflight results, lifecycle events,
|
|
17
|
+
and API payload helpers.
|
|
18
|
+
- `errors.py`: public exception exports.
|
|
19
|
+
- `storage/`: `ExcelStorage` protocol.
|
|
20
|
+
- `storage/gateway.py`: configured storage resolver and missing-storage
|
|
21
|
+
fallback.
|
|
22
|
+
- `artifacts.py`: binary/data-URL workbook transport wrapper.
|
|
23
|
+
|
|
24
|
+
## Implementation Areas
|
|
25
|
+
|
|
26
|
+
- `adapters/`: framework boundaries, currently Pydantic model extraction and
|
|
27
|
+
validation error normalization.
|
|
28
|
+
- `schema/`: Excel schema layout extraction and flattened column planning.
|
|
29
|
+
- `worksheet/`: worksheet tables, header records, header parsing, and header validation.
|
|
30
|
+
- `runtime/`: facade, import sessions, row aggregation, execution, and preflight.
|
|
31
|
+
- `rendering/`: workbook rendering and low-level writing.
|
|
32
|
+
- `messages.py`: runtime and workbook-facing message lookup.
|
|
33
|
+
- `policies.py`: named deterministic policies for layout, payload, result,
|
|
34
|
+
event, and missing-value behavior.
|
|
35
|
+
- `diagnostics.py`: logger names and structured diagnostics helpers.
|
|
36
|
+
|
|
37
|
+
## Removed 2.x Surfaces
|
|
38
|
+
|
|
39
|
+
These paths and aliases are intentionally absent in 3.0:
|
|
40
|
+
|
|
41
|
+
- `excelalchemy.core.*`
|
|
42
|
+
- `excelalchemy.helper.*`
|
|
43
|
+
- `excelalchemy.i18n.*`
|
|
44
|
+
- `excelalchemy._primitives.*`
|
|
45
|
+
- `excelalchemy.const`
|
|
46
|
+
- `excelalchemy.exc`
|
|
47
|
+
- `excelalchemy.identity`
|
|
48
|
+
- `excelalchemy.header_models`
|
|
49
|
+
- `excelalchemy.metadata`
|
|
50
|
+
- `excelalchemy.types.*`
|
|
51
|
+
- `excelalchemy.util.convertor`
|
|
52
|
+
- facade aliases `df`, `header_df`, `cell_errors`, `row_errors`
|
|
53
|
+
- config fields `minio`, `bucket_name`, `url_expires`
|
|
54
|
+
- codec aliases `comment`, `serialize`, `deserialize`, `__validate__`,
|
|
55
|
+
`model_items`
|
|
56
|
+
- metadata alias `value_type`
|
|
57
|
+
|
|
58
|
+
Use `storage=...` with an `ExcelStorage` implementation for any backend,
|
|
59
|
+
including `MinioStorageGateway`.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Public ExcelAlchemy API."""
|
|
2
|
+
|
|
3
|
+
__version__ = '3.0.0'
|
|
4
|
+
from excelalchemy.artifacts import ExcelArtifact
|
|
5
|
+
from excelalchemy.codecs.boolean import BooleanCodec
|
|
6
|
+
from excelalchemy.codecs.choice import MultiChoiceCodec, SingleChoiceCodec
|
|
7
|
+
from excelalchemy.codecs.date import DateCodec
|
|
8
|
+
from excelalchemy.codecs.date_range import DateRangeCodec
|
|
9
|
+
from excelalchemy.codecs.email import EmailCodec
|
|
10
|
+
from excelalchemy.codecs.field_codec import CompositeExcelFieldCodec, ExcelFieldCodec, ExcelFieldCodecSpec
|
|
11
|
+
from excelalchemy.codecs.number import NumberCodec
|
|
12
|
+
from excelalchemy.codecs.number_range import NumberRangeCodec
|
|
13
|
+
from excelalchemy.codecs.phone_number import PhoneNumberCodec
|
|
14
|
+
from excelalchemy.codecs.text import TextCodec
|
|
15
|
+
from excelalchemy.codecs.url import UrlCodec
|
|
16
|
+
from excelalchemy.columns import ExcelColumn
|
|
17
|
+
from excelalchemy.config import ExportConfig, ExporterConfig, ImportConfig, ImporterConfig, ImportMode
|
|
18
|
+
from excelalchemy.errors import (
|
|
19
|
+
ConfigError,
|
|
20
|
+
ExcelCellError,
|
|
21
|
+
ExcelRowError,
|
|
22
|
+
ProgrammaticError,
|
|
23
|
+
WorksheetNotFoundError,
|
|
24
|
+
)
|
|
25
|
+
from excelalchemy.primitives.constants import CharacterSet, DataRangeOption, DateFormat, Option
|
|
26
|
+
from excelalchemy.primitives.identity import (
|
|
27
|
+
ColumnIndex,
|
|
28
|
+
DataUrlStr,
|
|
29
|
+
Key,
|
|
30
|
+
Label,
|
|
31
|
+
OptionId,
|
|
32
|
+
RowIndex,
|
|
33
|
+
UniqueKey,
|
|
34
|
+
UniqueLabel,
|
|
35
|
+
UrlStr,
|
|
36
|
+
)
|
|
37
|
+
from excelalchemy.results import (
|
|
38
|
+
CellErrorMap,
|
|
39
|
+
ImportPreflightResult,
|
|
40
|
+
ImportPreflightStatus,
|
|
41
|
+
ImportResult,
|
|
42
|
+
RowIssueMap,
|
|
43
|
+
ValidateHeaderResult,
|
|
44
|
+
ValidateResult,
|
|
45
|
+
ValidateRowResult,
|
|
46
|
+
)
|
|
47
|
+
from excelalchemy.runtime.facade import ExcelAlchemy
|
|
48
|
+
from excelalchemy.storage import ExcelStorage
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
'BooleanCodec',
|
|
52
|
+
'CellErrorMap',
|
|
53
|
+
'ColumnIndex',
|
|
54
|
+
'CompositeExcelFieldCodec',
|
|
55
|
+
'ConfigError',
|
|
56
|
+
'DataRangeOption',
|
|
57
|
+
'DataUrlStr',
|
|
58
|
+
'DateCodec',
|
|
59
|
+
'DateFormat',
|
|
60
|
+
'DateRangeCodec',
|
|
61
|
+
'EmailCodec',
|
|
62
|
+
'ExcelAlchemy',
|
|
63
|
+
'ExcelArtifact',
|
|
64
|
+
'ExcelCellError',
|
|
65
|
+
'ExcelColumn',
|
|
66
|
+
'ExcelFieldCodec',
|
|
67
|
+
'ExcelFieldCodecSpec',
|
|
68
|
+
'ExcelRowError',
|
|
69
|
+
'ExcelStorage',
|
|
70
|
+
'ExportConfig',
|
|
71
|
+
'ExporterConfig',
|
|
72
|
+
'ImportConfig',
|
|
73
|
+
'ImportMode',
|
|
74
|
+
'ImportPreflightResult',
|
|
75
|
+
'ImportPreflightStatus',
|
|
76
|
+
'ImportResult',
|
|
77
|
+
'ImporterConfig',
|
|
78
|
+
'Key',
|
|
79
|
+
'Label',
|
|
80
|
+
'MultiChoiceCodec',
|
|
81
|
+
'NumberCodec',
|
|
82
|
+
'NumberRangeCodec',
|
|
83
|
+
'Option',
|
|
84
|
+
'OptionId',
|
|
85
|
+
'PhoneNumberCodec',
|
|
86
|
+
'ProgrammaticError',
|
|
87
|
+
'RowIndex',
|
|
88
|
+
'RowIssueMap',
|
|
89
|
+
'SingleChoiceCodec',
|
|
90
|
+
'TextCodec',
|
|
91
|
+
'UniqueKey',
|
|
92
|
+
'UniqueLabel',
|
|
93
|
+
'UrlCodec',
|
|
94
|
+
'UrlStr',
|
|
95
|
+
'ValidateHeaderResult',
|
|
96
|
+
'ValidateResult',
|
|
97
|
+
'ValidateRowResult',
|
|
98
|
+
'WorksheetNotFoundError',
|
|
99
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Framework adapter boundaries."""
|
|
2
|
+
|
|
3
|
+
from excelalchemy.adapters.pydantic import (
|
|
4
|
+
PydanticFieldAdapter,
|
|
5
|
+
PydanticModelAdapter,
|
|
6
|
+
ValidationMessageNormalizationPolicy,
|
|
7
|
+
extract_pydantic_model,
|
|
8
|
+
get_model_field_names,
|
|
9
|
+
instantiate_pydantic_model,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'PydanticFieldAdapter',
|
|
14
|
+
'PydanticModelAdapter',
|
|
15
|
+
'ValidationMessageNormalizationPolicy',
|
|
16
|
+
'extract_pydantic_model',
|
|
17
|
+
'get_model_field_names',
|
|
18
|
+
'instantiate_pydantic_model',
|
|
19
|
+
]
|