simplibs-exception 0.1.0__py3-none-any.whl
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.
- simplibs/exception/__init__.py +41 -0
- simplibs/exception/core/__init__.py +20 -0
- simplibs/exception/core/_internal_exception/SimpleExceptionInternalError.py +91 -0
- simplibs/exception/core/_internal_exception/__init__.py +16 -0
- simplibs/exception/core/data/SimpleExceptionData.py +114 -0
- simplibs/exception/core/data/__init__.py +15 -0
- simplibs/exception/exception/SimpleException.py +220 -0
- simplibs/exception/exception/__init__.py +27 -0
- simplibs/exception/exception/_mixins/__init__.py +43 -0
- simplibs/exception/exception/_mixins/dunders/InitSubclass.py +60 -0
- simplibs/exception/exception/_mixins/dunders/New.py +136 -0
- simplibs/exception/exception/_mixins/dunders/__init__.py +15 -0
- simplibs/exception/exception/_mixins/dunders/_utils/__init__.py +14 -0
- simplibs/exception/exception/_mixins/dunders/_utils/check_children_class_attributes.py +143 -0
- simplibs/exception/exception/_mixins/normalizers/NormalizeParam.py +61 -0
- simplibs/exception/exception/_mixins/normalizers/ProcessExceptionParam.py +85 -0
- simplibs/exception/exception/_mixins/normalizers/ProcessGetLocationParam.py +57 -0
- simplibs/exception/exception/_mixins/normalizers/ProcessHowToFixParam.py +80 -0
- simplibs/exception/exception/_mixins/normalizers/ProcessSkipLocationsParam.py +67 -0
- simplibs/exception/exception/_mixins/normalizers/__init__.py +23 -0
- simplibs/exception/exception/_mixins/serializers/ToDebugDict.py +88 -0
- simplibs/exception/exception/_mixins/serializers/ToDict.py +52 -0
- simplibs/exception/exception/_mixins/serializers/ToJson.py +38 -0
- simplibs/exception/exception/_mixins/serializers/__init__.py +17 -0
- simplibs/exception/modes/LOG.py +105 -0
- simplibs/exception/modes/ONELINE.py +94 -0
- simplibs/exception/modes/PRETTY.py +150 -0
- simplibs/exception/modes/SIMPLE.py +105 -0
- simplibs/exception/modes/__init__.py +32 -0
- simplibs/exception/modes/base_class/ModeBase.py +192 -0
- simplibs/exception/modes/base_class/__init__.py +13 -0
- simplibs/exception/modes/base_class/_mixins/PrintCallerInfo.py +84 -0
- simplibs/exception/modes/base_class/_mixins/PrintIntroLine.py +46 -0
- simplibs/exception/modes/base_class/_mixins/PrintValueWithType.py +56 -0
- simplibs/exception/modes/base_class/_mixins/__init__.py +18 -0
- simplibs/exception/modes/base_class/_validations/SimpleExceptionModeError.py +30 -0
- simplibs/exception/modes/base_class/_validations/__init__.py +17 -0
- simplibs/exception/modes/base_class/_validations/validate_has_simple_exception_data.py +44 -0
- simplibs/exception/settings/SimpleExceptionSettings.py +115 -0
- simplibs/exception/settings/__init__.py +23 -0
- simplibs/exception/settings/_meta/SimpleExceptionSettingsMeta.py +76 -0
- simplibs/exception/settings/_meta/__init__.py +16 -0
- simplibs/exception/settings/_meta/validations/SimpleExceptionSettingsError.py +30 -0
- simplibs/exception/settings/_meta/validations/__init__.py +22 -0
- simplibs/exception/settings/_meta/validations/validate_dynamic_cls_cache.py +33 -0
- simplibs/exception/settings/_meta/validations/validate_get_location.py +26 -0
- simplibs/exception/settings/_meta/validations/validate_location_blacklist.py +48 -0
- simplibs/exception/settings/_meta/validations/validate_message_mode.py +28 -0
- simplibs/exception/utils/__init__.py +22 -0
- simplibs/exception/utils/bool_or_exception.py +66 -0
- simplibs/exception/utils/extract_caller_info.py +130 -0
- simplibs_exception-0.1.0.dist-info/METADATA +506 -0
- simplibs_exception-0.1.0.dist-info/RECORD +56 -0
- simplibs_exception-0.1.0.dist-info/WHEEL +5 -0
- simplibs_exception-0.1.0.dist-info/licenses/LICENSE +21 -0
- simplibs_exception-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from .exception import SimpleException
|
|
2
|
+
from .utils import bool_or_exception, extract_caller_info
|
|
3
|
+
from .modes import PRETTY, SIMPLE, ONELINE, LOG, ModeBase
|
|
4
|
+
from .settings import SimpleExceptionSettings, SimpleExceptionSettingsError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
# Core class
|
|
9
|
+
"SimpleException",
|
|
10
|
+
# Utils
|
|
11
|
+
"bool_or_exception",
|
|
12
|
+
"extract_caller_info",
|
|
13
|
+
# Modes
|
|
14
|
+
"PRETTY",
|
|
15
|
+
"SIMPLE",
|
|
16
|
+
"ONELINE",
|
|
17
|
+
"LOG",
|
|
18
|
+
"ModeBase",
|
|
19
|
+
# Settings
|
|
20
|
+
"SimpleExceptionSettings",
|
|
21
|
+
"SimpleExceptionSettingsError",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_DESIGN_NOTES = """
|
|
26
|
+
# simple_exception
|
|
27
|
+
|
|
28
|
+
## Public API
|
|
29
|
+
| Name | Description |
|
|
30
|
+
|--------------------------------|-------------------------------------------------------------------|
|
|
31
|
+
| `SimpleException` | Core class — the foundation for all exceptions in the ecosystem |
|
|
32
|
+
| `bool_or_exception` | Shortcut for conditional exception raising |
|
|
33
|
+
| `extract_caller_info` | Utility for retrieving call site information from the stack |
|
|
34
|
+
| `PRETTY` | Default mode — structured output with separator lines |
|
|
35
|
+
| `SIMPLE` | Plain text output without decorations |
|
|
36
|
+
| `ONELINE` | Compact single-line output |
|
|
37
|
+
| `LOG` | Key=value format for log parsers |
|
|
38
|
+
| `ModeBase` | Base class for custom modes |
|
|
39
|
+
| `SimpleExceptionSettings` | Central configuration of the library |
|
|
40
|
+
| `SimpleExceptionSettingsError` | Exception for errors when changing settings |
|
|
41
|
+
"""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .data import SimpleExceptionData
|
|
2
|
+
from ._internal_exception import SimpleExceptionInternalError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
_DESIGN_NOTES = """
|
|
6
|
+
# core
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
The data layer of the library — defines the exception structure and the base
|
|
10
|
+
internal exception that consumes this structure.
|
|
11
|
+
|
|
12
|
+
| Name | Description |
|
|
13
|
+
|--------------------------------|-----------------------------------------------------------|
|
|
14
|
+
| `SimpleExceptionData` | Data class — exception structure and default values |
|
|
15
|
+
| `SimpleExceptionInternalError` | Base internal exception — foundation for grouped exceptions|
|
|
16
|
+
|
|
17
|
+
## Note
|
|
18
|
+
This package has no dependencies on the rest of the library — it is completely
|
|
19
|
+
isolated. Everything else draws from it; nothing points back into it.
|
|
20
|
+
"""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
# Commons
|
|
3
|
+
from ..data import SimpleExceptionData
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class SimpleExceptionInternalError(SimpleExceptionData, Exception):
|
|
8
|
+
"""Internal library exception — no validation, direct output only."""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Available attributes:
|
|
12
|
+
error_name: str = "INTERNAL ERROR"
|
|
13
|
+
# value: object = UNSET
|
|
14
|
+
# value_label: str = UNSET
|
|
15
|
+
# expected: str = UNSET
|
|
16
|
+
# problem: str = UNSET
|
|
17
|
+
# context: str = UNSET
|
|
18
|
+
# how_to_fix: tuple[str, ...] = UNSET
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Build the message:
|
|
22
|
+
def __post_init__(self):
|
|
23
|
+
|
|
24
|
+
# 1. Render the message
|
|
25
|
+
from ...modes import PRETTY
|
|
26
|
+
rendered_message = PRETTY(self, validate=False)
|
|
27
|
+
|
|
28
|
+
# 2. Pass the message to the exception
|
|
29
|
+
# Instead of super().__init__ we write directly to Exception.args —
|
|
30
|
+
# this ensures Exception(message) works correctly without overwriting our data.
|
|
31
|
+
Exception.__init__(self, rendered_message)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_DESIGN_NOTES = """
|
|
35
|
+
# SimpleExceptionInternalError
|
|
36
|
+
|
|
37
|
+
## Purpose
|
|
38
|
+
The base internal exception of the library — completely isolated from
|
|
39
|
+
`SimpleException` logic. Inherits directly from `SimpleExceptionData`
|
|
40
|
+
and `Exception`, with no dependency on the rest of the library beyond
|
|
41
|
+
the `PRETTY` mode.
|
|
42
|
+
|
|
43
|
+
## Isolation and lazy import
|
|
44
|
+
`PRETTY` is loaded lazily inside `__post_init__` to avoid a circular import —
|
|
45
|
+
`SimpleExceptionInternalError` lives in `core/_internal_exception` and a
|
|
46
|
+
standard top-level import of `modes` would pull it back in. The lazy import
|
|
47
|
+
breaks this cycle and loads the mode only when it is actually needed.
|
|
48
|
+
|
|
49
|
+
`PRETTY` is called with `validate=False` for two reasons:
|
|
50
|
+
- This is an internal call where the data is already guaranteed to be correct —
|
|
51
|
+
validation is unnecessary.
|
|
52
|
+
- Validation would re-import `modes` and cause a circular import even through
|
|
53
|
+
the lazy import.
|
|
54
|
+
|
|
55
|
+
`PRETTY` is hardcoded — a deliberate choice for reliability. Internal library
|
|
56
|
+
exceptions must produce consistent output regardless of the active settings
|
|
57
|
+
configuration. In the very situations these exceptions handle (misconfiguration,
|
|
58
|
+
logic errors in modes) the settings may be unreliable or only partially
|
|
59
|
+
initialised.
|
|
60
|
+
|
|
61
|
+
## Group base class
|
|
62
|
+
Serves as the base for grouped internal library exceptions:
|
|
63
|
+
```python
|
|
64
|
+
class SimpleExceptionSettingsError(SimpleExceptionInternalError):
|
|
65
|
+
error_name: str = "SETTINGS ERROR"
|
|
66
|
+
```
|
|
67
|
+
Grouped exceptions can be caught together:
|
|
68
|
+
```python
|
|
69
|
+
except SimpleExceptionInternalError:
|
|
70
|
+
...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Attributes to define
|
|
74
|
+
A full description of all attributes is in `SimpleExceptionData._DESIGN_NOTES`.
|
|
75
|
+
For internal exceptions, only the following are typically needed:
|
|
76
|
+
|
|
77
|
+
| Attribute | Description |
|
|
78
|
+
|---------------|-------------------------------------------------------------------|
|
|
79
|
+
| `error_name` | Exception group name — displayed as the heading in the output |
|
|
80
|
+
| `value` | The value that caused the exception |
|
|
81
|
+
| `value_label` | Label for the value — e.g. the attribute or parameter name |
|
|
82
|
+
| `expected` | What was expected |
|
|
83
|
+
| `problem` | What is wrong |
|
|
84
|
+
| `context` | Additional context — only include if it adds meaningful information|
|
|
85
|
+
| `how_to_fix` | Tips on how to resolve the error |
|
|
86
|
+
|
|
87
|
+
## Commented-out attributes
|
|
88
|
+
The commented-out attributes in the class definition serve as a quick
|
|
89
|
+
declarative reference — without reading the documentation it is immediately
|
|
90
|
+
clear which attributes are meaningful to define.
|
|
91
|
+
"""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .SimpleExceptionInternalError import SimpleExceptionInternalError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_DESIGN_NOTES = """
|
|
5
|
+
# core/_internal_exception
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
The base internal exception of the library — placed here because it inherits
|
|
9
|
+
directly from `SimpleExceptionData` and is therefore a natural part of the
|
|
10
|
+
data layer. Serves as the foundation for all grouped internal exceptions
|
|
11
|
+
in the library.
|
|
12
|
+
|
|
13
|
+
| Name | Description |
|
|
14
|
+
|--------------------------------|-------------------------------------------------------|
|
|
15
|
+
| `SimpleExceptionInternalError` | Base internal exception — foundation for other groups |
|
|
16
|
+
"""
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from simplibs.sentinels import UNSET, UnsetType
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class SimpleExceptionData:
|
|
7
|
+
"""Data class defining the structure and default values of SimpleException."""
|
|
8
|
+
|
|
9
|
+
# --- Core exception info ---
|
|
10
|
+
error_name: str = "ERROR"
|
|
11
|
+
exception: type[Exception] | UnsetType = UNSET
|
|
12
|
+
_intercepted_exception: str | UnsetType = UNSET
|
|
13
|
+
|
|
14
|
+
# --- Info about the inspected value ---
|
|
15
|
+
value: object = UNSET
|
|
16
|
+
value_label: str | UnsetType = UNSET
|
|
17
|
+
|
|
18
|
+
# --- Exception description ---
|
|
19
|
+
expected: str | UnsetType = UNSET
|
|
20
|
+
problem: str | UnsetType = UNSET
|
|
21
|
+
context: str | UnsetType = UNSET
|
|
22
|
+
message: str | UnsetType = UNSET
|
|
23
|
+
|
|
24
|
+
# --- How to fix ---
|
|
25
|
+
how_to_fix: tuple[str, ...] | UnsetType = UNSET
|
|
26
|
+
|
|
27
|
+
# --- Location info ---
|
|
28
|
+
_get_location: int | bool = True
|
|
29
|
+
_skip_locations: tuple[str, ...] = ()
|
|
30
|
+
|
|
31
|
+
# --- Single-line output ---
|
|
32
|
+
_oneline: bool = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
_DESIGN_NOTES = """
|
|
36
|
+
# SimpleExceptionData
|
|
37
|
+
|
|
38
|
+
## Purpose
|
|
39
|
+
A data class defining the structure, default values, and interface of `SimpleException`.
|
|
40
|
+
Completely isolated from the rest of the library — it has no dependencies on
|
|
41
|
+
`SimpleExceptionSettings` or any other library class. This makes it a shared
|
|
42
|
+
foundation for:
|
|
43
|
+
|
|
44
|
+
1. **`SimpleException`** — subclasses can override public attributes
|
|
45
|
+
to change default behaviour.
|
|
46
|
+
2. **Internal exceptions** — `SimpleExceptionInternalError` and its subclasses
|
|
47
|
+
share the same structure without depending on `SimpleException` logic.
|
|
48
|
+
3. **Data protocol for modes** — `ModeBase` expects data with this structure.
|
|
49
|
+
Custom modes can optionally validate input via `validate=True`.
|
|
50
|
+
|
|
51
|
+
## Underscore convention
|
|
52
|
+
- **Without underscore** — input parameters that the user can set either as
|
|
53
|
+
`__init__` arguments or as class-level attributes on subclasses.
|
|
54
|
+
- **With underscore** (`_intercepted_exception`, `_get_location`, etc.) —
|
|
55
|
+
values that are computed or managed automatically by the system.
|
|
56
|
+
The user should never set these directly.
|
|
57
|
+
|
|
58
|
+
## Attribute reference
|
|
59
|
+
|
|
60
|
+
### Core info
|
|
61
|
+
| Attribute | Default | Description |
|
|
62
|
+
|-------------------------|-----------|------------------------------------------------------|
|
|
63
|
+
| `error_name` | `"ERROR"` | Error name displayed in the exception output |
|
|
64
|
+
| `exception` | `UNSET` | Exception class dynamically added to the MRO |
|
|
65
|
+
| `_intercepted_exception`| `UNSET` | Description of a caught exception — set automatically|
|
|
66
|
+
|
|
67
|
+
### Inspected value
|
|
68
|
+
| Attribute | Default | Description |
|
|
69
|
+
|---------------|---------|-----------------------------------------------------------|
|
|
70
|
+
| `value` | `UNSET` | The value that caused the exception |
|
|
71
|
+
| `value_label` | `UNSET` | Human-readable label for the value (e.g. `"parameter age"`) |
|
|
72
|
+
|
|
73
|
+
### Exception description
|
|
74
|
+
| Attribute | Default | Description |
|
|
75
|
+
|------------|---------|------------------------------------------------------------------|
|
|
76
|
+
| `expected` | `UNSET` | What was expected (e.g. `"a positive integer"`) |
|
|
77
|
+
| `problem` | `UNSET` | What is wrong (e.g. `"value is negative"`) |
|
|
78
|
+
| `context` | `UNSET` | Broader context — only include if it adds meaningful information |
|
|
79
|
+
| `message` | `UNSET` | Free-form message — an alternative to the structured parameters |
|
|
80
|
+
|
|
81
|
+
### Location
|
|
82
|
+
| Attribute | Default | Description |
|
|
83
|
+
|-------------------|---------|---------------------------------------------------------------------|
|
|
84
|
+
| `_get_location` | `True` | Enable/disable location reporting, or set stack depth |
|
|
85
|
+
| `_skip_locations` | `()` | Strings matched against file paths — a match causes the frame to be skipped |
|
|
86
|
+
|
|
87
|
+
### How to fix
|
|
88
|
+
| Attribute | Default | Description |
|
|
89
|
+
|--------------|---------|---------------------------------------------------------------|
|
|
90
|
+
| `how_to_fix` | `UNSET` | Tips on how to resolve the error — displayed in the output |
|
|
91
|
+
|
|
92
|
+
### Output format
|
|
93
|
+
| Attribute | Default | Description |
|
|
94
|
+
|------------|---------|--------------------------------------------------------------------------|
|
|
95
|
+
| `_oneline` | `False` | When `True`, overrides the active mode and prints the exception on a single line. Useful when a specific call site needs compact output regardless of the configured default mode. |
|
|
96
|
+
|
|
97
|
+
## Overriding defaults on a subclass
|
|
98
|
+
```python
|
|
99
|
+
class MyError(SimpleException):
|
|
100
|
+
error_name = "MY_ERROR"
|
|
101
|
+
expected = "a positive integer"
|
|
102
|
+
how_to_fix = "Provide a value greater than 0."
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Notes
|
|
106
|
+
- The class itself contains no logic — it is purely declarative.
|
|
107
|
+
All logic lives in the mixins and in `SimpleException.__init__`.
|
|
108
|
+
- It serves as the data protocol for `ModeBase.render_message` — the data must
|
|
109
|
+
contain all attributes defined here (they may hold `UNSET`), otherwise
|
|
110
|
+
validation will fail. Internal calls always pass `validate=False`.
|
|
111
|
+
- Internal exceptions (`SimpleExceptionInternalError` and its subclasses)
|
|
112
|
+
inherit directly from this class — bypassing `SimpleException` logic
|
|
113
|
+
and avoiding any circular dependency.
|
|
114
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .SimpleExceptionData import SimpleExceptionData
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_DESIGN_NOTES = """
|
|
5
|
+
# core/data
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
The data layer of the library — contains the base structures defining the
|
|
9
|
+
shape of `SimpleException`. These classes contain no logic and serve as a
|
|
10
|
+
pure data contract between the individual parts of the system.
|
|
11
|
+
|
|
12
|
+
| Name | Description |
|
|
13
|
+
|-----------------------|-----------------------------------------------------------|
|
|
14
|
+
| `SimpleExceptionData` | Data class defining the structure and default values |
|
|
15
|
+
"""
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from simplibs.sentinels import UNSET, UnsetType
|
|
2
|
+
# Commons
|
|
3
|
+
from ..core import SimpleExceptionData
|
|
4
|
+
from ..settings import SimpleExceptionSettings as S
|
|
5
|
+
# Inners
|
|
6
|
+
from ._mixins.dunders import DunderInitSubclassMixin, DunderNewMixin
|
|
7
|
+
from ._mixins.serializers import ToDictMixin, ToDebugDictMixin, ToJsonMixin
|
|
8
|
+
from ._mixins.normalizers import (
|
|
9
|
+
NormalizeParamMixin,
|
|
10
|
+
ProcessExceptionParamMixin,
|
|
11
|
+
ProcessHowToFixParamMixin,
|
|
12
|
+
ProcessSkipLocationsParamMixin,
|
|
13
|
+
ProcessGetLocationParamMixin
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SimpleException(
|
|
18
|
+
# Base class
|
|
19
|
+
SimpleExceptionData, # Base class with class-level attributes
|
|
20
|
+
# Dunders
|
|
21
|
+
DunderInitSubclassMixin, # def __init_subclass__(cls, **kwargs) -> None
|
|
22
|
+
DunderNewMixin, # def __new__(cls, *args, exception: type[Exception] | UnsetType, **kwargs) -> None
|
|
23
|
+
# Normalizers
|
|
24
|
+
NormalizeParamMixin, # def _normalize_param(self, value: Any, attr: str, typ: type) -> Any
|
|
25
|
+
ProcessExceptionParamMixin, # def _process_exception_param(self, value: type[Exception]) -> tuple[type[Exception] | UnsetType, str | UnsetType]
|
|
26
|
+
ProcessHowToFixParamMixin, # def _process_how_to_fix_param(self, value: tuple[str, ...] | str | UnsetType) -> tuple[str, ...] | UnsetType
|
|
27
|
+
ProcessSkipLocationsParamMixin, # def _process_skip_locations_param(self, value: tuple[str, ...] | list[str] | str | UnsetType) -> tuple[str, ...]
|
|
28
|
+
ProcessGetLocationParamMixin, # def _process_get_location_param(self, value: int | bool | UnsetType) -> int | bool
|
|
29
|
+
# Serializers
|
|
30
|
+
ToDictMixin, # def to_dict(self) -> dict
|
|
31
|
+
ToDebugDictMixin, # def to_debug_dict(self) -> dict
|
|
32
|
+
ToJsonMixin, # def to_json(self) -> str
|
|
33
|
+
# Base exceptions
|
|
34
|
+
Exception # Base exception enabling the raise mechanism
|
|
35
|
+
):
|
|
36
|
+
"""Structured exception for the Simple ecosystem."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# -------------------------------------------------------------------------
|
|
40
|
+
# __init__ — attribute assignment and message assembly
|
|
41
|
+
# -------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
message: str | UnsetType = UNSET,
|
|
46
|
+
*,
|
|
47
|
+
value: object = UNSET,
|
|
48
|
+
value_label: str | UnsetType = UNSET,
|
|
49
|
+
expected: str | UnsetType = UNSET,
|
|
50
|
+
problem: str | UnsetType = UNSET,
|
|
51
|
+
context: str | UnsetType = UNSET,
|
|
52
|
+
how_to_fix: tuple[str, ...] | str | UnsetType = UNSET,
|
|
53
|
+
error_name: str | UnsetType = UNSET,
|
|
54
|
+
exception: Exception | type[Exception] | UnsetType = UNSET,
|
|
55
|
+
get_location: bool | int | UnsetType = UNSET,
|
|
56
|
+
skip_locations: tuple[str, ...] | str | UnsetType = UNSET,
|
|
57
|
+
oneline: bool = False,
|
|
58
|
+
):
|
|
59
|
+
|
|
60
|
+
# --- Core info ---
|
|
61
|
+
self.error_name = self._normalize_param(error_name, "error_name", str)
|
|
62
|
+
self.exception, self._intercepted_exception = self._process_exception_param(exception)
|
|
63
|
+
|
|
64
|
+
# --- Inspected value ---
|
|
65
|
+
self.value = value
|
|
66
|
+
self.value_label = self._normalize_param(value_label, "value_label", str)
|
|
67
|
+
|
|
68
|
+
# --- Exception description ---
|
|
69
|
+
self.expected = self._normalize_param(expected, "expected", str)
|
|
70
|
+
self.problem = self._normalize_param(problem, "problem", str)
|
|
71
|
+
self.context = self._normalize_param(context, "context", str)
|
|
72
|
+
self.message = self._normalize_param(message, "message", str)
|
|
73
|
+
|
|
74
|
+
# --- How to fix ---
|
|
75
|
+
self.how_to_fix = self._process_how_to_fix_param(how_to_fix)
|
|
76
|
+
|
|
77
|
+
# --- Location ---
|
|
78
|
+
self._get_location = self._process_get_location_param(get_location)
|
|
79
|
+
self._skip_locations = self._process_skip_locations_param(skip_locations)
|
|
80
|
+
|
|
81
|
+
# --- Single-line output ---
|
|
82
|
+
self._oneline = self._normalize_param(oneline, "oneline", bool)
|
|
83
|
+
|
|
84
|
+
# --- Assemble the message ---
|
|
85
|
+
self._rendered_message = self._render_message()
|
|
86
|
+
|
|
87
|
+
# --- Initialise Exception — bypasses the dataclass __init__ in the MRO ---
|
|
88
|
+
Exception.__init__(self, self._rendered_message)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# -------------------------------------------------------------------------
|
|
92
|
+
# Message assembly — delegates to the output mode
|
|
93
|
+
# -------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
def _render_message(self) -> str:
|
|
96
|
+
"""Passes the data to the active mode and returns the assembled string."""
|
|
97
|
+
if self._oneline:
|
|
98
|
+
from ..modes import ONELINE
|
|
99
|
+
return ONELINE(self, validate=False)
|
|
100
|
+
return S.DEFAULT_MESSAGE_MODE(self, validate=False)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# -------------------------------------------------------------------------
|
|
104
|
+
# Dunder methods
|
|
105
|
+
# -------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
def __repr__(self) -> str:
|
|
108
|
+
return f"<{self.__class__.__name__}(error_name={self.error_name!r}, value={self.value!r})>"
|
|
109
|
+
|
|
110
|
+
def __str__(self) -> str:
|
|
111
|
+
return self._rendered_message
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_DESIGN_NOTES = """
|
|
115
|
+
# SimpleException
|
|
116
|
+
|
|
117
|
+
## Purpose
|
|
118
|
+
The core class of the ecosystem — a structured exception that combines a data
|
|
119
|
+
layer, validation, normalisation, and output modes into a single unit. Designed
|
|
120
|
+
so that the exception itself communicates with the developer — describing the
|
|
121
|
+
cause, the circumstances, and the path to a fix.
|
|
122
|
+
|
|
123
|
+
## Class composition
|
|
124
|
+
`SimpleException` is assembled from mixins, each with a single responsibility:
|
|
125
|
+
|
|
126
|
+
### Data layer
|
|
127
|
+
- `SimpleExceptionData` — class-level attributes and default values, the source
|
|
128
|
+
of truth for all parameters. Subclasses can override them.
|
|
129
|
+
|
|
130
|
+
### Dunders
|
|
131
|
+
- `DunderInitSubclassMixin` — validates subclasses at definition time,
|
|
132
|
+
catching typos and incorrect types immediately on import.
|
|
133
|
+
- `DunderNewMixin` — dynamically adds `exception` to the instance ancestors,
|
|
134
|
+
enabling `isinstance(e, ValueError)` without static inheritance.
|
|
135
|
+
|
|
136
|
+
### Normalizers
|
|
137
|
+
- `NormalizeParamMixin` — normalises simple parameters based on a type check.
|
|
138
|
+
If the value does not match the expected type, the class-level default is
|
|
139
|
+
returned. Never raises an exception.
|
|
140
|
+
- `ProcessExceptionParamMixin` — processes the `exception` parameter,
|
|
141
|
+
accepting both an exception class and an exception instance. Handles the
|
|
142
|
+
fallback to the class-level default internally when no value is provided —
|
|
143
|
+
consistent with the other normalisation methods.
|
|
144
|
+
- `ProcessHowToFixParamMixin` — normalises the `how_to_fix` parameter,
|
|
145
|
+
accepting `str`, `tuple[str, ...]`, or `list[str]` and normalising to
|
|
146
|
+
`tuple[str, ...]`. Falls back to the class-level default.
|
|
147
|
+
- `ProcessGetLocationParamMixin` — processes the `get_location` parameter,
|
|
148
|
+
returning the provided value or `S.DEFAULT_GET_LOCATION` from settings.
|
|
149
|
+
Unlike other normalisations, the fallback comes from settings rather than
|
|
150
|
+
the class-level default — `get_location` is a global library setting.
|
|
151
|
+
- `ProcessSkipLocationsParamMixin` — processes the `skip_locations` parameter,
|
|
152
|
+
normalises the input to `tuple[str, ...]` and merges it with
|
|
153
|
+
`S.DEFAULT_LOCATION_BLACKLIST`. The merge happens inside the method —
|
|
154
|
+
the user's blacklist and the global blacklist always apply together.
|
|
155
|
+
|
|
156
|
+
### Serializers
|
|
157
|
+
- `ToDictMixin` — public attributes as a dictionary, UNSET values omitted.
|
|
158
|
+
- `ToDebugDictMixin` — public and private computed attributes as a dictionary.
|
|
159
|
+
- `ToJsonMixin` — public attributes as a JSON string.
|
|
160
|
+
|
|
161
|
+
## Private vs public attributes
|
|
162
|
+
Attributes are divided by their semantics:
|
|
163
|
+
|
|
164
|
+
**Public** — exception data, included in `to_dict()`:
|
|
165
|
+
error_name, exception, value, value_label,
|
|
166
|
+
expected, problem, context, message, how_to_fix
|
|
167
|
+
|
|
168
|
+
**Private** — behavioural configuration and computed values, only in `to_debug_dict()`:
|
|
169
|
+
_get_location, _skip_locations, _oneline,
|
|
170
|
+
_intercepted_exception, _rendered_message
|
|
171
|
+
|
|
172
|
+
## Output modes
|
|
173
|
+
The message is assembled in `_render_message()`, which delegates to a mode:
|
|
174
|
+
- `_oneline=True` — uses the `ONELINE` mode (lazy import)
|
|
175
|
+
- otherwise — uses `S.DEFAULT_MESSAGE_MODE` (default: `PRETTY`)
|
|
176
|
+
|
|
177
|
+
The lazy import of `ONELINE` in `_render_message` is intentional — it prevents
|
|
178
|
+
a circular dependency between the `exception` and `modes` modules.
|
|
179
|
+
|
|
180
|
+
## Processing flow in `__init__`
|
|
181
|
+
1. Normalise core info (error_name, exception)
|
|
182
|
+
2. Normalise the inspected value (value, value_label)
|
|
183
|
+
3. Normalise the exception description (expected, problem, context, message)
|
|
184
|
+
4. Process how_to_fix
|
|
185
|
+
5. Process location (_get_location, _skip_locations)
|
|
186
|
+
6. Set the output format (_oneline)
|
|
187
|
+
7. Assemble the message via _render_message()
|
|
188
|
+
8. Pass the message to Exception.__init__()
|
|
189
|
+
|
|
190
|
+
## How to create a custom exception
|
|
191
|
+
class MyValidationError(SimpleException):
|
|
192
|
+
error_name = "VALIDATION ERROR"
|
|
193
|
+
expected = "a positive integer"
|
|
194
|
+
how_to_fix = (
|
|
195
|
+
"Provide a value greater than 0.",
|
|
196
|
+
"Use the int type.",
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
raise MyValidationError(value=age, value_label="parameter age")
|
|
200
|
+
|
|
201
|
+
Class-level attributes overridden on the subclass take precedence over
|
|
202
|
+
parameters — they can always be overridden again at the call site.
|
|
203
|
+
|
|
204
|
+
## Using the exception parameter
|
|
205
|
+
# As a class:
|
|
206
|
+
raise SimpleException(exception=ValueError, problem="negative value")
|
|
207
|
+
|
|
208
|
+
# As an instance from an except block:
|
|
209
|
+
try:
|
|
210
|
+
...
|
|
211
|
+
except ValueError as e:
|
|
212
|
+
raise SimpleException(exception=e, problem="negative value")
|
|
213
|
+
|
|
214
|
+
## Notes
|
|
215
|
+
- `_render_message` stores the result in `self._rendered_message` — a private
|
|
216
|
+
attribute that `__str__` returns directly. `self.message` remains a clean
|
|
217
|
+
input parameter accessible via `to_dict()`.
|
|
218
|
+
- All normalisation methods are designed to never raise an exception —
|
|
219
|
+
in the worst case they return the class-level default or the settings value.
|
|
220
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .SimpleException import SimpleException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_DESIGN_NOTES = """
|
|
5
|
+
# exception
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
The core class of the ecosystem — a structured exception combining the data
|
|
9
|
+
layer, validation, normalisation, and output modes.
|
|
10
|
+
|
|
11
|
+
| Name | Description |
|
|
12
|
+
|------------------|------------------------------------------------------------------|
|
|
13
|
+
| `SimpleException`| Core class — the foundation for all exceptions in the Simple ecosystem |
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
```python
|
|
17
|
+
from simple_exception.exception import SimpleException
|
|
18
|
+
|
|
19
|
+
raise SimpleException(
|
|
20
|
+
value_label = "parameter age",
|
|
21
|
+
expected = "a positive integer",
|
|
22
|
+
value = age,
|
|
23
|
+
problem = "value is negative",
|
|
24
|
+
how_to_fix = "Provide a value greater than 0.",
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Dunders
|
|
2
|
+
from .dunders import DunderInitSubclassMixin, DunderNewMixin
|
|
3
|
+
# Normalizers
|
|
4
|
+
from .normalizers import (
|
|
5
|
+
NormalizeParamMixin,
|
|
6
|
+
ProcessExceptionParamMixin,
|
|
7
|
+
ProcessHowToFixParamMixin,
|
|
8
|
+
ProcessGetLocationParamMixin,
|
|
9
|
+
ProcessSkipLocationsParamMixin,
|
|
10
|
+
)
|
|
11
|
+
# Serializers
|
|
12
|
+
from .serializers import ToDictMixin, ToDebugDictMixin, ToJsonMixin
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
_DESIGN_NOTES = """
|
|
16
|
+
# exception/_mixins
|
|
17
|
+
|
|
18
|
+
## Contents
|
|
19
|
+
The aggregation point for all mixins of the `SimpleException` class.
|
|
20
|
+
Imports from subpackages and re-exports them as a unified interface.
|
|
21
|
+
|
|
22
|
+
| Package | Mixins |
|
|
23
|
+
|---------------|------------------------------------------------------------------------|
|
|
24
|
+
| `dunders` | DunderInitSubclassMixin, DunderNewMixin |
|
|
25
|
+
| `normalizers` | NormalizeParamMixin, ProcessExceptionParamMixin, |
|
|
26
|
+
| | ProcessHowToFixParamMixin, ProcessGetLocationParamMixin, |
|
|
27
|
+
| | ProcessSkipLocationsParamMixin |
|
|
28
|
+
| `serializers` | ToDictMixin, ToDebugDictMixin, ToJsonMixin |
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
from ._mixins import (
|
|
32
|
+
DunderInitSubclassMixin,
|
|
33
|
+
DunderNewMixin,
|
|
34
|
+
NormalizeParamMixin,
|
|
35
|
+
ProcessExceptionParamMixin,
|
|
36
|
+
ProcessHowToFixParamMixin,
|
|
37
|
+
ProcessGetLocationParamMixin,
|
|
38
|
+
ProcessSkipLocationsParamMixin,
|
|
39
|
+
ToDictMixin,
|
|
40
|
+
ToDebugDictMixin,
|
|
41
|
+
ToJsonMixin,
|
|
42
|
+
)
|
|
43
|
+
"""
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Commons
|
|
2
|
+
from ....core import SimpleExceptionData
|
|
3
|
+
# Inners
|
|
4
|
+
from ._utils import check_children_class_attributes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DunderInitSubclassMixin:
|
|
8
|
+
"""Mixin for validating subclasses at definition time."""
|
|
9
|
+
|
|
10
|
+
def __init_subclass__(
|
|
11
|
+
cls,
|
|
12
|
+
**kwargs
|
|
13
|
+
) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Validates the subclass when it is defined — checks for typos and incorrect attribute types.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
cls: The newly defined class (subclass).
|
|
19
|
+
"""
|
|
20
|
+
super().__init_subclass__(**kwargs)
|
|
21
|
+
check_children_class_attributes(SimpleExceptionData, cls)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_DESIGN_NOTES = """
|
|
25
|
+
# DunderInitSubclassMixin
|
|
26
|
+
|
|
27
|
+
## Purpose
|
|
28
|
+
Validates `SimpleException` subclasses at **definition time** — that is, at
|
|
29
|
+
import, not when an instance is created. Developer errors (typos, incorrect
|
|
30
|
+
attribute types) are surfaced immediately.
|
|
31
|
+
|
|
32
|
+
## Why at class definition time rather than at instantiation
|
|
33
|
+
`__init_subclass__` is called automatically by Python the moment the
|
|
34
|
+
interpreter processes the subclass definition. This means the developer
|
|
35
|
+
receives an error immediately on module import — not somewhere during
|
|
36
|
+
program execution where the root cause would be harder to trace.
|
|
37
|
+
|
|
38
|
+
## What is checked
|
|
39
|
+
Delegates to `check_children_class_attributes`, which verifies:
|
|
40
|
+
- The subclass contains no attributes not defined in `SimpleExceptionData`
|
|
41
|
+
(likely typos)
|
|
42
|
+
- Attribute values match the types declared in `SimpleExceptionData`
|
|
43
|
+
|
|
44
|
+
## Why the logic lives in a separate function
|
|
45
|
+
`__init_subclass__` decides **when** to validate.
|
|
46
|
+
`check_children_class_attributes` decides **how** to validate.
|
|
47
|
+
Separation of concerns — the mixin remains a clean orchestrator that is
|
|
48
|
+
readable at a glance. The validation logic itself (~35 lines with two
|
|
49
|
+
branches and walrus operators) would clutter the method to the point where
|
|
50
|
+
the intent would no longer be obvious without reading the details.
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
- `super().__init_subclass__(**kwargs)` must always be called first —
|
|
54
|
+
this ensures correct MRO behaviour under multiple inheritance.
|
|
55
|
+
- The private implementation detail `check_children_class_attributes` lives
|
|
56
|
+
in the `_utils` subdirectory alongside this mixin — it does not leave
|
|
57
|
+
that scope.
|
|
58
|
+
- Errors are reported via `SimpleExceptionInternalError` — an internal
|
|
59
|
+
library exception that signals a developer error, not a runtime error.
|
|
60
|
+
"""
|