DataExcept 0.4.2__tar.gz → 0.4.3__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.2 → dataexcept-0.4.3}/CHANGELOG.md +47 -1
- {dataexcept-0.4.2 → dataexcept-0.4.3}/CITATION.cff +1 -1
- {dataexcept-0.4.2 → dataexcept-0.4.3}/PKG-INFO +3 -3
- {dataexcept-0.4.2 → dataexcept-0.4.3}/README.md +2 -2
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/logging_helpers.py +56 -7
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/redaction.py +4 -1
- {dataexcept-0.4.2 → dataexcept-0.4.3}/pyproject.toml +2 -1
- {dataexcept-0.4.2 → dataexcept-0.4.3}/LICENSE +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/__init__.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/__main__.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/_deprecation.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/_validation.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/base.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/database_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/dataengineering_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/datascience_exceptions/__init__.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/datascience_exceptions/base.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/datascience_exceptions/ingestion.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/datascience_exceptions/operations.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/datascience_exceptions/training.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/__init__.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/authentication.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/base.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/configuration.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/external.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/lifecycle.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/notification.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/parsing.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/scheduling.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/exceptions/validation.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/io_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/job_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/network_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/pandas_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/pipeline_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/py.typed +0 -0
- {dataexcept-0.4.2 → dataexcept-0.4.3}/dataexcept/security_exceptions.py +0 -0
|
@@ -7,6 +7,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- The release workflow's `verify-wheel` job installed a hand-written list of
|
|
13
|
+
test requirements, which went stale the moment `hypothesis` was added — the
|
|
14
|
+
suite could not even be collected against the built wheel. It now installs
|
|
15
|
+
the dev group from the lock, so the list cannot drift again. The job did its
|
|
16
|
+
job: it failed, and publishing was skipped.
|
|
17
|
+
- `test_no_module_imports_tomllib_without_a_fallback` walked the whole tree, so
|
|
18
|
+
it picked up any virtualenv sitting in it. It now scans the project's own
|
|
19
|
+
source directories by name; site-packages is not ours to police.
|
|
20
|
+
|
|
21
|
+
## [0.4.3] - 2026-08-25
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Property-based tests over the logging helpers and the CLI: generated context
|
|
26
|
+
values, including hostile ones, must never make `log_exception` raise; the
|
|
27
|
+
normalised context must be strict-JSON encodable; `log_and_raise` must
|
|
28
|
+
re-raise the *same* exception with its traceback intact; and the CLI must
|
|
29
|
+
raise nothing but `SystemExit` for any argument list.
|
|
30
|
+
- **Property-based tests over the exception hierarchy**, using Hypothesis.
|
|
31
|
+
Generated text is fed to every class and four properties are asserted for
|
|
32
|
+
each: construction raises nothing but `TypeError`, a message built from real
|
|
33
|
+
input is non-empty, the exception survives a pickle round trip with its type,
|
|
34
|
+
message and cause intact, and a credential-bearing URL is never rendered.
|
|
35
|
+
Every constructor defect the reviews found — `MergeKeyError("id")` becoming
|
|
36
|
+
`['i', 'd']`, `is_number(True)`, and the one below — was in argument
|
|
37
|
+
handling and was found by inspection rather than by a test.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **`log_exception` could raise, replacing the caller's error with its own.**
|
|
42
|
+
A context value whose `__repr__` raises propagated straight out of the
|
|
43
|
+
logging helper — so a helper called while handling a failure made the failure
|
|
44
|
+
worse. Nothing in context normalisation can raise now; a value that cannot be
|
|
45
|
+
described at all becomes `<unrepresentable TypeName>`.
|
|
46
|
+
- **`NaN` and `Infinity` in a log context produced invalid JSON.**
|
|
47
|
+
`json.dumps` emits them bare, which a strict consumer downstream rejects.
|
|
48
|
+
They are stringified instead.
|
|
49
|
+
- `ApiError`, `DatabaseConnectionError` and `WebhookError` raised
|
|
50
|
+
`AttributeError: 'object' object has no attribute 'decode'` when given a
|
|
51
|
+
non-string, because the redaction helpers passed whatever they were given to
|
|
52
|
+
`urlsplit`. That masked the caller's actual mistake with a message about
|
|
53
|
+
`.decode`. The helpers now return a non-string untouched.
|
|
54
|
+
|
|
10
55
|
## [0.4.2] - 2026-08-25
|
|
11
56
|
|
|
12
57
|
### Changed
|
|
@@ -429,7 +474,8 @@ First public release.
|
|
|
429
474
|
- Published to PyPI via OIDC trusted publishing; no long-lived API token is
|
|
430
475
|
involved in a release.
|
|
431
476
|
|
|
432
|
-
[Unreleased]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.
|
|
477
|
+
[Unreleased]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.3...HEAD
|
|
478
|
+
[0.4.3]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.2...v0.4.3
|
|
433
479
|
[0.4.2]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.1...v0.4.2
|
|
434
480
|
[0.4.1]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.0...v0.4.1
|
|
435
481
|
[0.4.0]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.3.0...v0.4.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: DataExcept
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: A Python package providing structured, easily-extendable custom exception types.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -225,7 +225,7 @@ BiasDetectionError
|
|
|
225
225
|
|
|
226
226
|
# Check version
|
|
227
227
|
$ dataexcept --version
|
|
228
|
-
dataexcept 0.4.
|
|
228
|
+
dataexcept 0.4.3
|
|
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.4.
|
|
378
|
+
version = {0.4.3},
|
|
379
379
|
year = {2026},
|
|
380
380
|
publisher = {GitHub}
|
|
381
381
|
}
|
|
@@ -198,7 +198,7 @@ BiasDetectionError
|
|
|
198
198
|
|
|
199
199
|
# Check version
|
|
200
200
|
$ dataexcept --version
|
|
201
|
-
dataexcept 0.4.
|
|
201
|
+
dataexcept 0.4.3
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
## 🎯 Use Cases
|
|
@@ -348,7 +348,7 @@ If you use DataExcept in your research, please cite it:
|
|
|
348
348
|
author = {Ribeiro, Diogo},
|
|
349
349
|
title = {DataExcept: Structured Exception Handling for Data Science},
|
|
350
350
|
url = {https://github.com/DiogoRibeiro7/DataExcept},
|
|
351
|
-
version = {0.4.
|
|
351
|
+
version = {0.4.3},
|
|
352
352
|
year = {2026},
|
|
353
353
|
publisher = {GitHub}
|
|
354
354
|
}
|
|
@@ -12,6 +12,9 @@ from .redaction import redact_urls_in_text
|
|
|
12
12
|
|
|
13
13
|
Context = Mapping[str, Any]
|
|
14
14
|
|
|
15
|
+
#: Sentinel: the value could not be coerced into anything JSON will take.
|
|
16
|
+
_UNCOERCIBLE = object()
|
|
17
|
+
|
|
15
18
|
__all__ = [
|
|
16
19
|
"Context",
|
|
17
20
|
"log_and_raise",
|
|
@@ -20,15 +23,61 @@ __all__ = [
|
|
|
20
23
|
]
|
|
21
24
|
|
|
22
25
|
|
|
23
|
-
def
|
|
26
|
+
def _is_json_safe(value: Any) -> bool:
|
|
27
|
+
"""True if a strict JSON encoder will accept *value* as it stands.
|
|
28
|
+
|
|
29
|
+
``allow_nan=False`` because ``json.dumps`` otherwise emits bare ``NaN`` and
|
|
30
|
+
``Infinity``, which are not valid JSON and will be rejected downstream.
|
|
31
|
+
"""
|
|
24
32
|
try:
|
|
25
|
-
json.dumps(value)
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
json.dumps(value, allow_nan=False)
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return False
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _coerced(value: Any) -> Any:
|
|
40
|
+
"""Round-trip *value* through JSON, stringifying whatever will not encode.
|
|
41
|
+
|
|
42
|
+
Returns ``_UNCOERCIBLE`` rather than raising: this runs while the caller is
|
|
43
|
+
already handling a failure.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
return json.loads(json.dumps(value, default=str, allow_nan=False))
|
|
47
|
+
except Exception:
|
|
48
|
+
return _UNCOERCIBLE
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _described(value: Any) -> str:
|
|
52
|
+
"""Describe *value* without letting it raise.
|
|
53
|
+
|
|
54
|
+
An object may define a ``__repr__`` that raises. Naming the type is the
|
|
55
|
+
most that can be said without invoking anything the object controls.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
return repr(value)
|
|
59
|
+
except Exception:
|
|
28
60
|
try:
|
|
29
|
-
return
|
|
30
|
-
except Exception: # pragma: no cover -
|
|
31
|
-
return
|
|
61
|
+
return f"<unrepresentable {type(value).__name__}>"
|
|
62
|
+
except Exception: # pragma: no cover - a type with a hostile __name__
|
|
63
|
+
return "<unrepresentable>"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _normalize_context_value(value: Any) -> Any:
|
|
67
|
+
"""Return *value* in a form a strict JSON log encoder will accept.
|
|
68
|
+
|
|
69
|
+
Nothing here may raise. This runs while the caller is already handling a
|
|
70
|
+
failure, and an exception escaping would replace their error with one about
|
|
71
|
+
logging it -- so even a hostile ``__repr__`` has to be survivable.
|
|
72
|
+
"""
|
|
73
|
+
if _is_json_safe(value):
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
coerced = _coerced(value)
|
|
77
|
+
if coerced is not _UNCOERCIBLE:
|
|
78
|
+
return coerced
|
|
79
|
+
|
|
80
|
+
return _described(value)
|
|
32
81
|
|
|
33
82
|
|
|
34
83
|
def _build_extra(context: Context | None) -> dict[str, Any] | None:
|
|
@@ -170,7 +170,10 @@ def redact_url(url: Optional[str], *, keep_path: bool = True) -> Optional[str]:
|
|
|
170
170
|
incoming webhook URL is the common case -- Slack, Discord and others put
|
|
171
171
|
the secret in the path, so preserving it would defeat the point.
|
|
172
172
|
"""
|
|
173
|
-
if not url:
|
|
173
|
+
if not url or not isinstance(url, str):
|
|
174
|
+
# Anything that is not a string is handed back untouched. urlsplit
|
|
175
|
+
# would raise AttributeError from inside urllib, masking whatever the
|
|
176
|
+
# caller's real mistake was with a message about `.decode`.
|
|
174
177
|
return url
|
|
175
178
|
|
|
176
179
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "DataExcept"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3"
|
|
4
4
|
description = "A Python package providing structured, easily-extendable custom exception types."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -43,6 +43,7 @@ include = [
|
|
|
43
43
|
black = "*"
|
|
44
44
|
coverage = "*"
|
|
45
45
|
flake8 = "*"
|
|
46
|
+
hypothesis = "*"
|
|
46
47
|
isort = "*"
|
|
47
48
|
mypy = "*"
|
|
48
49
|
pre-commit = "*"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|