Coverage for src / ts_stat_tests / utils / errors.py: 100%
29 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-01 09:48 +0000
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-01 09:48 +0000
1# ============================================================================ #
2# #
3# Title: Error Utilities #
4# Purpose: Functions for generating standardized error messages and #
5# performing data equality checks. #
6# #
7# ============================================================================ #
10# ---------------------------------------------------------------------------- #
11# #
12# Overview ####
13# #
14# ---------------------------------------------------------------------------- #
17# ---------------------------------------------------------------------------- #
18# Description ####
19# ---------------------------------------------------------------------------- #
22"""
23!!! note "Summary"
25 Provides utility functions for generating standardized error messages and performing numeric assertions.
27 This module includes functions to format error messages consistently and check if numeric values are within a specified tolerance, which is useful for testing and validation purposes.
28"""
31# ---------------------------------------------------------------------------- #
32# #
33# Setup ####
34# #
35# ---------------------------------------------------------------------------- #
38## --------------------------------------------------------------------------- #
39## Imports ####
40## --------------------------------------------------------------------------- #
43# ## Python StdLib Imports ----
44from typing import Mapping, Optional, Union, overload
46# ## Python Third Party Imports ----
47from typeguard import typechecked
50# ---------------------------------------------------------------------------- #
51# #
52# Functions ####
53# #
54# ---------------------------------------------------------------------------- #
57## --------------------------------------------------------------------------- #
58## Error messages ####
59## --------------------------------------------------------------------------- #
62@typechecked
63def generate_error_message(
64 parameter_name: str,
65 value_parsed: str,
66 options: Union[Mapping[str, Union[tuple[str, ...], list[str], str]], tuple[str, ...], list[str]],
67) -> str:
68 r"""
69 !!! note "Summary"
70 Generates a formatted error message for mismatched values or invalid options.
72 ???+ abstract "Details"
73 This function constructs a standardized string that describes a mismatch between a provided value and the allowed options for a given parameter. It is primarily used to provide clear, consistent feedback in `ValueError` exceptions within dispatchers.
75 Params:
76 parameter_name (str):
77 The name of the parameter or variable being checked.
78 value_parsed (str):
79 The actual value that was received.
80 options (Union[Mapping[str, Union[tuple[str, ...], list[str], str]], tuple[str, ...], list[str]]):
81 The set of valid options or a dictionary mapping categories to valid options.
83 Returns:
84 (str):
85 A formatted error message string.
87 ???+ example "Examples"
89 ```pycon {.py .python linenums="1" title="Setup"}
90 >>> from ts_stat_tests.utils.errors import generate_error_message
92 ```
94 ```pycon {.py .python linenums="1" title="Example 1: Simple Options"}
95 >>> msg = generate_error_message("param", "invalid", ["opt1", "opt2"])
96 >>> print(msg)
97 Invalid 'param': invalid. Options: ['opt1', 'opt2']
99 ```
101 ??? question "References"
102 1. [Python F-Strings documentation](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
104 """
105 return f"Invalid '{parameter_name}': {value_parsed}. Options: {options}"
108@overload
109def is_almost_equal(first: float, second: float, *, places: int = 7) -> bool: ...
110@overload
111def is_almost_equal(first: float, second: float, *, delta: float) -> bool: ...
112@typechecked
113def is_almost_equal(
114 first: float,
115 second: float,
116 *,
117 places: Optional[int] = None,
118 delta: Optional[float] = None,
119) -> bool:
120 r"""
121 !!! note "Summary"
122 Checks if two float values are almost equal within a specified precision.
124 ???+ abstract "Details"
125 Determines the equality of two floating-point numbers within a tolerance. This is necessary because floating-point arithmetic can introduce small errors that make direct equality comparisons (e.g., `a == b`) unreliable.
127 The user can specify tolerance either by `places` (decimal places) or by an absolute `delta`.
129 Params:
130 first (float):
131 The first float value.
132 second (float):
133 The second float value.
134 places (Optional[int]):
135 The number of decimal places for comparison. Defaults to 7 if not provided.
136 delta (Optional[float]):
137 An optional delta value for comparison.
139 Raises:
140 (ValueError):
141 If both `places` and `delta` are provided.
143 Returns:
144 (bool):
145 `True` if the values are almost equal, `False` otherwise.
147 ???+ example "Examples"
149 ```pycon {.py .python linenums="1" title="Setup"}
150 >>> from ts_stat_tests.utils.errors import is_almost_equal
152 ```
154 ```pycon {.py .python linenums="1" title="Example 1: Using `places`"}
155 >>> res_places = is_almost_equal(1.0, 1.00000001, places=7)
156 >>> print(res_places)
157 True
159 ```
161 ```pycon {.py .python linenums="1" title="Example 2: Using `delta`"}
162 >>> res_delta = is_almost_equal(1.0, 1.1, delta=0.2)
163 >>> print(res_delta)
164 True
166 ```
168 ```pycon {.py .python linenums="1" title="Example 3: Not Almost Equal"}
169 >>> res_not_equal = is_almost_equal(1.0, 1.1, places=3)
170 >>> print(res_not_equal)
171 False
173 ```
175 ??? equation "Calculation"
177 The comparison depends on whether `delta` or `places` is provided.
179 If `delta` is specified:
181 $$
182 |first - second| \le \text{delta}
183 $$
185 If `places` is specified (defaults to 7):
187 $$
188 \text{round}(|first - second|, \text{places}) = 0
189 $$
191 ??? success "Credit"
192 Inspiration from Python's UnitTest function [`assertAlmostEqual`](https://github.com/python/cpython/blob/3.11/Lib/unittest/case.py).
194 ??? question "References"
195 1. [Python unittest source code](https://github.com/python/cpython/blob/main/Lib/unittest/case.py)
197 """
198 if places is not None and delta is not None:
199 raise ValueError("Specify `delta` or `places`, not both.")
200 if first == second:
201 return True
202 diff: float = abs(first - second)
203 if delta is not None:
204 if diff <= delta:
205 return True
206 else:
207 places_val: int = places if places is not None else 7
208 if round(diff, places_val) == 0:
209 return True
210 return False
213@overload
214def assert_almost_equal(first: float, second: float, msg: Optional[str] = None, *, places: int = 7) -> None: ...
215@overload
216def assert_almost_equal(first: float, second: float, msg: Optional[str] = None, *, delta: float) -> None: ...
217@typechecked
218def assert_almost_equal(
219 first: float,
220 second: float,
221 msg: Optional[str] = None,
222 *,
223 places: Optional[int] = None,
224 delta: Optional[float] = None,
225) -> None:
226 r"""
227 !!! note "Summary"
228 Asserts that two float values are almost equal within a specified precision.
230 ???+ abstract "Details"
231 Performs a floating-point comparison using [is_almost_equal][ts_stat_tests.utils.errors.is_almost_equal]. If the comparison fails, an `AssertionError` is raised with a descriptive message.
233 Params:
234 first (float):
235 The first float value.
236 second (float):
237 The second float value.
238 msg (Optional[str]):
239 An optional message to include in the exception if the values are not almost equal.
240 places (Optional[int]):
241 The number of decimal places for comparison. Defaults to 7 if not provided.
242 delta (Optional[float]):
243 An optional delta value for comparison.
245 Raises:
246 (ValueError):
247 If both `places` and `delta` are provided.
248 (AssertionError):
249 If the two float values are not almost equal.
251 Returns:
252 (None):
253 None. Raises an `AssertionError` if the values are not almost equal to within the tolerances specified.
255 ???+ example "Examples"
257 ```pycon {.py .python linenums="1" title="Setup"}
258 >>> from ts_stat_tests.utils.errors import assert_almost_equal
260 ```
262 ```pycon {.py .python linenums="1" title="Example 1: Using `places`"}
263 >>> res_places = assert_almost_equal(1.0, 1.0, places=7)
264 >>> print(res_places is None)
265 True
267 ```
269 ```pycon {.py .python linenums="1" title="Example 2: Using `delta`"}
270 >>> res_delta = assert_almost_equal(1.0, 1.1, delta=0.2)
271 >>> print(res_delta is None)
272 True
274 ```
276 ```pycon {.py .python linenums="1" title="Example 3: AssertionError Raised"}
277 >>> assert_almost_equal(1.0, 1.1, places=3)
278 Traceback (most recent call last):
279 ...
280 AssertionError: Assertion failed: 1.0 != 1.1 (places=3, delta=None)
282 ```
284 ??? equation "Calculation"
286 Refer to [is_almost_equal][ts_stat_tests.utils.errors.is_almost_equal] for the underlying logic.
288 ??? success "Credit"
289 Inspiration from Python's UnitTest function [`assertAlmostEqual`](https://github.com/python/cpython/blob/3.11/Lib/unittest/case.py).
291 ??? question "References"
292 1. [Python unittest source code](https://github.com/python/cpython/blob/main/Lib/unittest/case.py)
294 """
295 is_equal: bool = False
296 if delta is not None:
297 is_equal = is_almost_equal(first, second, delta=delta)
298 else:
299 places_val: int = places if places is not None else 7
300 is_equal = is_almost_equal(first, second, places=places_val)
302 if not is_equal:
303 error_msg: str = msg if msg is not None else f"Assertion failed: {first} != {second} ({places=}, {delta=})"
304 raise AssertionError(error_msg)