DataExcept 0.3.0__tar.gz → 0.4.1__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.
- dataexcept-0.4.1/CHANGELOG.md +394 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/CITATION.cff +2 -2
- {dataexcept-0.3.0 → dataexcept-0.4.1}/PKG-INFO +14 -14
- {dataexcept-0.3.0 → dataexcept-0.4.1}/README.md +11 -12
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/__init__.py +16 -1
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/__main__.py +7 -1
- dataexcept-0.4.1/dataexcept/_validation.py +28 -0
- dataexcept-0.4.1/dataexcept/base.py +190 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/database_exceptions.py +7 -3
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/dataengineering_exceptions.py +5 -2
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/datascience_exceptions/base.py +3 -1
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/datascience_exceptions/ingestion.py +7 -5
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/datascience_exceptions/operations.py +6 -4
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/datascience_exceptions/training.py +9 -8
- dataexcept-0.4.1/dataexcept/exceptions/base.py +7 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/notification.py +11 -4
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/io_exceptions.py +7 -4
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/logging_helpers.py +41 -1
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/network_exceptions.py +3 -1
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/pandas_exceptions.py +10 -6
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/pipeline_exceptions.py +8 -5
- dataexcept-0.4.1/dataexcept/redaction.py +201 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/security_exceptions.py +11 -4
- {dataexcept-0.3.0 → dataexcept-0.4.1}/pyproject.toml +27 -3
- dataexcept-0.3.0/CHANGELOG.md +0 -175
- dataexcept-0.3.0/dataexcept/exceptions/base.py +0 -4
- {dataexcept-0.3.0 → dataexcept-0.4.1}/LICENSE +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/_deprecation.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/datascience_exceptions/__init__.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/__init__.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/authentication.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/configuration.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/external.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/lifecycle.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/parsing.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/scheduling.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/exceptions/validation.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/job_exceptions.py +0 -0
- {dataexcept-0.3.0 → dataexcept-0.4.1}/dataexcept/py.typed +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.1] - 2026-08-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Tests that derive the facts stated in more than one file, instead of trusting
|
|
15
|
+
them to be copied correctly: the citation, security policy, changelog and
|
|
16
|
+
checklist must all name the current version, checklist sections must be
|
|
17
|
+
numbered uniquely, no document may quote a test count, and the supported
|
|
18
|
+
Python range must agree across `requires-python`, the classifiers, the CI
|
|
19
|
+
matrix and the release gate.
|
|
20
|
+
- A contract-coverage test. The whole-hierarchy suites called `pytest.skip()`
|
|
21
|
+
when they could not build a class, so a class could sit outside the pickling,
|
|
22
|
+
message and inheritance guarantees with CI still green — and two were doing
|
|
23
|
+
exactly that. Skipping now requires an explicit, reasoned entry in an
|
|
24
|
+
exclusion table, which is empty.
|
|
25
|
+
|
|
26
|
+
- **Python 3.14 support.** `requires-python` capped at `<3.14`, so the package
|
|
27
|
+
refused to install on the current stable interpreter — 3.14 was released in
|
|
28
|
+
October 2025 and is no longer upcoming. The range is now `>=3.10,<3.15`, 3.14
|
|
29
|
+
is in the classifiers and the CI matrix, and `test (3.14)` joins the checks
|
|
30
|
+
the release workflow requires before it will build.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- `SECURITY.md` states the boundary precisely, including what is **not**
|
|
35
|
+
covered: a bare non-URL secret written into free-form text cannot be
|
|
36
|
+
recognised. The previous wording claimed credentials "never appear ...
|
|
37
|
+
whatever you pass in", which was broader than the implementation.
|
|
38
|
+
|
|
39
|
+
- The serialization and hierarchy guarantees are stated more precisely.
|
|
40
|
+
"Survives a process boundary intact" now says what happens to state that
|
|
41
|
+
cannot be serialized, and "catches anything this package raises" is now
|
|
42
|
+
"every operational exception" — constructors deliberately raise plain
|
|
43
|
+
`TypeError` for invalid arguments, which sits outside the hierarchy.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- **An exception pickled by 0.4.0 could not be loaded by 0.4.1.** Restoring the
|
|
48
|
+
chain added three parameters to the private `_rebuild`, and an older payload
|
|
49
|
+
passes only three arguments — so a queued exception that outlived an upgrade,
|
|
50
|
+
or one sent by a worker on the previous release, raised `TypeError` on
|
|
51
|
+
unpickling. The new parameters carry defaults.
|
|
52
|
+
|
|
53
|
+
- The test probe fed a bare string to `Sequence[str]` parameters, so
|
|
54
|
+
`DataFormatError` and `DtypeMismatchError` correctly rejected it and were
|
|
55
|
+
silently skipped. The probe builds them properly now; nothing is skipped.
|
|
56
|
+
- `is_number` accepted booleans, because `bool` subclasses `int` and so
|
|
57
|
+
satisfies `numbers.Real`. A boolean is never a meaningful metric, threshold
|
|
58
|
+
or ratio, and accepting one hid a caller passing the wrong variable.
|
|
59
|
+
- `MergeKeyError("id", "cust_id")` silently became `['i', 'd']` — a bare string
|
|
60
|
+
is a sequence of strings. It now raises `TypeError`, matching
|
|
61
|
+
`DtypeMismatchError`, which already rejected this.
|
|
62
|
+
- `SECURITY.md` named 0.3.x as supported at 0.4.0, `CHECKLIST.md` was dated to
|
|
63
|
+
the previous release and numbered two sections 12, and the README quoted a
|
|
64
|
+
test count that no longer matched. `scripts/bump_version.py` now writes every
|
|
65
|
+
file that states the version, so they cannot drift apart by hand again.
|
|
66
|
+
- `black`'s target version is pinned rather than inferred from
|
|
67
|
+
`requires-python`. Adding 3.14 made inference pick `py314`, and black then
|
|
68
|
+
refused to verify its own output when run on an older interpreter — which is
|
|
69
|
+
what CI does.
|
|
70
|
+
|
|
71
|
+
- **Exception chaining was lost on a pickle round trip.** `__reduce__` saved
|
|
72
|
+
`args` and `__dict__`, but `__cause__`, `__context__` and
|
|
73
|
+
`__suppress_context__` are special exception state rather than `__dict__`
|
|
74
|
+
entries — so a wrapped exception rebuilt in another process no longer showed
|
|
75
|
+
what actually failed. All three are now restored explicitly. The 0.4.0 tests
|
|
76
|
+
did not catch this because they compared `args`, rendered text and `__dict__`
|
|
77
|
+
and never the chain; they now check it.
|
|
78
|
+
- **An exception carrying unpickleable state could not cross a process
|
|
79
|
+
boundary at all.** Several classes accept arbitrary caller state, so a
|
|
80
|
+
lambda, generator, open file or locally defined class made the whole
|
|
81
|
+
exception unserializable — replacing the real failure with a serialization
|
|
82
|
+
error about it. Such values are now replaced by an `UnpicklableValue`
|
|
83
|
+
describing what was there, and an unpickleable cause by an
|
|
84
|
+
`UnpicklableCause`, so the exception still arrives.
|
|
85
|
+
|
|
86
|
+
### Security
|
|
87
|
+
|
|
88
|
+
- **Redaction is no longer defeated by the traceback.** `log_exception` passed
|
|
89
|
+
`exc_info`, so logging rendered the whole exception chain — including a
|
|
90
|
+
wrapped third-party exception whose own message still quoted the
|
|
91
|
+
credential-bearing URL. Redacting what DataExcept renders did nothing about
|
|
92
|
+
that. When the chain contains a URL, `log_exception` now formats the
|
|
93
|
+
traceback and scrubs it; every other exception keeps the structured
|
|
94
|
+
`exc_info` path, so nothing changes for them.
|
|
95
|
+
- `SECURITY.md` documents what remains outside that boundary: the wrapped
|
|
96
|
+
exception object is still reachable, so `traceback.print_exc()`,
|
|
97
|
+
`repr(exc.__dict__)` or `logger.error(..., exc_info=True)` will render its
|
|
98
|
+
text. A third-party exception's message is not ours to rewrite.
|
|
99
|
+
|
|
100
|
+
- **Every GitHub Action is pinned to a full-length commit SHA.** All 34
|
|
101
|
+
references used mutable tags, including `pypa/gh-action-pypi-publish` in the
|
|
102
|
+
OIDC publishing job — the step that holds the credential which uploads to
|
|
103
|
+
PyPI. Whoever controls an action repository can move a tag to different code
|
|
104
|
+
at any time; a commit SHA is the only immutable reference. Each pin carries
|
|
105
|
+
the version in a trailing comment so it stays reviewable and Dependabot can
|
|
106
|
+
still update it, and a test fails if any mutable reference reappears.
|
|
107
|
+
|
|
108
|
+
- **Credentials in a URL path are now redacted.** `redact_url` kept the whole
|
|
109
|
+
path, so `WebhookError` logged a Slack webhook URL — which Slack documents as
|
|
110
|
+
a secret in its entirety — unchanged. Where the path *is* the credential the
|
|
111
|
+
path is now dropped, keeping the host, which is what makes the error
|
|
112
|
+
actionable.
|
|
113
|
+
- **Sensitive parameters are matched by substring, not an exact allowlist.**
|
|
114
|
+
`X-Amz-Signature`, `X-Amz-Credential`, `auth_token` and `refresh_token` all
|
|
115
|
+
passed through before. Fragments are covered too, so an OAuth
|
|
116
|
+
`#access_token=` no longer survives.
|
|
117
|
+
- **A secret can no longer be reintroduced after redaction.** Redacting only
|
|
118
|
+
the structured argument left two open routes: a caller-supplied `message`,
|
|
119
|
+
and the text of a wrapped exception quoting the original URL. Every message
|
|
120
|
+
in the hierarchy now passes through one scrubbing boundary, so a URL is
|
|
121
|
+
redacted wherever it appears. Where the library was handed the secret
|
|
122
|
+
explicitly, that exact value is removed from the message as well.
|
|
123
|
+
- **URL-bearing fields beyond the four patched classes are redacted.**
|
|
124
|
+
`DataLoadingError.source` documents itself as "file path, URL" and rendered
|
|
125
|
+
a presigned S3 URL verbatim. Nine such fields now use `redact_if_url`, which
|
|
126
|
+
leaves ordinary file paths untouched.
|
|
127
|
+
|
|
128
|
+
## [0.4.0] - 2026-08-24
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **`DataExceptError`, the root of the hierarchy.** Every exception the package
|
|
133
|
+
raises now derives from it, so one clause catches the whole library:
|
|
134
|
+
```python
|
|
135
|
+
except DataExceptError:
|
|
136
|
+
...
|
|
137
|
+
```
|
|
138
|
+
The nine domain roots (`JobError`, `DataScienceError`, `PipelineError` and
|
|
139
|
+
the rest) sit beneath it and still catch only their own domain, so granular
|
|
140
|
+
handling is unchanged.
|
|
141
|
+
|
|
142
|
+
### Changed
|
|
143
|
+
|
|
144
|
+
- `SECURITY.md` claimed 0.1.x was the supported version, and `CHECKLIST.md`
|
|
145
|
+
still described an uncommitted lockfile, 30 of 96 top-level exports, 109 tests
|
|
146
|
+
and 85% coverage. Both now match the project.
|
|
147
|
+
- **Coverage is now measured honestly, and gated.** `coverage run -m pytest`
|
|
148
|
+
had no `source` setting, so it counted the tests and examples in the
|
|
149
|
+
denominator — and test files are by definition fully executed. The reported
|
|
150
|
+
figure was inflated: 86% when the package alone was at 79%.
|
|
151
|
+
`[tool.coverage.run]` now restricts measurement to `dataexcept` and enables
|
|
152
|
+
branch coverage, and `fail_under = 91` stops it regressing. The honest number
|
|
153
|
+
is **92%**; the README, checklist and roadmap now quote that rather than the
|
|
154
|
+
inflated one.
|
|
155
|
+
- The CLI is exercised in-process as well as through a subprocess. The
|
|
156
|
+
subprocess tests verify real invocation but coverage cannot see inside them,
|
|
157
|
+
which left `__main__.py` reporting 23% despite being tested. It now reports
|
|
158
|
+
93%.
|
|
159
|
+
|
|
160
|
+
### Fixed
|
|
161
|
+
|
|
162
|
+
- **NumPy scalars are accepted where a number is expected.** Eleven validations
|
|
163
|
+
used `isinstance(value, (int, float))`, which rejects `numpy.float32` and
|
|
164
|
+
`numpy.int64` — a poor answer from a library aimed at data science. They now
|
|
165
|
+
use `numbers.Real`, which NumPy registers its scalar types with, so this needs
|
|
166
|
+
no dependency on NumPy. Arrays and non-numbers are still rejected.
|
|
167
|
+
- **A wrapped exception is now chained.** Constructors that take an underlying
|
|
168
|
+
exception recorded it on an attribute but never set `__cause__`, so a
|
|
169
|
+
traceback did not show what actually failed. `DataExceptError` now mirrors it,
|
|
170
|
+
and Python prints "The above exception was the direct cause of the following
|
|
171
|
+
exception" as if `raise ... from` had been used.
|
|
172
|
+
- `from dataexcept import *` failed under `-W error::DeprecationWarning`,
|
|
173
|
+
because `__all__` listed the deprecated `job_exceptions` module. It is no
|
|
174
|
+
longer advertised there; it remains importable until 1.0.0.
|
|
175
|
+
- A redundant `global` declaration in `examples/lambda_main.py` raised three
|
|
176
|
+
`F824` warnings. CI linted only `dataexcept` and `tests` with flake8 while the
|
|
177
|
+
formatters covered `examples` and `scripts`; flake8 now covers all four.
|
|
178
|
+
- **Four exceptions discarded the reason for the failure.**
|
|
179
|
+
`DataLoadingError`, `MissingDataError`, `ModelSerializationError` and
|
|
180
|
+
`DeploymentError` rendered only their identifying attribute —
|
|
181
|
+
`[DataLoadingError] orders.csv` — while "invalid utf-8", "disk full" or
|
|
182
|
+
"permission denied" sat unseen in `args[0]`. Since logging uses `str(exc)`,
|
|
183
|
+
the part a reader needs never reached the log. All four now render the full
|
|
184
|
+
message, and a test asserts no class drops it.
|
|
185
|
+
- **Exceptions can now cross a process boundary.** They could not be pickled:
|
|
186
|
+
most constructors take several arguments while `Exception.args` holds only
|
|
187
|
+
the rendered message, and the default protocol replays `args` through
|
|
188
|
+
`__init__`. Of 98 classes, 39 raised `TypeError` on unpickling and a further
|
|
189
|
+
47 came back with different state — only 10 round-tripped exactly. Raising
|
|
190
|
+
one inside a `ProcessPoolExecutor` killed the pool with `BrokenProcessPool`.
|
|
191
|
+
`DataExceptError.__reduce__` restores `args` and `__dict__` directly instead
|
|
192
|
+
of replaying `__init__`. All 97 constructible classes now round-trip with
|
|
193
|
+
identical type, message and attributes, covered by a test per class plus a
|
|
194
|
+
real process-pool test.
|
|
195
|
+
- The stability policy and README both claimed `except JobError:` catches
|
|
196
|
+
"anything else this library raises". It did not — there were nine
|
|
197
|
+
disconnected trees under `Exception`, so `JobError` caught neither
|
|
198
|
+
`ModelTrainingError` nor `PipelineError` nor `DatabaseError`. The claim is
|
|
199
|
+
now true of `DataExceptError`, and the docs say which base covers what.
|
|
200
|
+
|
|
201
|
+
### Security
|
|
202
|
+
|
|
203
|
+
- **A release now has to prove where it came from.** The release workflow
|
|
204
|
+
checked only that the tag text matched `pyproject.toml`, so a tag pushed to
|
|
205
|
+
an unreviewed branch could reach the PyPI publishing job. It now refuses to
|
|
206
|
+
build unless the tagged commit is reachable from `main` and the same checks
|
|
207
|
+
branch protection requires are green on that exact commit.
|
|
208
|
+
- The built wheel is tested before it is published. A new `verify-wheel` job
|
|
209
|
+
installs the artifact, deletes the source package so nothing can import it by
|
|
210
|
+
accident, and runs the whole suite against what will actually be uploaded.
|
|
211
|
+
`scripts/check_wheel.py` then asserts the distribution ships `py.typed` and a
|
|
212
|
+
complete `__all__` — a file can be present in the repository and missing from
|
|
213
|
+
the artifact.
|
|
214
|
+
- **Credentials are no longer written into exception messages.** `log_exception`
|
|
215
|
+
logs `str(exc)`, so a failed connection put the database password in the log.
|
|
216
|
+
`InvalidTokenError` embedded the whole token; `DatabaseConnectionError` the
|
|
217
|
+
whole connection URL including username and password; `WebhookError` and
|
|
218
|
+
`ApiError` the URL including any signing or key parameter.
|
|
219
|
+
These are now redacted before being stored or rendered, so the raw value is
|
|
220
|
+
absent from the message, the attributes and a pickle of the exception. A
|
|
221
|
+
secret renders as `***(1a2b3c4d)` — a truncated SHA-256, so the same bad
|
|
222
|
+
credential failing repeatedly stays correlatable in a log without appearing
|
|
223
|
+
in it. Host, port and path survive, because those are what make the error
|
|
224
|
+
actionable.
|
|
225
|
+
`QueryExecutionError` still embeds the SQL it is given; SECURITY.md now says
|
|
226
|
+
so explicitly rather than leaving it to be discovered.
|
|
227
|
+
|
|
228
|
+
## [0.3.0] - 2026-08-24
|
|
229
|
+
|
|
230
|
+
### Added
|
|
231
|
+
|
|
232
|
+
- **Every exception the package defines is now importable from `dataexcept`
|
|
233
|
+
directly.** All 98 classes are exported at the top level, so callers no
|
|
234
|
+
longer need to know which domain module a class lives in. The domain modules
|
|
235
|
+
export the same objects, so `from dataexcept import ValidationError` and
|
|
236
|
+
`from dataexcept.exceptions import ValidationError` are interchangeable and
|
|
237
|
+
`except` behaves identically either way.
|
|
238
|
+
|
|
239
|
+
This was only safe because 0.2.0 removed the two hazards that make a flat
|
|
240
|
+
namespace dangerous: there are no duplicate class names left, and nothing
|
|
241
|
+
shadows a Python builtin. Imports are explicit rather than generated at
|
|
242
|
+
runtime, so type checkers and IDEs see the full surface — the package ships
|
|
243
|
+
`py.typed`.
|
|
244
|
+
- `dataexcept.exceptions` and `dataexcept.logging_helpers` are now named in
|
|
245
|
+
`__all__` alongside the other domain modules; the stability policy already
|
|
246
|
+
described them as public.
|
|
247
|
+
- Tests covering the public surface: every exception the package defines must
|
|
248
|
+
be exported and resolve, each top-level export must be the *same object* as
|
|
249
|
+
the submodule one, no exported name may shadow a builtin, no two exceptions
|
|
250
|
+
may share a name, and `from dataexcept import *` must expose the documented
|
|
251
|
+
surface. Verified these fail when an unexported exception is introduced.
|
|
252
|
+
- Tests that `poetry.lock` is committed, and that `docs/requirements.txt`
|
|
253
|
+
agrees with the Poetry `docs` group — the two list the same packages and
|
|
254
|
+
could drift apart silently.
|
|
255
|
+
|
|
256
|
+
### Changed
|
|
257
|
+
|
|
258
|
+
- `poetry.lock` is committed, and CI installs from it. Every job previously ran
|
|
259
|
+
`pip install black flake8 isort mypy ruff` unpinned, so a new release of any
|
|
260
|
+
linter could turn the build red with no commit to point at. `poetry check
|
|
261
|
+
--lock` now fails the build if the lock and `pyproject.toml` disagree.
|
|
262
|
+
- The coverage badge is generated by `scripts/coverage_badge.py` instead of
|
|
263
|
+
the `coverage-badge` package. That package still imports `pkg_resources`,
|
|
264
|
+
removed in setuptools 81, so using it required pinning `setuptools<81` — and
|
|
265
|
+
dependency review flagged a moderate-severity advisory against the version
|
|
266
|
+
that pin selected. Its last release was August 2024. Generating the SVG
|
|
267
|
+
directly removes the dependency, the pin and the advisory together; the
|
|
268
|
+
output is byte-identical to the published badge apart from the percentage.
|
|
269
|
+
|
|
270
|
+
## [0.2.1] - 2026-08-24
|
|
271
|
+
|
|
272
|
+
Documentation and CLI fixes. 0.2.0's README is what PyPI renders as the
|
|
273
|
+
project description, and its quick-start example did not run.
|
|
274
|
+
|
|
275
|
+
### Fixed
|
|
276
|
+
|
|
277
|
+
- `python -m dataexcept --version` reported `__main__.py` as the program name
|
|
278
|
+
instead of `dataexcept`, because argparse defaults `prog` to `sys.argv[0]`.
|
|
279
|
+
- `dataexcept list` imported the deprecated `job_exceptions` shim to build its
|
|
280
|
+
output, so it emitted a `DeprecationWarning` at anyone who merely wanted to
|
|
281
|
+
see what the package offers, and it advertised `ConnectionError` and
|
|
282
|
+
`TimeoutError` alongside their replacements. Deprecated modules are now
|
|
283
|
+
skipped; the listing is 98 names, matching the classes the package defines.
|
|
284
|
+
- README's quick-start example began `from dataexcept import ValidationError,
|
|
285
|
+
ModelTrainingError`, which raises `ImportError` — `ModelTrainingError` is in
|
|
286
|
+
`dataexcept.datascience_exceptions` and is not re-exported at the top level.
|
|
287
|
+
- `docs/advanced_usage.md` taught `from dataexcept.job_exceptions import
|
|
288
|
+
JobError`, the deprecated path.
|
|
289
|
+
- README's comparison table showed exception messages without the
|
|
290
|
+
`[ClassName]` prefix the classes actually emit, its sample `dataexcept list`
|
|
291
|
+
output did not match the real alphabetical listing, its exception count and
|
|
292
|
+
CLI version were stale, and its end-to-end example used `np.log` without
|
|
293
|
+
importing numpy.
|
|
294
|
+
|
|
295
|
+
### Added
|
|
296
|
+
|
|
297
|
+
- Tests that read the documentation: every `from dataexcept... import ...` in
|
|
298
|
+
README and `docs/` must resolve, no example may import a deprecated module,
|
|
299
|
+
and the README's exception count must match the package. Checked that these
|
|
300
|
+
fail when the original defects are reintroduced.
|
|
301
|
+
|
|
302
|
+
## [0.2.0] - 2026-08-24
|
|
303
|
+
|
|
304
|
+
### Changed
|
|
305
|
+
|
|
306
|
+
- **`ConnectionError` is now `ServiceConnectionError`, and `TimeoutError` is
|
|
307
|
+
now `OperationTimeoutError`.** The old names shadowed Python builtins without
|
|
308
|
+
inheriting from them, so after `from dataexcept import ConnectionError` an
|
|
309
|
+
`except ConnectionError:` in that module silently stopped catching real
|
|
310
|
+
socket failures.
|
|
311
|
+
- **`datascience_exceptions.SerializationError` is now
|
|
312
|
+
`ModelSerializationError`**, and **`pipeline_exceptions.FeatureEngineeringError`
|
|
313
|
+
is now `FeaturePreprocessingError`.** Each of those names previously referred
|
|
314
|
+
to two different classes in different modules, so catching one silently
|
|
315
|
+
missed the other.
|
|
316
|
+
- Project metadata moved from Poetry's `[tool.poetry]` table to the standard
|
|
317
|
+
PEP 621 `[project]` table, clearing every `poetry check` deprecation. The
|
|
318
|
+
license is now an SPDX expression (PEP 639), so the built metadata carries
|
|
319
|
+
`License-Expression: MIT` and the redundant license classifier is gone.
|
|
320
|
+
Wheel and sdist contents are otherwise unchanged.
|
|
321
|
+
- `dataexcept.job_exceptions` now names its removal version, 1.0.0, in both the
|
|
322
|
+
warning and the module docstring.
|
|
323
|
+
|
|
324
|
+
### Deprecated
|
|
325
|
+
|
|
326
|
+
- `ConnectionError`, `TimeoutError`, `datascience_exceptions.SerializationError`
|
|
327
|
+
and `pipeline_exceptions.FeatureEngineeringError`. All four still resolve, to
|
|
328
|
+
the **same class object** as their replacement, so existing `except` clauses
|
|
329
|
+
keep working; touching one emits a `DeprecationWarning` naming the
|
|
330
|
+
replacement and 1.0.0 as the removal. Importing the package does not warn.
|
|
331
|
+
Find remaining uses with `python -W error::DeprecationWarning -m pytest`.
|
|
332
|
+
|
|
333
|
+
### Added
|
|
334
|
+
|
|
335
|
+
- A published [API stability policy](https://diogoribeiro7.github.io/DataExcept/stability/)
|
|
336
|
+
stating what is public, what each kind of change costs in version terms, the
|
|
337
|
+
deprecation process, and how to migrate off the 0.2.0 renames.
|
|
338
|
+
- Security scanning: CodeQL, and pip-audit against the runtime and
|
|
339
|
+
documentation dependency sets, on push, pull request and weekly — advisories
|
|
340
|
+
are published against code that has not changed. Pull requests also get a
|
|
341
|
+
dependency review failing at moderate severity.
|
|
342
|
+
- Complexity and security linting via ruff's mccabe (`C90`) and flake8-bandit
|
|
343
|
+
(`S`) rule sets, so neither needs a separate tool. Complexity is capped at 8;
|
|
344
|
+
the highest score in the package is 6.
|
|
345
|
+
- A regression guard that fails if any exported name ever shadows a builtin
|
|
346
|
+
again.
|
|
347
|
+
- `__all__` on `dataexcept.logging_helpers`, the one public module without one.
|
|
348
|
+
|
|
349
|
+
### Fixed
|
|
350
|
+
|
|
351
|
+
- `dataexcept.__version__` and `tests/test_version.py` read the version out of
|
|
352
|
+
`pyproject.toml` when the package is not installed, and were still looking in
|
|
353
|
+
`[tool.poetry]`. They now read `[project]`.
|
|
354
|
+
- `examples/example_usage.py` raised `TimeoutError` with keyword arguments the
|
|
355
|
+
builtin does not accept — a live instance of the shadowing hazard.
|
|
356
|
+
|
|
357
|
+
## [0.1.0] - 2026-08-24
|
|
358
|
+
|
|
359
|
+
First public release.
|
|
360
|
+
|
|
361
|
+
### Added
|
|
362
|
+
|
|
363
|
+
- Hierarchical exception classes for data science, machine learning and data
|
|
364
|
+
engineering workflows. Catch a specific failure or a broad category, and get
|
|
365
|
+
a message that names the value that caused it rather than a bare
|
|
366
|
+
`ValueError`.
|
|
367
|
+
- Domain modules for validation, configuration, authentication, parsing,
|
|
368
|
+
serialization, scheduling, notification, lifecycle and external-service
|
|
369
|
+
errors, plus dedicated pandas, database, network, I/O, pipeline and security
|
|
370
|
+
exception groups.
|
|
371
|
+
- `dataexcept.logging_helpers` with `log_exception`, `log_and_raise` and
|
|
372
|
+
`log_then_raise`, for logging exceptions with structured context and
|
|
373
|
+
re-raising without losing the traceback.
|
|
374
|
+
- A `dataexcept` command-line entry point that lists the exported exception
|
|
375
|
+
classes and reports the installed version.
|
|
376
|
+
- A `py.typed` marker, backed by a mypy-clean codebase that CI enforces, so
|
|
377
|
+
downstream type checkers get annotations that are actually correct.
|
|
378
|
+
- Documentation at
|
|
379
|
+
[diogoribeiro7.github.io/DataExcept](https://diogoribeiro7.github.io/DataExcept/),
|
|
380
|
+
including an API reference generated from the docstrings.
|
|
381
|
+
|
|
382
|
+
### Notes
|
|
383
|
+
|
|
384
|
+
- Supports Python 3.10 through 3.13.
|
|
385
|
+
- Published to PyPI via OIDC trusted publishing; no long-lived API token is
|
|
386
|
+
involved in a release.
|
|
387
|
+
|
|
388
|
+
[Unreleased]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.1...HEAD
|
|
389
|
+
[0.4.1]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.0...v0.4.1
|
|
390
|
+
[0.4.0]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.3.0...v0.4.0
|
|
391
|
+
[0.3.0]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.2.1...v0.3.0
|
|
392
|
+
[0.2.1]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.2.0...v0.2.1
|
|
393
|
+
[0.2.0]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.1.0...v0.2.0
|
|
394
|
+
[0.1.0]: https://github.com/DiogoRibeiro7/DataExcept/releases/tag/v0.1.0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
cff-version: 1.2.0
|
|
2
2
|
message: "If you use this software, please cite it using the following metadata."
|
|
3
3
|
title: "DataExcept"
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.4.1"
|
|
5
5
|
authors:
|
|
6
6
|
- family-names: "Ribeiro"
|
|
7
7
|
given-names: "Diogo"
|
|
@@ -11,4 +11,4 @@ authors:
|
|
|
11
11
|
type: software
|
|
12
12
|
url: "https://github.com/DiogoRibeiro7/DataExcept"
|
|
13
13
|
repository-code: "https://github.com/DiogoRibeiro7/DataExcept"
|
|
14
|
-
date-released: "2026-08-
|
|
14
|
+
date-released: "2026-08-25"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DataExcept
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: A Python package providing structured, easily-extendable custom exception types.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -9,13 +9,14 @@ Author: Diogo Ribeiro
|
|
|
9
9
|
Author-email: dfr@esmad.ipp.pt
|
|
10
10
|
Maintainer: Diogo Ribeiro
|
|
11
11
|
Maintainer-email: diogo.debastos.ribeiro@gmail.com
|
|
12
|
-
Requires-Python: >=3.10,<3.
|
|
12
|
+
Requires-Python: >=3.10,<3.15
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Requires-Dist: tomli ; python_version < "3.11"
|
|
20
21
|
Project-URL: Changelog, https://github.com/DiogoRibeiro7/DataExcept/releases
|
|
21
22
|
Project-URL: Documentation, https://diogoribeiro7.github.io/DataExcept/
|
|
@@ -41,13 +42,13 @@ Description-Content-Type: text/markdown
|
|
|
41
42
|
|
|
42
43
|
## 🎯 Key Features
|
|
43
44
|
|
|
44
|
-
- **🏗️ Hierarchical Structure**: Catch specific
|
|
45
|
+
- **🏗️ Hierarchical Structure**: Catch one specific error, a whole domain, or every operational error via `DataExceptError`
|
|
45
46
|
- **📦 One Import**: Every exception is available from `dataexcept` directly, or from its domain module — same objects either way
|
|
46
|
-
- **📊 Data Science Focused**:
|
|
47
|
-
- **🔧 Production Ready**:
|
|
47
|
+
- **📊 Data Science Focused**: 100 exception classes covering ML pipelines, feature engineering, model training
|
|
48
|
+
- **🔧 Production Ready**: Logging helpers, error context, and exceptions that pickle — so they cross a process boundary with their message, attributes and cause intact
|
|
48
49
|
- **📚 Academic Quality**: Proper documentation, type hints, and citation support
|
|
49
|
-
- **🐍 Python 3.10
|
|
50
|
-
- **🧪 Well Tested**:
|
|
50
|
+
- **🐍 Python 3.10 – 3.14**: Every supported version tested in CI, with full type safety
|
|
51
|
+
- **🧪 Well Tested**: Full branch coverage of the package gated in CI, with contract tests over every exception class
|
|
51
52
|
|
|
52
53
|
## 📦 Quick Installation
|
|
53
54
|
|
|
@@ -105,8 +106,7 @@ def load_dataset(file_path: str) -> pd.DataFrame:
|
|
|
105
106
|
### Exception Hierarchies
|
|
106
107
|
|
|
107
108
|
```python
|
|
108
|
-
from dataexcept import
|
|
109
|
-
from dataexcept.datascience_exceptions import ModelTrainingError, ConvergenceError
|
|
109
|
+
from dataexcept import ConvergenceError, DataExceptError, ModelTrainingError
|
|
110
110
|
|
|
111
111
|
try:
|
|
112
112
|
# Your ML pipeline
|
|
@@ -119,8 +119,8 @@ except ModelTrainingError:
|
|
|
119
119
|
# Handle any training-related error
|
|
120
120
|
logger.error("Training failed, falling back to simpler model")
|
|
121
121
|
train_simple_model()
|
|
122
|
-
except
|
|
123
|
-
# Handle
|
|
122
|
+
except DataExceptError:
|
|
123
|
+
# Handle anything else DataExcept raised
|
|
124
124
|
logger.error("Job failed, notifying administrators")
|
|
125
125
|
send_alert()
|
|
126
126
|
```
|
|
@@ -214,7 +214,7 @@ except Exception as exc:
|
|
|
214
214
|
### Command Line Interface
|
|
215
215
|
|
|
216
216
|
```bash
|
|
217
|
-
# List every exception class the package exports (
|
|
217
|
+
# List every exception class the package exports (100 of them, alphabetically)
|
|
218
218
|
$ dataexcept list
|
|
219
219
|
ApiError
|
|
220
220
|
AuthenticationError
|
|
@@ -225,7 +225,7 @@ BiasDetectionError
|
|
|
225
225
|
|
|
226
226
|
# Check version
|
|
227
227
|
$ dataexcept --version
|
|
228
|
-
dataexcept 0.
|
|
228
|
+
dataexcept 0.4.1
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
## 🎯 Use Cases
|
|
@@ -375,7 +375,7 @@ If you use DataExcept in your research, please cite it:
|
|
|
375
375
|
author = {Ribeiro, Diogo},
|
|
376
376
|
title = {DataExcept: Structured Exception Handling for Data Science},
|
|
377
377
|
url = {https://github.com/DiogoRibeiro7/DataExcept},
|
|
378
|
-
version = {0.
|
|
378
|
+
version = {0.4.1},
|
|
379
379
|
year = {2026},
|
|
380
380
|
publisher = {GitHub}
|
|
381
381
|
}
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
## 🎯 Key Features
|
|
17
17
|
|
|
18
|
-
- **🏗️ Hierarchical Structure**: Catch specific
|
|
18
|
+
- **🏗️ Hierarchical Structure**: Catch one specific error, a whole domain, or every operational error via `DataExceptError`
|
|
19
19
|
- **📦 One Import**: Every exception is available from `dataexcept` directly, or from its domain module — same objects either way
|
|
20
|
-
- **📊 Data Science Focused**:
|
|
21
|
-
- **🔧 Production Ready**:
|
|
20
|
+
- **📊 Data Science Focused**: 100 exception classes covering ML pipelines, feature engineering, model training
|
|
21
|
+
- **🔧 Production Ready**: Logging helpers, error context, and exceptions that pickle — so they cross a process boundary with their message, attributes and cause intact
|
|
22
22
|
- **📚 Academic Quality**: Proper documentation, type hints, and citation support
|
|
23
|
-
- **🐍 Python 3.10
|
|
24
|
-
- **🧪 Well Tested**:
|
|
23
|
+
- **🐍 Python 3.10 – 3.14**: Every supported version tested in CI, with full type safety
|
|
24
|
+
- **🧪 Well Tested**: Full branch coverage of the package gated in CI, with contract tests over every exception class
|
|
25
25
|
|
|
26
26
|
## 📦 Quick Installation
|
|
27
27
|
|
|
@@ -79,8 +79,7 @@ def load_dataset(file_path: str) -> pd.DataFrame:
|
|
|
79
79
|
### Exception Hierarchies
|
|
80
80
|
|
|
81
81
|
```python
|
|
82
|
-
from dataexcept import
|
|
83
|
-
from dataexcept.datascience_exceptions import ModelTrainingError, ConvergenceError
|
|
82
|
+
from dataexcept import ConvergenceError, DataExceptError, ModelTrainingError
|
|
84
83
|
|
|
85
84
|
try:
|
|
86
85
|
# Your ML pipeline
|
|
@@ -93,8 +92,8 @@ except ModelTrainingError:
|
|
|
93
92
|
# Handle any training-related error
|
|
94
93
|
logger.error("Training failed, falling back to simpler model")
|
|
95
94
|
train_simple_model()
|
|
96
|
-
except
|
|
97
|
-
# Handle
|
|
95
|
+
except DataExceptError:
|
|
96
|
+
# Handle anything else DataExcept raised
|
|
98
97
|
logger.error("Job failed, notifying administrators")
|
|
99
98
|
send_alert()
|
|
100
99
|
```
|
|
@@ -188,7 +187,7 @@ except Exception as exc:
|
|
|
188
187
|
### Command Line Interface
|
|
189
188
|
|
|
190
189
|
```bash
|
|
191
|
-
# List every exception class the package exports (
|
|
190
|
+
# List every exception class the package exports (100 of them, alphabetically)
|
|
192
191
|
$ dataexcept list
|
|
193
192
|
ApiError
|
|
194
193
|
AuthenticationError
|
|
@@ -199,7 +198,7 @@ BiasDetectionError
|
|
|
199
198
|
|
|
200
199
|
# Check version
|
|
201
200
|
$ dataexcept --version
|
|
202
|
-
dataexcept 0.
|
|
201
|
+
dataexcept 0.4.1
|
|
203
202
|
```
|
|
204
203
|
|
|
205
204
|
## 🎯 Use Cases
|
|
@@ -349,7 +348,7 @@ If you use DataExcept in your research, please cite it:
|
|
|
349
348
|
author = {Ribeiro, Diogo},
|
|
350
349
|
title = {DataExcept: Structured Exception Handling for Data Science},
|
|
351
350
|
url = {https://github.com/DiogoRibeiro7/DataExcept},
|
|
352
|
-
version = {0.
|
|
351
|
+
version = {0.4.1},
|
|
353
352
|
year = {2026},
|
|
354
353
|
publisher = {GitHub}
|
|
355
354
|
}
|
|
@@ -4,6 +4,12 @@ Every exception the package defines is importable straight from here::
|
|
|
4
4
|
|
|
5
5
|
from dataexcept import ValidationError, ModelTrainingError
|
|
6
6
|
|
|
7
|
+
They all derive from :class:`DataExceptError`, so one clause catches anything
|
|
8
|
+
this package raises::
|
|
9
|
+
|
|
10
|
+
except DataExceptError:
|
|
11
|
+
...
|
|
12
|
+
|
|
7
13
|
The domain modules (``datascience_exceptions``, ``pipeline_exceptions`` and so
|
|
8
14
|
on) remain importable and export the same objects, so both spellings work and
|
|
9
15
|
refer to the same classes.
|
|
@@ -32,6 +38,7 @@ from . import ( # noqa: F401
|
|
|
32
38
|
security_exceptions,
|
|
33
39
|
)
|
|
34
40
|
from ._deprecation import resolve_deprecated
|
|
41
|
+
from .base import DataExceptError, UnpicklableCause, UnpicklableValue
|
|
35
42
|
from .database_exceptions import (
|
|
36
43
|
DatabaseConnectionError,
|
|
37
44
|
DatabaseError,
|
|
@@ -156,6 +163,12 @@ from .security_exceptions import (
|
|
|
156
163
|
)
|
|
157
164
|
|
|
158
165
|
__all__ = [
|
|
166
|
+
# The root of the hierarchy: catches every operational exception the
|
|
167
|
+
# package raises.
|
|
168
|
+
"DataExceptError",
|
|
169
|
+
# Placeholders for state that could not survive serialization.
|
|
170
|
+
"UnpicklableCause",
|
|
171
|
+
"UnpicklableValue",
|
|
159
172
|
# Every exception class the package defines.
|
|
160
173
|
"ApiError",
|
|
161
174
|
"AuthenticationError",
|
|
@@ -261,12 +274,14 @@ __all__ = [
|
|
|
261
274
|
"log_exception",
|
|
262
275
|
"log_then_raise",
|
|
263
276
|
# Domain modules, for callers who prefer a qualified import.
|
|
277
|
+
# job_exceptions is deliberately absent: it is deprecated, and listing it
|
|
278
|
+
# makes `from dataexcept import *` emit a DeprecationWarning, which turns
|
|
279
|
+
# into an error under -W error::DeprecationWarning. It stays importable.
|
|
264
280
|
"database_exceptions",
|
|
265
281
|
"dataengineering_exceptions",
|
|
266
282
|
"datascience_exceptions",
|
|
267
283
|
"exceptions",
|
|
268
284
|
"io_exceptions",
|
|
269
|
-
"job_exceptions",
|
|
270
285
|
"logging_helpers",
|
|
271
286
|
"network_exceptions",
|
|
272
287
|
"pandas_exceptions",
|
|
@@ -21,11 +21,17 @@ _DEPRECATED_MODULES = frozenset({"dataexcept.job_exceptions"})
|
|
|
21
21
|
def _iter_exception_modules() -> Iterable[ModuleType]:
|
|
22
22
|
"""Yield every non-deprecated submodule that explicitly defines ``__all__``."""
|
|
23
23
|
allowed_suffixes = ("exceptions", "_exceptions")
|
|
24
|
+
# dataexcept.base holds DataExceptError, the root of the hierarchy, and
|
|
25
|
+
# does not match the suffix rule.
|
|
26
|
+
always_include = {"dataexcept.base"}
|
|
24
27
|
|
|
25
28
|
for module_info in pkgutil.walk_packages(
|
|
26
29
|
_PKG_PATH, prefix="dataexcept.", onerror=lambda name: None
|
|
27
30
|
):
|
|
28
|
-
if
|
|
31
|
+
if (
|
|
32
|
+
not module_info.name.endswith(allowed_suffixes)
|
|
33
|
+
and module_info.name not in always_include
|
|
34
|
+
):
|
|
29
35
|
continue
|
|
30
36
|
if module_info.name in _DEPRECATED_MODULES:
|
|
31
37
|
continue
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Small runtime checks shared by the exception constructors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numbers
|
|
6
|
+
|
|
7
|
+
__all__ = ["is_number"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def is_number(value: object) -> bool:
|
|
11
|
+
"""Return True for any real number, including NumPy scalars.
|
|
12
|
+
|
|
13
|
+
``isinstance(value, (int, float))`` rejects ``numpy.float32`` and
|
|
14
|
+
``numpy.int64``, which is a poor answer from a library aimed at data
|
|
15
|
+
science. ``numbers.Real`` accepts them because NumPy registers its scalar
|
|
16
|
+
types with the ABC, and it needs no dependency on NumPy to do so.
|
|
17
|
+
|
|
18
|
+
Booleans are excluded: ``bool`` subclasses ``int``, so ``numbers.Real``
|
|
19
|
+
would accept ``True`` as a metric.
|
|
20
|
+
|
|
21
|
+
This is a function rather than an inline ``isinstance`` because narrowing a
|
|
22
|
+
value to ``numbers.Real`` defeats mypy's inference for the rest of the
|
|
23
|
+
enclosing class.
|
|
24
|
+
"""
|
|
25
|
+
# bool subclasses int, so numbers.Real accepts True and False. A boolean
|
|
26
|
+
# is never a meaningful metric, threshold or ratio, and accepting one hides
|
|
27
|
+
# a caller passing the wrong variable.
|
|
28
|
+
return not isinstance(value, bool) and isinstance(value, numbers.Real)
|