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.
Files changed (84) hide show
  1. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/PKG-INFO +3 -3
  2. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/README-pypi.md +2 -2
  3. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/__init__.py +7 -1
  4. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/base.py +46 -6
  5. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/boolean.py +15 -8
  6. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/date.py +2 -2
  7. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/date_range.py +7 -3
  8. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/multi_checkbox.py +7 -7
  9. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/number.py +2 -2
  10. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/organization.py +8 -3
  11. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/radio.py +10 -14
  12. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/staff.py +8 -8
  13. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/tree.py +6 -4
  14. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/exceptions.py +17 -0
  15. excelalchemy-2.2.7/src/excelalchemy/results.py +457 -0
  16. excelalchemy-2.2.6/src/excelalchemy/results.py +0 -231
  17. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/LICENSE +0 -0
  18. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/pyproject.toml +0 -0
  19. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/__init__.py +0 -0
  20. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/constants.py +0 -0
  21. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/deprecation.py +0 -0
  22. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/header_models.py +0 -0
  23. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/identity.py +0 -0
  24. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/_primitives/payloads.py +0 -0
  25. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/artifacts.py +0 -0
  26. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/__init__.py +0 -0
  27. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/email.py +0 -0
  28. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/money.py +0 -0
  29. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/number_range.py +0 -0
  30. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/phone_number.py +0 -0
  31. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/string.py +0 -0
  32. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/codecs/url.py +0 -0
  33. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/config.py +0 -0
  34. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/const.py +0 -0
  35. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/__init__.py +0 -0
  36. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/abstract.py +0 -0
  37. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/alchemy.py +0 -0
  38. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/executor.py +0 -0
  39. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/headers.py +0 -0
  40. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/import_session.py +0 -0
  41. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/rendering.py +0 -0
  42. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/rows.py +0 -0
  43. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/schema.py +0 -0
  44. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/storage.py +0 -0
  45. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/storage_minio.py +0 -0
  46. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/storage_protocol.py +0 -0
  47. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/table.py +0 -0
  48. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/core/writer.py +0 -0
  49. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/exc.py +0 -0
  50. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/header_models.py +0 -0
  51. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/helper/__init__.py +0 -0
  52. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/helper/pydantic.py +0 -0
  53. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/i18n/__init__.py +0 -0
  54. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/i18n/messages.py +0 -0
  55. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/identity.py +0 -0
  56. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/metadata.py +0 -0
  57. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/py.typed +0 -0
  58. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/__init__.py +0 -0
  59. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/abstract.py +0 -0
  60. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/alchemy.py +0 -0
  61. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/field.py +0 -0
  62. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/header.py +0 -0
  63. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/identity.py +0 -0
  64. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/result.py +0 -0
  65. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/__init__.py +0 -0
  66. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/boolean.py +0 -0
  67. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/date.py +0 -0
  68. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/date_range.py +0 -0
  69. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/email.py +0 -0
  70. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/money.py +0 -0
  71. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/multi_checkbox.py +0 -0
  72. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/number.py +0 -0
  73. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/number_range.py +0 -0
  74. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/organization.py +0 -0
  75. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/phone_number.py +0 -0
  76. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/radio.py +0 -0
  77. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/staff.py +0 -0
  78. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/string.py +0 -0
  79. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/tree.py +0 -0
  80. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/types/value/url.py +0 -0
  81. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/__init__.py +0 -0
  82. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/converter.py +0 -0
  83. {excelalchemy-2.2.6 → excelalchemy-2.2.7}/src/excelalchemy/util/convertor.py +0 -0
  84. {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.6
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.6`, which continues the 2.x line with stronger result-object guidance, a copyable FastAPI reference project, more robust smoke verification, and clearer codec fallback diagnostics.
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.6`, which continues the 2.x line with stronger result-object guidance, a copyable FastAPI reference project, more robust smoke verification, and clearer codec fallback diagnostics.
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.6'
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
- logging.warning(
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
- _summarize_exception(exc),
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
- logging.warning(
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
- _summarize_exception(exc),
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 ExcelFieldCodec, WorkbookDisplayValue, WorkbookInputValue
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
- logging.warning('Could not recognize boolean value %s; returning the original value', value)
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
- logging.warning(
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
- logging.info(
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
- logging.warning('%s could not be deserialized; returning the original value', cls.__name__)
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
- logging.warning(
77
- 'ValueType <%s> could not parse Excel input %s; returning the original value', cls.__name__, value
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
- logging.warning(
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
- logging.warning('fraction_digits is too small and causes precision loss: %s', e)
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
- logging.warning('Could not resolve organization option %s; returning the original value', value)
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
- logging.warning('%s could not be deserialized; returning the original value', cls.__name__)
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 ExcelFieldCodec, WorkbookDisplayValue, WorkbookInputValue
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
- logging.error('Field %s of type %s must define options', declared.label, cls.__name__)
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
- logging.warning(
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
- logging.warning(
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
- logging.warning(
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
- logging.warning('%s could not be deserialized', cls.__name__)
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 logging
2
-
3
- from excelalchemy.codecs.base import WorkbookDisplayValue, WorkbookInputValue
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
- logging.warning('Could not resolve tree option %s; returning the original value', value)
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