ExcelAlchemy 2.2.6__tar.gz → 2.2.7__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-2.2.6 → excelalchemy-2.2.7}/PKG-INFO +3 -3
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/README-pypi.md +2 -2
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/__init__.py +7 -1
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/base.py +46 -6
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/boolean.py +15 -8
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/date.py +2 -2
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/date_range.py +7 -3
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/multi_checkbox.py +7 -7
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/number.py +2 -2
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/organization.py +8 -3
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/radio.py +10 -14
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/staff.py +8 -8
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/tree.py +6 -4
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/exceptions.py +17 -0
- excelalchemy-2.2.7/src/excelalchemy/results.py +457 -0
- excelalchemy-2.2.6/src/excelalchemy/results.py +0 -231
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/LICENSE +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/pyproject.toml +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/constants.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/deprecation.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/header_models.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/identity.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/payloads.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/artifacts.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/email.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/money.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/number_range.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/phone_number.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/string.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/url.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/config.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/const.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/abstract.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/alchemy.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/executor.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/headers.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/import_session.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/rendering.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/rows.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/schema.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/storage.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/storage_minio.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/storage_protocol.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/table.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/writer.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/exc.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/header_models.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/helper/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/helper/pydantic.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/i18n/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/i18n/messages.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/identity.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/metadata.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/py.typed +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/abstract.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/alchemy.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/field.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/header.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/identity.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/result.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/boolean.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/date.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/date_range.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/email.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/money.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/multi_checkbox.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/number.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/number_range.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/organization.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/phone_number.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/radio.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/staff.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/string.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/tree.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/url.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/__init__.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/converter.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/convertor.py +0 -0
- {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/file.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ExcelAlchemy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.7
|
|
4
4
|
Summary: Schema-driven Python library for typed Excel import/export workflows with Pydantic and locale-aware workbooks.
|
|
5
5
|
Keywords: excel,openpyxl,pydantic,minio,schema
|
|
6
6
|
Author: Ray
|
|
@@ -49,9 +49,9 @@ ExcelAlchemy turns Pydantic models into typed workbook contracts:
|
|
|
49
49
|
- render workbook-facing output in `zh-CN` or `en`
|
|
50
50
|
- keep storage pluggable through `ExcelStorage`
|
|
51
51
|
|
|
52
|
-
The current stable release is `2.2.
|
|
52
|
+
The current stable release is `2.2.7`, which continues the 2.x line with stronger API-facing result payloads, a more complete FastAPI reference app, harder install-time smoke verification, and more consistent codec diagnostics.
|
|
53
53
|
|
|
54
|
-
[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) · [Result Objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md) · [Examples Showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md) · [Architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/architecture.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/MIGRATIONS.md)
|
|
54
|
+
[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) · [Result Objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md) · [API Response Cookbook](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/api-response-cookbook.md) · [Examples Showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md) · [Architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/architecture.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/MIGRATIONS.md)
|
|
55
55
|
|
|
56
56
|
## Screenshots
|
|
57
57
|
|
|
@@ -10,9 +10,9 @@ ExcelAlchemy turns Pydantic models into typed workbook contracts:
|
|
|
10
10
|
- render workbook-facing output in `zh-CN` or `en`
|
|
11
11
|
- keep storage pluggable through `ExcelStorage`
|
|
12
12
|
|
|
13
|
-
The current stable release is `2.2.
|
|
13
|
+
The current stable release is `2.2.7`, which continues the 2.x line with stronger API-facing result payloads, a more complete FastAPI reference app, harder install-time smoke verification, and more consistent codec diagnostics.
|
|
14
14
|
|
|
15
|
-
[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) · [Result Objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md) · [Examples Showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md) · [Architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/architecture.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/MIGRATIONS.md)
|
|
15
|
+
[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) · [Result Objects](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/result-objects.md) · [API Response Cookbook](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/api-response-cookbook.md) · [Examples Showcase](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/examples-showcase.md) · [Architecture](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/docs/architecture.md) · [Migration Notes](https://github.com/RayCarterLab/ExcelAlchemy/blob/main/MIGRATIONS.md)
|
|
16
16
|
|
|
17
17
|
## Screenshots
|
|
18
18
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""A Python Library for Reading and Writing Excel Files"""
|
|
2
2
|
|
|
3
|
-
__version__ = '2.2.
|
|
3
|
+
__version__ = '2.2.7'
|
|
4
4
|
from excelalchemy._primitives.constants import CharacterSet, DataRangeOption, DateFormat, Option
|
|
5
5
|
from excelalchemy._primitives.deprecation import ExcelAlchemyDeprecationWarning
|
|
6
6
|
from excelalchemy._primitives.identity import (
|
|
@@ -51,9 +51,12 @@ from excelalchemy.metadata import ExcelMeta, FieldMeta, PatchFieldMeta
|
|
|
51
51
|
from excelalchemy.results import (
|
|
52
52
|
CellErrorMap,
|
|
53
53
|
CellIssueRecord,
|
|
54
|
+
CodeIssueSummary,
|
|
55
|
+
FieldIssueSummary,
|
|
54
56
|
ImportResult,
|
|
55
57
|
RowIssueMap,
|
|
56
58
|
RowIssueRecord,
|
|
59
|
+
RowIssueSummary,
|
|
57
60
|
ValidateHeaderResult,
|
|
58
61
|
ValidateResult,
|
|
59
62
|
ValidateRowResult,
|
|
@@ -66,6 +69,7 @@ __all__ = [
|
|
|
66
69
|
'BooleanCodec',
|
|
67
70
|
'CellErrorMap',
|
|
68
71
|
'CellIssueRecord',
|
|
72
|
+
'CodeIssueSummary',
|
|
69
73
|
'ColumnIndex',
|
|
70
74
|
'CompositeExcelFieldCodec',
|
|
71
75
|
'ConfigError',
|
|
@@ -87,6 +91,7 @@ __all__ = [
|
|
|
87
91
|
'ExcelRowError',
|
|
88
92
|
'ExcelStorage',
|
|
89
93
|
'ExporterConfig',
|
|
94
|
+
'FieldIssueSummary',
|
|
90
95
|
'FieldMeta',
|
|
91
96
|
'ImportMode',
|
|
92
97
|
'ImportResult',
|
|
@@ -117,6 +122,7 @@ __all__ = [
|
|
|
117
122
|
'RowIndex',
|
|
118
123
|
'RowIssueMap',
|
|
119
124
|
'RowIssueRecord',
|
|
125
|
+
'RowIssueSummary',
|
|
120
126
|
'SingleChoiceCodec',
|
|
121
127
|
'SingleOrganization',
|
|
122
128
|
'SingleOrganizationCodec',
|
|
@@ -19,6 +19,9 @@ type WorkbookInputValue = Any
|
|
|
19
19
|
type WorkbookDisplayValue = Any
|
|
20
20
|
type NormalizedImportValue = Any
|
|
21
21
|
|
|
22
|
+
CODEC_LOGGER_NAME = 'excelalchemy.codecs'
|
|
23
|
+
codec_logger = logging.getLogger(CODEC_LOGGER_NAME)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
def _summarize_exception(exc: Exception) -> str:
|
|
24
27
|
details: list[str] = []
|
|
@@ -43,20 +46,29 @@ def _summarize_exception(exc: Exception) -> str:
|
|
|
43
46
|
return exc.__class__.__name__
|
|
44
47
|
|
|
45
48
|
|
|
49
|
+
def _fallback_reason(*, exc: Exception | None = None, reason: str | None = None) -> str:
|
|
50
|
+
if reason:
|
|
51
|
+
return reason
|
|
52
|
+
if exc is not None:
|
|
53
|
+
return _summarize_exception(exc)
|
|
54
|
+
return 'No additional details'
|
|
55
|
+
|
|
56
|
+
|
|
46
57
|
def log_codec_parse_fallback(
|
|
47
58
|
codec_name: str,
|
|
48
59
|
value: object,
|
|
49
60
|
*,
|
|
50
61
|
field_label: str | None = None,
|
|
51
|
-
exc: Exception,
|
|
62
|
+
exc: Exception | None = None,
|
|
63
|
+
reason: str | None = None,
|
|
52
64
|
) -> None:
|
|
53
65
|
field_context = f' for field "{field_label}"' if field_label else ''
|
|
54
|
-
|
|
66
|
+
codec_logger.warning(
|
|
55
67
|
'Codec %s could not parse workbook input%s; keeping the original value %r. Reason: %s',
|
|
56
68
|
codec_name,
|
|
57
69
|
field_context,
|
|
58
70
|
value,
|
|
59
|
-
|
|
71
|
+
_fallback_reason(exc=exc, reason=reason),
|
|
60
72
|
)
|
|
61
73
|
|
|
62
74
|
|
|
@@ -65,15 +77,43 @@ def log_codec_render_fallback(
|
|
|
65
77
|
value: object,
|
|
66
78
|
*,
|
|
67
79
|
field_label: str | None = None,
|
|
68
|
-
exc: Exception,
|
|
80
|
+
exc: Exception | None = None,
|
|
81
|
+
reason: str | None = None,
|
|
69
82
|
) -> None:
|
|
70
83
|
field_context = f' for field "{field_label}"' if field_label else ''
|
|
71
|
-
|
|
84
|
+
codec_logger.warning(
|
|
72
85
|
'Codec %s could not format workbook value%s; returning %r as-is. Reason: %s',
|
|
73
86
|
codec_name,
|
|
74
87
|
field_context,
|
|
75
88
|
value,
|
|
76
|
-
|
|
89
|
+
_fallback_reason(exc=exc, reason=reason),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def log_codec_option_resolution_fallback(
|
|
94
|
+
codec_name: str,
|
|
95
|
+
value: object,
|
|
96
|
+
*,
|
|
97
|
+
field_label: str | None = None,
|
|
98
|
+
exc: Exception | None = None,
|
|
99
|
+
reason: str | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
field_context = f' for field "{field_label}"' if field_label else ''
|
|
102
|
+
codec_logger.warning(
|
|
103
|
+
'Codec %s could not resolve a configured option%s; returning %r as-is. Reason: %s',
|
|
104
|
+
codec_name,
|
|
105
|
+
field_context,
|
|
106
|
+
value,
|
|
107
|
+
_fallback_reason(exc=exc, reason=reason),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def log_codec_missing_options(codec_name: str, *, field_label: str | None = None) -> None:
|
|
112
|
+
field_context = f' for field "{field_label}"' if field_label else ''
|
|
113
|
+
codec_logger.warning(
|
|
114
|
+
'Codec %s is missing configured options%s; workbook comments and validation may be incomplete.',
|
|
115
|
+
codec_name,
|
|
116
|
+
field_context,
|
|
77
117
|
)
|
|
78
118
|
|
|
79
119
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
1
|
from excelalchemy.codecs import excel_choice_codec
|
|
4
|
-
from excelalchemy.codecs.base import
|
|
2
|
+
from excelalchemy.codecs.base import (
|
|
3
|
+
ExcelFieldCodec,
|
|
4
|
+
WorkbookDisplayValue,
|
|
5
|
+
WorkbookInputValue,
|
|
6
|
+
log_codec_render_fallback,
|
|
7
|
+
)
|
|
5
8
|
from excelalchemy.i18n.messages import MessageKey
|
|
6
9
|
from excelalchemy.i18n.messages import display_message as dmsg
|
|
7
10
|
from excelalchemy.i18n.messages import message as msg
|
|
@@ -63,15 +66,19 @@ class Boolean(ExcelFieldCodec):
|
|
|
63
66
|
if value in cls._false_values():
|
|
64
67
|
return cls._false_display()
|
|
65
68
|
if value not in cls._true_values() | cls._false_values():
|
|
66
|
-
|
|
69
|
+
log_codec_render_fallback(
|
|
70
|
+
cls.__name__,
|
|
71
|
+
value,
|
|
72
|
+
field_label=declared.label,
|
|
73
|
+
reason=f'Expected {cls._true_display()!r} or {cls._false_display()!r}',
|
|
74
|
+
)
|
|
67
75
|
return value
|
|
68
76
|
else:
|
|
69
|
-
|
|
70
|
-
'Type %s could not deserialize %s for field %s; returning the default value %s',
|
|
77
|
+
log_codec_render_fallback(
|
|
71
78
|
cls.__name__,
|
|
72
79
|
value,
|
|
73
|
-
declared.label,
|
|
74
|
-
cls._false_display(),
|
|
80
|
+
field_label=declared.label,
|
|
81
|
+
reason=f'Expected a boolean or one of {cls._true_display()!r}/{cls._false_display()!r}',
|
|
75
82
|
)
|
|
76
83
|
|
|
77
84
|
return cls._true_display() if str(value) in cls._true_values() else cls._false_display()
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from datetime import datetime
|
|
3
2
|
from typing import cast
|
|
4
3
|
|
|
@@ -11,6 +10,7 @@ from excelalchemy.codecs.base import (
|
|
|
11
10
|
NormalizedImportValue,
|
|
12
11
|
WorkbookDisplayValue,
|
|
13
12
|
WorkbookInputValue,
|
|
13
|
+
codec_logger,
|
|
14
14
|
log_codec_parse_fallback,
|
|
15
15
|
)
|
|
16
16
|
from excelalchemy.exceptions import ConfigError
|
|
@@ -53,7 +53,7 @@ class Date(ExcelFieldCodec, datetime):
|
|
|
53
53
|
declared = field_meta.declared
|
|
54
54
|
presentation = field_meta.presentation
|
|
55
55
|
if isinstance(value, DateTime):
|
|
56
|
-
|
|
56
|
+
codec_logger.info(
|
|
57
57
|
'Codec %s received a parsed datetime for %s; returning it unchanged: %s',
|
|
58
58
|
cls.__name__,
|
|
59
59
|
declared.label,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from collections.abc import Mapping
|
|
3
2
|
from datetime import datetime
|
|
4
3
|
from typing import cast
|
|
@@ -9,7 +8,7 @@ from pydantic import BaseModel
|
|
|
9
8
|
|
|
10
9
|
from excelalchemy._primitives.constants import DATE_FORMAT_TO_PYTHON_MAPPING, MILLISECOND_TO_SECOND, DataRangeOption
|
|
11
10
|
from excelalchemy._primitives.identity import Key
|
|
12
|
-
from excelalchemy.codecs.base import CompositeExcelFieldCodec, log_codec_parse_fallback
|
|
11
|
+
from excelalchemy.codecs.base import CompositeExcelFieldCodec, log_codec_parse_fallback, log_codec_render_fallback
|
|
13
12
|
from excelalchemy.exceptions import ConfigError
|
|
14
13
|
from excelalchemy.i18n.messages import MessageKey
|
|
15
14
|
from excelalchemy.i18n.messages import display_message as dmsg
|
|
@@ -149,7 +148,12 @@ class DateRange(CompositeExcelFieldCodec):
|
|
|
149
148
|
if mapping is not None:
|
|
150
149
|
return cls.__deserialize__dict(py_date_format, mapping)
|
|
151
150
|
|
|
152
|
-
|
|
151
|
+
log_codec_render_fallback(
|
|
152
|
+
cls.__name__,
|
|
153
|
+
value,
|
|
154
|
+
field_label=field_meta.declared.label,
|
|
155
|
+
reason='The workbook value is not a string, datetime, or start/end mapping',
|
|
156
|
+
)
|
|
153
157
|
return str(value)
|
|
154
158
|
|
|
155
159
|
@classmethod
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from typing import cast
|
|
3
2
|
|
|
4
3
|
from excelalchemy._primitives.constants import MULTI_CHECKBOX_SEPARATOR
|
|
5
4
|
from excelalchemy._primitives.identity import OptionId
|
|
6
|
-
from excelalchemy.codecs.base import ExcelFieldCodec
|
|
5
|
+
from excelalchemy.codecs.base import ExcelFieldCodec, log_codec_missing_options, log_codec_parse_fallback
|
|
7
6
|
from excelalchemy.exceptions import ProgrammaticError
|
|
8
7
|
from excelalchemy.i18n.messages import MessageKey
|
|
9
8
|
from excelalchemy.i18n.messages import display_message as dmsg
|
|
@@ -73,8 +72,11 @@ class MultiCheckbox(ExcelFieldCodec, list[str]):
|
|
|
73
72
|
if isinstance(value, str):
|
|
74
73
|
return [item.strip() for item in value.split(MULTI_CHECKBOX_SEPARATOR)]
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
log_codec_parse_fallback(
|
|
76
|
+
cls.__name__,
|
|
77
|
+
value,
|
|
78
|
+
field_label=field_meta.declared.label,
|
|
79
|
+
reason='Expected a delimited string or a list of selected values',
|
|
78
80
|
)
|
|
79
81
|
return value
|
|
80
82
|
|
|
@@ -92,9 +94,7 @@ class MultiCheckbox(ExcelFieldCodec, list[str]):
|
|
|
92
94
|
raise ProgrammaticError(msg(MessageKey.OPTIONS_CANNOT_BE_NONE_FOR_VALUE_TYPE, value_type=cls.__name__))
|
|
93
95
|
|
|
94
96
|
if not presentation.options: # empty
|
|
95
|
-
|
|
96
|
-
'Field %s of type %s has no options; returning the original value', declared.label, cls.__name__
|
|
97
|
-
)
|
|
97
|
+
log_codec_missing_options(cls.__name__, field_label=declared.label)
|
|
98
98
|
return parsed
|
|
99
99
|
|
|
100
100
|
if len(parsed) != len(set(parsed)):
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from decimal import ROUND_DOWN, Context, Decimal, InvalidOperation
|
|
3
2
|
|
|
4
3
|
from excelalchemy.codecs.base import (
|
|
@@ -6,6 +5,7 @@ from excelalchemy.codecs.base import (
|
|
|
6
5
|
NormalizedImportValue,
|
|
7
6
|
WorkbookDisplayValue,
|
|
8
7
|
WorkbookInputValue,
|
|
8
|
+
codec_logger,
|
|
9
9
|
log_codec_parse_fallback,
|
|
10
10
|
)
|
|
11
11
|
from excelalchemy.i18n.messages import MessageKey
|
|
@@ -24,7 +24,7 @@ def canonicalize_decimal(value: Decimal, digits_limit: int | None) -> Decimal:
|
|
|
24
24
|
context=Context(rounding=ROUND_DOWN),
|
|
25
25
|
)
|
|
26
26
|
except InvalidOperation as e:
|
|
27
|
-
|
|
27
|
+
codec_logger.warning('Codec Number detected precision loss while quantizing fraction_digits: %s', e)
|
|
28
28
|
return value
|
|
29
29
|
|
|
30
30
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from typing import cast
|
|
3
2
|
|
|
4
3
|
from excelalchemy._primitives.constants import MULTI_CHECKBOX_SEPARATOR
|
|
5
4
|
from excelalchemy._primitives.identity import OptionId
|
|
5
|
+
from excelalchemy.codecs.base import log_codec_option_resolution_fallback, log_codec_render_fallback
|
|
6
6
|
from excelalchemy.codecs.multi_checkbox import MultiCheckbox
|
|
7
7
|
from excelalchemy.codecs.radio import Radio
|
|
8
8
|
from excelalchemy.i18n.messages import MessageKey
|
|
@@ -44,7 +44,7 @@ class SingleOrganization(Radio):
|
|
|
44
44
|
try:
|
|
45
45
|
return presentation.options_id_map(field_label=declared.label)[OptionId(value.strip())].name
|
|
46
46
|
except KeyError:
|
|
47
|
-
|
|
47
|
+
log_codec_option_resolution_fallback(cls.__name__, value, field_label=declared.label)
|
|
48
48
|
|
|
49
49
|
return value
|
|
50
50
|
|
|
@@ -88,7 +88,12 @@ class MultiOrganization(MultiCheckbox):
|
|
|
88
88
|
option_names = presentation.exchange_option_ids_to_names(option_ids, field_label=declared.label)
|
|
89
89
|
return MULTI_CHECKBOX_SEPARATOR.join(map(str, option_names))
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
log_codec_render_fallback(
|
|
92
|
+
cls.__name__,
|
|
93
|
+
value,
|
|
94
|
+
field_label=declared.label,
|
|
95
|
+
reason='The workbook value is not a string or a list of option ids',
|
|
96
|
+
)
|
|
92
97
|
return str(value)
|
|
93
98
|
|
|
94
99
|
@classmethod
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
1
|
from excelalchemy._primitives.constants import MULTI_CHECKBOX_SEPARATOR
|
|
4
2
|
from excelalchemy._primitives.identity import OptionId
|
|
5
|
-
from excelalchemy.codecs.base import
|
|
3
|
+
from excelalchemy.codecs.base import (
|
|
4
|
+
ExcelFieldCodec,
|
|
5
|
+
WorkbookDisplayValue,
|
|
6
|
+
WorkbookInputValue,
|
|
7
|
+
log_codec_missing_options,
|
|
8
|
+
log_codec_option_resolution_fallback,
|
|
9
|
+
)
|
|
6
10
|
from excelalchemy.exceptions import ProgrammaticError
|
|
7
11
|
from excelalchemy.i18n.messages import MessageKey
|
|
8
12
|
from excelalchemy.i18n.messages import display_message as dmsg
|
|
@@ -49,7 +53,7 @@ class Radio(ExcelFieldCodec, str):
|
|
|
49
53
|
declared = field_meta.declared
|
|
50
54
|
presentation = field_meta.presentation
|
|
51
55
|
if not presentation.options:
|
|
52
|
-
|
|
56
|
+
log_codec_missing_options(cls.__name__, field_label=declared.label)
|
|
53
57
|
|
|
54
58
|
return '\n'.join(
|
|
55
59
|
[
|
|
@@ -74,13 +78,7 @@ class Radio(ExcelFieldCodec, str):
|
|
|
74
78
|
try:
|
|
75
79
|
return presentation.options_id_map(field_label=declared.label)[value.strip()].name
|
|
76
80
|
except Exception as exc:
|
|
77
|
-
|
|
78
|
-
'Type %s could not resolve option %s for field %s; returning the original value. Reason: %s',
|
|
79
|
-
cls.__name__,
|
|
80
|
-
value,
|
|
81
|
-
declared.label,
|
|
82
|
-
exc,
|
|
83
|
-
)
|
|
81
|
+
log_codec_option_resolution_fallback(cls.__name__, value, field_label=declared.label, exc=exc)
|
|
84
82
|
return value if value is not None else ''
|
|
85
83
|
|
|
86
84
|
@classmethod
|
|
@@ -96,9 +94,7 @@ class Radio(ExcelFieldCodec, str):
|
|
|
96
94
|
raise ProgrammaticError(msg(MessageKey.OPTIONS_CANNOT_BE_NONE_FOR_SELECTION_FIELDS))
|
|
97
95
|
|
|
98
96
|
if not presentation.options: # empty
|
|
99
|
-
|
|
100
|
-
'Field %s of type %s has no options; returning the original value', declared.label, cls.__name__
|
|
101
|
-
)
|
|
97
|
+
log_codec_missing_options(cls.__name__, field_label=declared.label)
|
|
102
98
|
return parsed
|
|
103
99
|
|
|
104
100
|
options_id_map = presentation.options_id_map(field_label=declared.label)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from typing import cast
|
|
3
2
|
|
|
4
3
|
from excelalchemy._primitives.constants import MULTI_CHECKBOX_SEPARATOR
|
|
5
4
|
from excelalchemy._primitives.identity import OptionId
|
|
5
|
+
from excelalchemy.codecs.base import log_codec_option_resolution_fallback, log_codec_render_fallback
|
|
6
6
|
from excelalchemy.codecs.multi_checkbox import MultiCheckbox
|
|
7
7
|
from excelalchemy.codecs.radio import Radio
|
|
8
8
|
from excelalchemy.i18n.messages import MessageKey
|
|
@@ -45,12 +45,7 @@ class SingleStaff(Radio):
|
|
|
45
45
|
try:
|
|
46
46
|
return presentation.options_id_map(field_label=declared.label)[OptionId(value.strip())].name
|
|
47
47
|
except KeyError:
|
|
48
|
-
|
|
49
|
-
'Type %s could not resolve option %s for field %s; returning the original value',
|
|
50
|
-
cls.__name__,
|
|
51
|
-
value,
|
|
52
|
-
declared.label,
|
|
53
|
-
)
|
|
48
|
+
log_codec_option_resolution_fallback(cls.__name__, value, field_label=declared.label)
|
|
54
49
|
return value
|
|
55
50
|
|
|
56
51
|
|
|
@@ -97,7 +92,12 @@ class MultiStaff(MultiCheckbox):
|
|
|
97
92
|
option_names = presentation.exchange_option_ids_to_names(option_ids, field_label=declared.label)
|
|
98
93
|
return f'{MULTI_CHECKBOX_SEPARATOR}'.join(option_names)
|
|
99
94
|
|
|
100
|
-
|
|
95
|
+
log_codec_render_fallback(
|
|
96
|
+
cls.__name__,
|
|
97
|
+
value,
|
|
98
|
+
field_label=declared.label,
|
|
99
|
+
reason='The workbook value is not a string or a list of option ids',
|
|
100
|
+
)
|
|
101
101
|
return str(value)
|
|
102
102
|
|
|
103
103
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
from excelalchemy.codecs.base import (
|
|
2
|
+
WorkbookDisplayValue,
|
|
3
|
+
WorkbookInputValue,
|
|
4
|
+
log_codec_option_resolution_fallback,
|
|
5
|
+
)
|
|
4
6
|
from excelalchemy.codecs.multi_checkbox import MultiCheckbox
|
|
5
7
|
from excelalchemy.codecs.radio import Radio
|
|
6
8
|
from excelalchemy.i18n.messages import MessageKey
|
|
@@ -43,7 +45,7 @@ class SingleTreeNode(Radio):
|
|
|
43
45
|
try:
|
|
44
46
|
return presentation.options_id_map(field_label=declared.label)[value.strip()].name
|
|
45
47
|
except KeyError:
|
|
46
|
-
|
|
48
|
+
log_codec_option_resolution_fallback(cls.__name__, value, field_label=declared.label)
|
|
47
49
|
|
|
48
50
|
return value if value is not None else ''
|
|
49
51
|
|
|
@@ -30,10 +30,22 @@ class ExcelAlchemyError(Exception):
|
|
|
30
30
|
def __str__(self) -> str:
|
|
31
31
|
return self.message
|
|
32
32
|
|
|
33
|
+
@property
|
|
34
|
+
def display_message(self) -> str:
|
|
35
|
+
return self.message
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def code(self) -> str:
|
|
39
|
+
if self.message_key is not None:
|
|
40
|
+
return self.message_key.value
|
|
41
|
+
return type(self).__name__
|
|
42
|
+
|
|
33
43
|
def to_dict(self) -> dict[str, object]:
|
|
34
44
|
payload: dict[str, object] = {
|
|
35
45
|
'type': type(self).__name__,
|
|
46
|
+
'code': self.code,
|
|
36
47
|
'message': self.message,
|
|
48
|
+
'display_message': self.display_message,
|
|
37
49
|
}
|
|
38
50
|
if self.message_key is not None:
|
|
39
51
|
payload['message_key'] = self.message_key.value
|
|
@@ -66,6 +78,10 @@ class ExcelCellError(ExcelAlchemyError):
|
|
|
66
78
|
def __str__(self) -> str:
|
|
67
79
|
return f'【{self.label}】{self.message}'
|
|
68
80
|
|
|
81
|
+
@property
|
|
82
|
+
def display_message(self) -> str:
|
|
83
|
+
return str(self)
|
|
84
|
+
|
|
69
85
|
def __repr__(self) -> str:
|
|
70
86
|
return (
|
|
71
87
|
f"{type(self).__name__}(label=Label('{self.label}'), "
|
|
@@ -103,6 +119,7 @@ class ExcelCellError(ExcelAlchemyError):
|
|
|
103
119
|
def to_dict(self) -> dict[str, object]:
|
|
104
120
|
payload = super().to_dict()
|
|
105
121
|
payload['label'] = str(self.label)
|
|
122
|
+
payload['field_label'] = str(self.label)
|
|
106
123
|
payload['parent_label'] = None if self.parent_label is None else str(self.parent_label)
|
|
107
124
|
payload['unique_label'] = str(self.unique_label)
|
|
108
125
|
return payload
|