DataExcept 0.4.2__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dataexcept-0.4.2 → dataexcept-1.0.0}/CHANGELOG.md +83 -1
- {dataexcept-0.4.2 → dataexcept-1.0.0}/CITATION.cff +1 -1
- {dataexcept-0.4.2 → dataexcept-1.0.0}/PKG-INFO +4 -3
- {dataexcept-0.4.2 → dataexcept-1.0.0}/README.md +3 -2
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/__init__.py +3 -24
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/__main__.py +1 -8
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/datascience_exceptions/__init__.py +0 -11
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/__init__.py +0 -13
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/logging_helpers.py +56 -7
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/pipeline_exceptions.py +0 -10
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/redaction.py +4 -1
- {dataexcept-0.4.2 → dataexcept-1.0.0}/pyproject.toml +2 -1
- dataexcept-0.4.2/dataexcept/_deprecation.py +0 -38
- dataexcept-0.4.2/dataexcept/job_exceptions.py +0 -71
- {dataexcept-0.4.2 → dataexcept-1.0.0}/LICENSE +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/_validation.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/base.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/database_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/dataengineering_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/datascience_exceptions/base.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/datascience_exceptions/ingestion.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/datascience_exceptions/operations.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/datascience_exceptions/training.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/authentication.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/base.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/configuration.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/external.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/lifecycle.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/notification.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/parsing.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/scheduling.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/exceptions/validation.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/io_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/network_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/pandas_exceptions.py +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/py.typed +0 -0
- {dataexcept-0.4.2 → dataexcept-1.0.0}/dataexcept/security_exceptions.py +0 -0
|
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.0] - 2026-08-25
|
|
11
|
+
|
|
12
|
+
First stable release. The public API is frozen; see the
|
|
13
|
+
[stability policy](https://diogoribeiro7.github.io/DataExcept/stability/)
|
|
14
|
+
and the [migration guide](https://diogoribeiro7.github.io/DataExcept/migration/).
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- A [migration guide](https://diogoribeiro7.github.io/DataExcept/migration/)
|
|
19
|
+
covering every rename between 0.1 and 1.0, with the one-line check for
|
|
20
|
+
whether any of it applies to you:
|
|
21
|
+
`python -W error::DeprecationWarning -m pytest` on 0.4.3.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **The public API is frozen.** The stability policy no longer carries a
|
|
26
|
+
pre-1.0 caveat: nothing public is renamed or removed outside a major release,
|
|
27
|
+
and anything that will be removed is deprecated first.
|
|
28
|
+
|
|
29
|
+
### Removed
|
|
30
|
+
|
|
31
|
+
- **`dataexcept.job_exceptions`**, deprecated since 0.1.0. Every name it
|
|
32
|
+
exported is available from `dataexcept.exceptions` and from the top-level
|
|
33
|
+
package, and they are the same class objects, so this is a change of import
|
|
34
|
+
line only.
|
|
35
|
+
- **The four aliases introduced in 0.2.0**: `ConnectionError`,
|
|
36
|
+
`TimeoutError`, `datascience_exceptions.SerializationError` and
|
|
37
|
+
`pipeline_exceptions.FeatureEngineeringError`. Each has emitted a
|
|
38
|
+
`DeprecationWarning` naming 1.0.0 as its removal since 0.2.0.
|
|
39
|
+
`SerializationError` and `FeatureEngineeringError` remain exported — the
|
|
40
|
+
rename applied to the *duplicate* in each pair, and the original kept its
|
|
41
|
+
name.
|
|
42
|
+
- The deprecation machinery itself (`dataexcept._deprecation`), which nothing
|
|
43
|
+
uses now.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- The release workflow's `verify-wheel` job installed a hand-written list of
|
|
48
|
+
test requirements, which went stale the moment `hypothesis` was added — the
|
|
49
|
+
suite could not even be collected against the built wheel. It now installs
|
|
50
|
+
the dev group from the lock, so the list cannot drift again. The job did its
|
|
51
|
+
job: it failed, and publishing was skipped.
|
|
52
|
+
- `test_no_module_imports_tomllib_without_a_fallback` walked the whole tree, so
|
|
53
|
+
it picked up any virtualenv sitting in it. It now scans the project's own
|
|
54
|
+
source directories by name; site-packages is not ours to police.
|
|
55
|
+
|
|
56
|
+
## [0.4.3] - 2026-08-25
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- Property-based tests over the logging helpers and the CLI: generated context
|
|
61
|
+
values, including hostile ones, must never make `log_exception` raise; the
|
|
62
|
+
normalised context must be strict-JSON encodable; `log_and_raise` must
|
|
63
|
+
re-raise the *same* exception with its traceback intact; and the CLI must
|
|
64
|
+
raise nothing but `SystemExit` for any argument list.
|
|
65
|
+
- **Property-based tests over the exception hierarchy**, using Hypothesis.
|
|
66
|
+
Generated text is fed to every class and four properties are asserted for
|
|
67
|
+
each: construction raises nothing but `TypeError`, a message built from real
|
|
68
|
+
input is non-empty, the exception survives a pickle round trip with its type,
|
|
69
|
+
message and cause intact, and a credential-bearing URL is never rendered.
|
|
70
|
+
Every constructor defect the reviews found — `MergeKeyError("id")` becoming
|
|
71
|
+
`['i', 'd']`, `is_number(True)`, and the one below — was in argument
|
|
72
|
+
handling and was found by inspection rather than by a test.
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- **`log_exception` could raise, replacing the caller's error with its own.**
|
|
77
|
+
A context value whose `__repr__` raises propagated straight out of the
|
|
78
|
+
logging helper — so a helper called while handling a failure made the failure
|
|
79
|
+
worse. Nothing in context normalisation can raise now; a value that cannot be
|
|
80
|
+
described at all becomes `<unrepresentable TypeName>`.
|
|
81
|
+
- **`NaN` and `Infinity` in a log context produced invalid JSON.**
|
|
82
|
+
`json.dumps` emits them bare, which a strict consumer downstream rejects.
|
|
83
|
+
They are stringified instead.
|
|
84
|
+
- `ApiError`, `DatabaseConnectionError` and `WebhookError` raised
|
|
85
|
+
`AttributeError: 'object' object has no attribute 'decode'` when given a
|
|
86
|
+
non-string, because the redaction helpers passed whatever they were given to
|
|
87
|
+
`urlsplit`. That masked the caller's actual mistake with a message about
|
|
88
|
+
`.decode`. The helpers now return a non-string untouched.
|
|
89
|
+
|
|
10
90
|
## [0.4.2] - 2026-08-25
|
|
11
91
|
|
|
12
92
|
### Changed
|
|
@@ -429,7 +509,9 @@ First public release.
|
|
|
429
509
|
- Published to PyPI via OIDC trusted publishing; no long-lived API token is
|
|
430
510
|
involved in a release.
|
|
431
511
|
|
|
432
|
-
[Unreleased]: https://github.com/DiogoRibeiro7/DataExcept/compare/
|
|
512
|
+
[Unreleased]: https://github.com/DiogoRibeiro7/DataExcept/compare/v1.0.0...HEAD
|
|
513
|
+
[1.0.0]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.3...v1.0.0
|
|
514
|
+
[0.4.3]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.2...v0.4.3
|
|
433
515
|
[0.4.2]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.1...v0.4.2
|
|
434
516
|
[0.4.1]: https://github.com/DiogoRibeiro7/DataExcept/compare/v0.4.0...v0.4.1
|
|
435
517
|
[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.
|
|
3
|
+
Version: 1.0.0
|
|
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.
|
|
228
|
+
dataexcept 1.0.0
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
## 🎯 Use Cases
|
|
@@ -362,6 +362,7 @@ through [SECURITY.md](SECURITY.md), not the public issue tracker.
|
|
|
362
362
|
- **Full Documentation**: [diogoribeiro7.github.io/DataExcept](https://diogoribeiro7.github.io/DataExcept/)
|
|
363
363
|
- **API Reference**: [API Docs](https://diogoribeiro7.github.io/DataExcept/api/)
|
|
364
364
|
- **API Stability**: [What is public and what may change](https://diogoribeiro7.github.io/DataExcept/stability/)
|
|
365
|
+
- **Upgrading from 0.x**: [Migration guide](https://diogoribeiro7.github.io/DataExcept/migration/)
|
|
365
366
|
- **Advanced Usage**: [Advanced Guide](https://diogoribeiro7.github.io/DataExcept/advanced_usage/)
|
|
366
367
|
- **CLI Reference**: [CLI Guide](https://diogoribeiro7.github.io/DataExcept/cli/)
|
|
367
368
|
- **Changelog**: [CHANGELOG.md](CHANGELOG.md)
|
|
@@ -375,7 +376,7 @@ If you use DataExcept in your research, please cite it:
|
|
|
375
376
|
author = {Ribeiro, Diogo},
|
|
376
377
|
title = {DataExcept: Structured Exception Handling for Data Science},
|
|
377
378
|
url = {https://github.com/DiogoRibeiro7/DataExcept},
|
|
378
|
-
version = {0.
|
|
379
|
+
version = {1.0.0},
|
|
379
380
|
year = {2026},
|
|
380
381
|
publisher = {GitHub}
|
|
381
382
|
}
|
|
@@ -198,7 +198,7 @@ BiasDetectionError
|
|
|
198
198
|
|
|
199
199
|
# Check version
|
|
200
200
|
$ dataexcept --version
|
|
201
|
-
dataexcept 0.
|
|
201
|
+
dataexcept 1.0.0
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
## 🎯 Use Cases
|
|
@@ -335,6 +335,7 @@ through [SECURITY.md](SECURITY.md), not the public issue tracker.
|
|
|
335
335
|
- **Full Documentation**: [diogoribeiro7.github.io/DataExcept](https://diogoribeiro7.github.io/DataExcept/)
|
|
336
336
|
- **API Reference**: [API Docs](https://diogoribeiro7.github.io/DataExcept/api/)
|
|
337
337
|
- **API Stability**: [What is public and what may change](https://diogoribeiro7.github.io/DataExcept/stability/)
|
|
338
|
+
- **Upgrading from 0.x**: [Migration guide](https://diogoribeiro7.github.io/DataExcept/migration/)
|
|
338
339
|
- **Advanced Usage**: [Advanced Guide](https://diogoribeiro7.github.io/DataExcept/advanced_usage/)
|
|
339
340
|
- **CLI Reference**: [CLI Guide](https://diogoribeiro7.github.io/DataExcept/cli/)
|
|
340
341
|
- **Changelog**: [CHANGELOG.md](CHANGELOG.md)
|
|
@@ -348,7 +349,7 @@ If you use DataExcept in your research, please cite it:
|
|
|
348
349
|
author = {Ribeiro, Diogo},
|
|
349
350
|
title = {DataExcept: Structured Exception Handling for Data Science},
|
|
350
351
|
url = {https://github.com/DiogoRibeiro7/DataExcept},
|
|
351
|
-
version = {0.
|
|
352
|
+
version = {1.0.0},
|
|
352
353
|
year = {2026},
|
|
353
354
|
publisher = {GitHub}
|
|
354
355
|
}
|
|
@@ -4,8 +4,8 @@ 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
|
|
8
|
-
this package raises::
|
|
7
|
+
They all derive from :class:`DataExceptError`, so one clause catches every
|
|
8
|
+
operational exception this package raises::
|
|
9
9
|
|
|
10
10
|
except DataExceptError:
|
|
11
11
|
...
|
|
@@ -22,8 +22,7 @@ try: # Python >=3.11
|
|
|
22
22
|
except ModuleNotFoundError: # pragma: no cover - fallback for Python <3.11
|
|
23
23
|
import tomli as tomllib # type: ignore
|
|
24
24
|
|
|
25
|
-
from importlib import
|
|
26
|
-
from typing import Any
|
|
25
|
+
from importlib import metadata
|
|
27
26
|
|
|
28
27
|
from . import ( # noqa: F401
|
|
29
28
|
database_exceptions,
|
|
@@ -37,7 +36,6 @@ from . import ( # noqa: F401
|
|
|
37
36
|
pipeline_exceptions,
|
|
38
37
|
security_exceptions,
|
|
39
38
|
)
|
|
40
|
-
from ._deprecation import resolve_deprecated
|
|
41
39
|
from .base import DataExceptError, UnpicklableCause, UnpicklableValue
|
|
42
40
|
from .database_exceptions import (
|
|
43
41
|
DatabaseConnectionError,
|
|
@@ -274,9 +272,6 @@ __all__ = [
|
|
|
274
272
|
"log_exception",
|
|
275
273
|
"log_then_raise",
|
|
276
274
|
# 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.
|
|
280
275
|
"database_exceptions",
|
|
281
276
|
"dataengineering_exceptions",
|
|
282
277
|
"datascience_exceptions",
|
|
@@ -295,19 +290,3 @@ except metadata.PackageNotFoundError: # pragma: no cover - fallback during dev
|
|
|
295
290
|
_root = Path(__file__).resolve().parents[1]
|
|
296
291
|
with open(_root / "pyproject.toml", "rb") as _f:
|
|
297
292
|
__version__ = tomllib.load(_f)["project"]["version"]
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
#: Renamed in 0.2.0 because they shadowed Python builtins without inheriting
|
|
301
|
-
#: from them. Each alias is the same class object as its replacement.
|
|
302
|
-
_DEPRECATED_ALIASES = {
|
|
303
|
-
"ConnectionError": ServiceConnectionError,
|
|
304
|
-
"TimeoutError": OperationTimeoutError,
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def __getattr__(name: str) -> Any:
|
|
309
|
-
if name == "job_exceptions":
|
|
310
|
-
module = import_module("dataexcept.job_exceptions")
|
|
311
|
-
globals()[name] = module
|
|
312
|
-
return module
|
|
313
|
-
return resolve_deprecated(__name__, _DEPRECATED_ALIASES, name)
|
|
@@ -12,14 +12,9 @@ from typing import Iterable
|
|
|
12
12
|
from . import __path__ as _PKG_PATH
|
|
13
13
|
from . import __version__
|
|
14
14
|
|
|
15
|
-
#: Deprecated compatibility shims. Listing these would advertise names that are
|
|
16
|
-
#: scheduled for removal, and importing them emits a DeprecationWarning at
|
|
17
|
-
#: anyone who merely wanted to see what the package offers.
|
|
18
|
-
_DEPRECATED_MODULES = frozenset({"dataexcept.job_exceptions"})
|
|
19
|
-
|
|
20
15
|
|
|
21
16
|
def _iter_exception_modules() -> Iterable[ModuleType]:
|
|
22
|
-
"""Yield every
|
|
17
|
+
"""Yield every submodule that explicitly defines ``__all__``."""
|
|
23
18
|
allowed_suffixes = ("exceptions", "_exceptions")
|
|
24
19
|
# dataexcept.base holds DataExceptError, the root of the hierarchy, and
|
|
25
20
|
# does not match the suffix rule.
|
|
@@ -33,8 +28,6 @@ def _iter_exception_modules() -> Iterable[ModuleType]:
|
|
|
33
28
|
and module_info.name not in always_include
|
|
34
29
|
):
|
|
35
30
|
continue
|
|
36
|
-
if module_info.name in _DEPRECATED_MODULES:
|
|
37
|
-
continue
|
|
38
31
|
try:
|
|
39
32
|
module = import_module(module_info.name)
|
|
40
33
|
except ImportError as exc: # pragma: no cover - defensive guard
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"""Custom exceptions for data science workflows."""
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from .._deprecation import resolve_deprecated
|
|
6
3
|
from .base import DataScienceError
|
|
7
4
|
from .ingestion import (
|
|
8
5
|
DataAugmentationError,
|
|
@@ -86,11 +83,3 @@ __all__ = [
|
|
|
86
83
|
"FeatureScalingError",
|
|
87
84
|
"ModelCompatibilityError",
|
|
88
85
|
]
|
|
89
|
-
|
|
90
|
-
#: Renamed in 0.2.0: this named the same thing as
|
|
91
|
-
#: ``dataexcept.exceptions.SerializationError`` while being a different class.
|
|
92
|
-
_DEPRECATED_ALIASES = {"SerializationError": ModelSerializationError}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def __getattr__(name: str) -> Any:
|
|
96
|
-
return resolve_deprecated(__name__, _DEPRECATED_ALIASES, name)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# __init__.py
|
|
2
|
-
from typing import Any
|
|
3
2
|
|
|
4
|
-
from .._deprecation import resolve_deprecated
|
|
5
3
|
from .authentication import AuthenticationError, AuthorizationError
|
|
6
4
|
from .base import JobError
|
|
7
5
|
from .configuration import ConfigurationError
|
|
@@ -37,14 +35,3 @@ __all__ = [
|
|
|
37
35
|
"WebhookError",
|
|
38
36
|
"JobCancellationError",
|
|
39
37
|
]
|
|
40
|
-
|
|
41
|
-
#: Renamed in 0.2.0 because they shadowed Python builtins without inheriting
|
|
42
|
-
#: from them. The alias is the same class object, so ``except`` keeps working.
|
|
43
|
-
_DEPRECATED_ALIASES = {
|
|
44
|
-
"ConnectionError": ServiceConnectionError,
|
|
45
|
-
"TimeoutError": OperationTimeoutError,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def __getattr__(name: str) -> Any:
|
|
50
|
-
return resolve_deprecated(__name__, _DEPRECATED_ALIASES, name)
|
|
@@ -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:
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
from ._deprecation import resolve_deprecated
|
|
8
7
|
from .base import DataExceptError
|
|
9
8
|
from .redaction import redact_if_url, redact_url
|
|
10
9
|
|
|
@@ -210,12 +209,3 @@ __all__ = [
|
|
|
210
209
|
"TypeCheckError",
|
|
211
210
|
"DataFetchError",
|
|
212
211
|
]
|
|
213
|
-
|
|
214
|
-
#: Renamed in 0.2.0: this named the same thing as
|
|
215
|
-
#: ``dataexcept.datascience_exceptions.FeatureEngineeringError`` while being a
|
|
216
|
-
#: different class, so catching one silently missed the other.
|
|
217
|
-
_DEPRECATED_ALIASES = {"FeatureEngineeringError": FeaturePreprocessingError}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
def __getattr__(name: str) -> Any:
|
|
221
|
-
return resolve_deprecated(__name__, _DEPRECATED_ALIASES, name)
|
|
@@ -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.
|
|
3
|
+
version = "1.0.0"
|
|
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 = "*"
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"""Internal support for names that have been renamed.
|
|
2
|
-
|
|
3
|
-
A rename keeps the old name working as an alias bound to the *same* class
|
|
4
|
-
object, so an existing ``except OldName:`` keeps catching exactly what it
|
|
5
|
-
caught before -- the alias is not a separate class.
|
|
6
|
-
|
|
7
|
-
Access goes through a PEP 562 module ``__getattr__`` rather than a plain
|
|
8
|
-
``OldName = NewName`` assignment, because an assignment cannot warn. This is
|
|
9
|
-
private; see the stability policy for the user-facing contract.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import warnings
|
|
15
|
-
from typing import Any, Mapping
|
|
16
|
-
|
|
17
|
-
#: Version in which every currently deprecated alias is removed.
|
|
18
|
-
REMOVED_IN = "1.0.0"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def resolve_deprecated(module: str, aliases: Mapping[str, Any], name: str) -> Any:
|
|
22
|
-
"""Return the target of the deprecated *name*, warning first.
|
|
23
|
-
|
|
24
|
-
Raises ``AttributeError`` for anything not in *aliases*, so normal
|
|
25
|
-
attribute lookup on the module keeps behaving normally.
|
|
26
|
-
"""
|
|
27
|
-
try:
|
|
28
|
-
target = aliases[name]
|
|
29
|
-
except KeyError:
|
|
30
|
-
raise AttributeError(f"module {module!r} has no attribute {name!r}") from None
|
|
31
|
-
warnings.warn(
|
|
32
|
-
f"{module}.{name} is deprecated and will be removed in {REMOVED_IN}; "
|
|
33
|
-
f"use {target.__name__} instead",
|
|
34
|
-
DeprecationWarning,
|
|
35
|
-
# resolve_deprecated -> __getattr__ -> the caller we want to blame.
|
|
36
|
-
stacklevel=3,
|
|
37
|
-
)
|
|
38
|
-
return target
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"""Backward compatible job-related exceptions.
|
|
2
|
-
|
|
3
|
-
.. deprecated:: 0.1.0
|
|
4
|
-
Use :mod:`dataexcept.exceptions` instead. This module is scheduled for
|
|
5
|
-
removal in 1.0.0; see the stability policy in the documentation.
|
|
6
|
-
|
|
7
|
-
This module re-exports the core job exceptions defined in
|
|
8
|
-
:mod:`dataexcept.exceptions`. Importing it emits a :class:`DeprecationWarning`.
|
|
9
|
-
Every name it exports is available from :mod:`dataexcept.exceptions` and from
|
|
10
|
-
the top-level :mod:`dataexcept` package, so the migration is a change of import
|
|
11
|
-
line only -- the classes are identical objects, not replacements, so existing
|
|
12
|
-
``except`` clauses keep working during the transition.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
|
-
import warnings
|
|
18
|
-
|
|
19
|
-
from .exceptions import (
|
|
20
|
-
AuthenticationError,
|
|
21
|
-
AuthorizationError,
|
|
22
|
-
ConfigurationError,
|
|
23
|
-
CronExpressionError,
|
|
24
|
-
DependencyError,
|
|
25
|
-
DeserializationError,
|
|
26
|
-
EmailError,
|
|
27
|
-
JobCancellationError,
|
|
28
|
-
JobError,
|
|
29
|
-
NotificationError,
|
|
30
|
-
OperationTimeoutError,
|
|
31
|
-
ParsingError,
|
|
32
|
-
ResourceNotFoundError,
|
|
33
|
-
ScheduleConflictError,
|
|
34
|
-
SerializationError,
|
|
35
|
-
ServiceConnectionError,
|
|
36
|
-
ValidationError,
|
|
37
|
-
WebhookError,
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
# This module is itself deprecated, so it binds the pre-0.2.0 names directly
|
|
41
|
-
# rather than going through the alias machinery and warning a second time.
|
|
42
|
-
ConnectionError = ServiceConnectionError
|
|
43
|
-
TimeoutError = OperationTimeoutError
|
|
44
|
-
|
|
45
|
-
__all__ = [
|
|
46
|
-
"JobError",
|
|
47
|
-
"ValidationError",
|
|
48
|
-
"ConfigurationError",
|
|
49
|
-
"ConnectionError",
|
|
50
|
-
"TimeoutError",
|
|
51
|
-
"ResourceNotFoundError",
|
|
52
|
-
"DependencyError",
|
|
53
|
-
"AuthenticationError",
|
|
54
|
-
"AuthorizationError",
|
|
55
|
-
"ParsingError",
|
|
56
|
-
"SerializationError",
|
|
57
|
-
"DeserializationError",
|
|
58
|
-
"ScheduleConflictError",
|
|
59
|
-
"CronExpressionError",
|
|
60
|
-
"NotificationError",
|
|
61
|
-
"EmailError",
|
|
62
|
-
"WebhookError",
|
|
63
|
-
"JobCancellationError",
|
|
64
|
-
]
|
|
65
|
-
|
|
66
|
-
warnings.warn(
|
|
67
|
-
"dataexcept.job_exceptions is deprecated and will be removed in 1.0.0; "
|
|
68
|
-
"use dataexcept.exceptions instead",
|
|
69
|
-
DeprecationWarning,
|
|
70
|
-
stacklevel=2,
|
|
71
|
-
)
|
|
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
|