valarray 0.4__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.
Files changed (45) hide show
  1. valarray/__init__.py +6 -0
  2. valarray/core/__init__.py +27 -0
  3. valarray/core/array.py +121 -0
  4. valarray/core/array_type_adapter.py +109 -0
  5. valarray/core/axes_and_fields.py +127 -0
  6. valarray/core/comparisons.py +71 -0
  7. valarray/core/errors_exceptions/__init__.py +41 -0
  8. valarray/core/errors_exceptions/error_list.py +87 -0
  9. valarray/core/errors_exceptions/exceptions.py +75 -0
  10. valarray/core/errors_exceptions/generic.py +57 -0
  11. valarray/core/errors_exceptions/validation_errors/__init__.py +0 -0
  12. valarray/core/errors_exceptions/validation_errors/array_creation.py +14 -0
  13. valarray/core/errors_exceptions/validation_errors/axes.py +68 -0
  14. valarray/core/errors_exceptions/validation_errors/base.py +26 -0
  15. valarray/core/errors_exceptions/validation_errors/dtype.py +60 -0
  16. valarray/core/errors_exceptions/validation_errors/values.py +99 -0
  17. valarray/core/types/__init__.py +24 -0
  18. valarray/core/types/generics.py +40 -0
  19. valarray/core/types/other.py +23 -0
  20. valarray/core/utils.py +89 -0
  21. valarray/core/validation_functions/__init__.py +20 -0
  22. valarray/core/validation_functions/array.py +62 -0
  23. valarray/core/validation_functions/array_values.py +47 -0
  24. valarray/core/validation_functions/dtype.py +44 -0
  25. valarray/core/validation_functions/field_values/__init__.py +0 -0
  26. valarray/core/validation_functions/field_values/core.py +106 -0
  27. valarray/core/validation_functions/field_values/types_and_data_structures.py +90 -0
  28. valarray/core/validation_functions/field_values/utils.py +143 -0
  29. valarray/core/validation_functions/shape.py +67 -0
  30. valarray/core/validation_functions/utils.py +24 -0
  31. valarray/core/validators/__init__.py +13 -0
  32. valarray/core/validators/base.py +72 -0
  33. valarray/core/validators/value_comparisons.py +107 -0
  34. valarray/numpy/__init__.py +24 -0
  35. valarray/numpy/array.py +63 -0
  36. valarray/numpy/array_type_adapter.py +133 -0
  37. valarray/numpy/axes_and_fields.py +83 -0
  38. valarray/numpy/comparisons.py +135 -0
  39. valarray/numpy/errors_exceptions.py +172 -0
  40. valarray/numpy/types.py +32 -0
  41. valarray/numpy/validation_functions.py +170 -0
  42. valarray/numpy/validators.py +35 -0
  43. valarray-0.4.dist-info/METADATA +698 -0
  44. valarray-0.4.dist-info/RECORD +45 -0
  45. valarray-0.4.dist-info/WHEEL +4 -0
@@ -0,0 +1,106 @@
1
+ from types import EllipsisType
2
+ from typing import Any, Literal, overload
3
+
4
+ from valarray.core.array_type_adapter import ArrayTypeAdapter
5
+ from valarray.core.axes_and_fields import AxesTuple, AxisNameAndIdx, FieldNameAndIdx
6
+ from valarray.core.errors_exceptions import (
7
+ IncorrectAxNumberError,
8
+ IncorrectAxSizesError,
9
+ InvalidFieldValuesError,
10
+ ValidationErrorList,
11
+ )
12
+ from valarray.core.types import ArrayIndicesT, ArrayT, ComparableValueT
13
+ from valarray.core.validation_functions.shape import validate_shape
14
+
15
+ from .utils import gather_validators, process_axis, resolve_schema
16
+
17
+
18
+ @overload
19
+ def validate_field_values(
20
+ arr: ArrayT,
21
+ schema: AxesTuple[ArrayT, ArrayIndicesT, ComparableValueT] | EllipsisType,
22
+ ata: ArrayTypeAdapter[ArrayT, Any, Any, ArrayIndicesT, ComparableValueT],
23
+ check_shape: Literal[False] = False,
24
+ ) -> ValidationErrorList[InvalidFieldValuesError[ArrayT, ArrayIndicesT]]: ...
25
+
26
+
27
+ @overload
28
+ def validate_field_values(
29
+ arr: ArrayT,
30
+ schema: AxesTuple[ArrayT, ArrayIndicesT, ComparableValueT] | EllipsisType,
31
+ ata: ArrayTypeAdapter[ArrayT, Any, Any, ArrayIndicesT, ComparableValueT],
32
+ check_shape: Literal[True] = True,
33
+ ) -> (
34
+ ValidationErrorList[InvalidFieldValuesError[ArrayT, ArrayIndicesT]]
35
+ | ValidationErrorList[IncorrectAxNumberError | IncorrectAxSizesError]
36
+ ): ...
37
+
38
+
39
+ def validate_field_values(
40
+ arr: ArrayT,
41
+ schema: AxesTuple[ArrayT, ArrayIndicesT, ComparableValueT] | EllipsisType,
42
+ ata: ArrayTypeAdapter[ArrayT, Any, Any, ArrayIndicesT, ComparableValueT],
43
+ check_shape: bool = False,
44
+ ) -> (
45
+ ValidationErrorList[InvalidFieldValuesError[ArrayT, ArrayIndicesT]]
46
+ | ValidationErrorList[IncorrectAxNumberError | IncorrectAxSizesError]
47
+ ):
48
+ """Validate array fields with validators defined in schema and return potential errors.
49
+
50
+ Args:
51
+ arr (ArrayT): Array to be validated.
52
+ schema (AxesTuple | EllipsisType):
53
+ Array schema (if `...` , do not validate).
54
+ ata (ArrayTypeAdapter[ArrayT, Any, Any, ArrayIndicesT, Any]):
55
+ Adapter for specific array type.
56
+ check_shape (bool, optional): If `True`, check shape with `validate_shape()` first.
57
+ Defaults to False.
58
+
59
+ Returns:
60
+ errs (ValidationErrorList[InvalidFieldValuesError[ArrayT, ArrayIndicesT]]):
61
+ List containing at most 1 error per validator
62
+ (including built-in field value comparisons like `gt`, `lt` etc.).
63
+
64
+ (introduced ***v0.4***)
65
+ """
66
+
67
+ if check_shape:
68
+ shape_errs = validate_shape(arr, schema)
69
+ if shape_errs:
70
+ return shape_errs
71
+
72
+ errs: ValidationErrorList[InvalidFieldValuesError[ArrayT, ArrayIndicesT]] = (
73
+ ValidationErrorList([])
74
+ )
75
+
76
+ if schema == ...:
77
+ return errs
78
+
79
+ resolved_schema = resolve_schema(schema, ata.comparisons)
80
+
81
+ val_dict = gather_validators(resolved_schema)
82
+
83
+ for validator, axes_and_fields in val_dict.items():
84
+ invalid_values_dict: dict[AxisNameAndIdx, dict[FieldNameAndIdx, Any]] = {}
85
+ is_fail = False
86
+
87
+ # TODO: add indices of invalid values to error
88
+
89
+ for ax, fields in axes_and_fields.items():
90
+ status, invalid_values = process_axis(arr, validator, ax, fields, ata)
91
+
92
+ if status == "FAIL":
93
+ is_fail = True
94
+
95
+ if invalid_values is not None:
96
+ invalid_values_dict[ax] = invalid_values
97
+
98
+ if is_fail:
99
+ invalid_vals = (
100
+ invalid_values_dict if len(invalid_values_dict.items()) > 0 else None
101
+ )
102
+ errs.append(
103
+ ata.errors.invalid_field_values(validator, invalid_values=invalid_vals)
104
+ )
105
+
106
+ return errs
@@ -0,0 +1,90 @@
1
+ from dataclasses import dataclass
2
+ from typing import Generic, Sequence
3
+
4
+ from valarray.core.axes_and_fields import AxisNameAndIdx, FieldNameAndIdx
5
+ from valarray.core.types import ArrayIndicesT, ArrayT, ComparableValueT
6
+ from valarray.core.validators import ComparisonValidator, Validator
7
+
8
+ AnyValidator = (
9
+ Validator[ArrayT, ArrayIndicesT]
10
+ | ComparisonValidator[ArrayT, ArrayIndicesT, ComparableValueT]
11
+ )
12
+
13
+
14
+ @dataclass
15
+ class _ResolvedField(Generic[ArrayT, ArrayIndicesT, ComparableValueT]):
16
+ """Field name and index + list of all validators (including `ComparisonValidator`s)
17
+
18
+ ### Example:
19
+ ```
20
+ _ResolvedField(
21
+ name_and_index=FieldNameAndIdx(name="example_field", idx=1),
22
+ validators=[
23
+ ComparisonValidator(operator="ge", value=1, comparisons=...),
24
+ ...
25
+ ],
26
+ )
27
+ ```
28
+ """
29
+
30
+ name_and_index: FieldNameAndIdx
31
+
32
+ validators: Sequence[AnyValidator]
33
+
34
+ def __str__(self) -> str:
35
+ val_list = [f"-> '{str(v)}': {repr(v)}" for v in self.validators]
36
+ val_s = "\n" + "\n".join(val_list) if val_list else " -> NA"
37
+ return str(self.name_and_index) + val_s
38
+
39
+
40
+ @dataclass
41
+ class _ResolvedAx(Generic[ArrayT, ArrayIndicesT, ComparableValueT]):
42
+ """Axis name and index + list of fields (name and index + list of all validators)
43
+
44
+ ### Example:
45
+ ```
46
+ _ResolvedAx(
47
+ name_and_index=AxisNameAndIdx("example_axis", idx=0),
48
+ fields=[
49
+ _ResolvedField(
50
+ name_and_index=FieldNameAndIdx(name="example_field_a", idx=0),
51
+ validators=[
52
+ ComparisonValidator(
53
+ operator="ge", value=1, comparisons=...
54
+ ),
55
+ ...
56
+ ],
57
+ ),
58
+ _ResolvedField(
59
+ name_and_index=FieldNameAndIdx(name="example_field", idx=1),
60
+ validators=[
61
+ ComparisonValidator(
62
+ operator="lt", value=100, comparisons=...
63
+ ),
64
+ ...
65
+ ],
66
+ ),
67
+ ],
68
+ )
69
+ ```
70
+ """
71
+
72
+ name_and_index: AxisNameAndIdx
73
+ fields: list[_ResolvedField[ArrayT, ArrayIndicesT, ComparableValueT]]
74
+
75
+ def __str__(self) -> str:
76
+ val_s = (
77
+ "\n\n" + "\n".join([str(f) for f in self.fields])
78
+ if self.fields
79
+ else " -> NA"
80
+ )
81
+ return str(self.name_and_index) + val_s
82
+
83
+
84
+ _ResolvedAxesTuple = tuple[_ResolvedAx[ArrayT, ArrayIndicesT, ComparableValueT], ...]
85
+
86
+
87
+ _FieldValidatorDict = dict[
88
+ AnyValidator[ArrayT, ArrayIndicesT, ComparableValueT],
89
+ dict[AxisNameAndIdx, list[FieldNameAndIdx]],
90
+ ]
@@ -0,0 +1,143 @@
1
+ from typing import Any, Literal
2
+
3
+ from valarray.core.array_type_adapter import ArrayTypeAdapter
4
+ from valarray.core.axes_and_fields import (
5
+ AxesTuple,
6
+ AxisNameAndIdx,
7
+ Field,
8
+ FieldNameAndIdx,
9
+ )
10
+ from valarray.core.comparisons import Comparisons
11
+ from valarray.core.types import ArrayIndicesT, ArrayT, ComparableValueT
12
+ from valarray.core.utils import constraints_to_validators
13
+ from valarray.core.validation_functions.utils import apply_validator
14
+
15
+ from .types_and_data_structures import (
16
+ AnyValidator,
17
+ _FieldValidatorDict,
18
+ _ResolvedAx,
19
+ _ResolvedAxesTuple,
20
+ _ResolvedField,
21
+ )
22
+
23
+
24
+ def _resolve_field(
25
+ i: int,
26
+ fld: str | Field[ArrayT, ArrayIndicesT, ComparableValueT],
27
+ comparisons: Comparisons[ArrayT, ArrayIndicesT, ComparableValueT],
28
+ ) -> _ResolvedField[ArrayT, ArrayIndicesT, ComparableValueT]:
29
+ """Get index, (optional) name and full list of validators for field.
30
+
31
+ ## Examples:
32
+ ```
33
+ _resolve_field(i=0, fld="field_string", comparisons=...)
34
+ >>> _ResolvedField(FieldNameAndIdx(name="field_string", idx=0), [])
35
+
36
+ _resolve_field(
37
+ i=0,
38
+ fld=Field(name="example_field", ge=0, validators=()),
39
+ comparisons=...,
40
+ )
41
+ >>> _ResolvedField(
42
+ FieldNameAndIdx(name="example_field", idx=0),
43
+ validators=[] + [ComparisonValidator(operator="ge", value=0, comparisons=...)],
44
+ )
45
+
46
+ _resolve_field(
47
+ i=0,
48
+ fld=Field(),
49
+ comparisons=NumpyComparisons(),
50
+ )
51
+ >>> _ResolvedField(FieldNameAndIdx(idx=0), validators=[])
52
+ ```
53
+ """
54
+ if isinstance(fld, str):
55
+ return _ResolvedField(FieldNameAndIdx(i, fld), [])
56
+
57
+ validators = constraints_to_validators(
58
+ fld.lt, fld.le, fld.ge, fld.gt, fld.eq, comparisons
59
+ ) + list(fld.validators)
60
+
61
+ return _ResolvedField(FieldNameAndIdx(i, fld.name), validators)
62
+
63
+
64
+ def resolve_schema(
65
+ array_schema: AxesTuple[ArrayT, ArrayIndicesT, ComparableValueT],
66
+ comparisons: Comparisons[ArrayT, ArrayIndicesT, ComparableValueT],
67
+ ) -> _ResolvedAxesTuple[ArrayT, ArrayIndicesT, ComparableValueT]:
68
+ """Get name, index and list of field for each axis in schema.
69
+
70
+ ## Example:
71
+ ```
72
+ resolve_schema(
73
+ (16, "example_ax", ("example_field",)), TestComparisons()
74
+ )
75
+ >>> (
76
+ _ResolvedAx(AxisNameAndIdx(name="axis_0_len_16", idx=0), fields=[]),
77
+ _ResolvedAx(AxisNameAndIdx(name="example_ax", idx=1), fields=[]),
78
+ _ResolvedAx(
79
+ AxisNameAndIdx(name="axis_2_len_1", idx=2),
80
+ fields=[
81
+ _ResolvedField(FieldNameAndIdx("example_field", idx=0), validators=[])
82
+ ],
83
+ ),
84
+ )
85
+ ```
86
+ """
87
+ axes: list[_ResolvedAx[ArrayT, ArrayIndicesT, ComparableValueT]] = []
88
+
89
+ for ax_i, ax in enumerate(array_schema):
90
+ if isinstance(ax, str):
91
+ name = ax
92
+ else:
93
+ size = ax if isinstance(ax, int) else len(ax)
94
+ name = f"_sized_{size}"
95
+
96
+ if isinstance(ax, (str, int)):
97
+ fields = []
98
+ else:
99
+ fields = [_resolve_field(f_i, f, comparisons) for f_i, f in enumerate(ax)]
100
+
101
+ axes.append(_ResolvedAx(AxisNameAndIdx(ax_i, name), fields))
102
+
103
+ return tuple(axes)
104
+
105
+
106
+ def gather_validators(
107
+ resolved_schema: _ResolvedAxesTuple[ArrayT, ArrayIndicesT, ComparableValueT],
108
+ ) -> _FieldValidatorDict[ArrayT, ArrayIndicesT, ComparableValueT]:
109
+ val_dict: _FieldValidatorDict[ArrayT, ArrayIndicesT, ComparableValueT] = {}
110
+
111
+ for ax in resolved_schema:
112
+ for fld in ax.fields:
113
+ for val in fld.validators:
114
+ if val_dict.get(val) is None:
115
+ val_dict[val] = {}
116
+
117
+ if val_dict[val].get(ax.name_and_index) is None:
118
+ val_dict[val][ax.name_and_index] = []
119
+
120
+ val_dict[val][ax.name_and_index].append(fld.name_and_index)
121
+
122
+ return val_dict
123
+
124
+
125
+ def process_axis(
126
+ arr: ArrayT,
127
+ validator: AnyValidator[ArrayT, ArrayIndicesT, ComparableValueT],
128
+ ax: AxisNameAndIdx,
129
+ fields: list[FieldNameAndIdx],
130
+ ata: ArrayTypeAdapter[ArrayT, Any, Any, ArrayIndicesT, ComparableValueT],
131
+ ) -> tuple[Literal["FAIL", "OK"], dict[FieldNameAndIdx, Any] | None]:
132
+ arr_subset = ata.select_subset(arr, ax, fields)
133
+
134
+ res = apply_validator(validator, arr_subset)
135
+
136
+ if res.status == "FAIL":
137
+ invalid_values = ata.get_values_from_subset(
138
+ arr_subset, res.indices_invalid, ax, fields
139
+ )
140
+ else:
141
+ invalid_values = None
142
+
143
+ return res.status, invalid_values
@@ -0,0 +1,67 @@
1
+ from dataclasses import dataclass
2
+ from types import EllipsisType
3
+ from typing import Any, Optional
4
+
5
+ from valarray.core.axes_and_fields import AxesTuple
6
+ from valarray.core.errors_exceptions import (
7
+ IncorrectAxNumberError,
8
+ IncorrectAxSizesError,
9
+ ValidationErrorList,
10
+ )
11
+ from valarray.core.types import ArrayP
12
+ from valarray.core.utils import ax_sizes_from_schema
13
+
14
+
15
+ @dataclass
16
+ class _AxComp:
17
+ actual: int
18
+ expected: Optional[int]
19
+
20
+
21
+ def validate_shape(
22
+ arr: ArrayP,
23
+ schema: AxesTuple[Any, Any, Any] | EllipsisType,
24
+ ) -> ValidationErrorList[IncorrectAxNumberError | IncorrectAxSizesError]:
25
+ """Validate that array has the correct shape.
26
+
27
+ Args:
28
+ arr (ArrayT): Array to be validated.
29
+ schema (AxesTuple | EllipsisType):
30
+ Expected array schema (if `...` , do not validate).
31
+
32
+ Returns:
33
+ errs (ValidationErrorList[IncorrectAxNumberError | IncorrectAxSizesError]):
34
+ incorrect ax number error and/or incorrect ax sizes error
35
+
36
+ (introduced ***v0.2***, last_modified ***v0.4***)
37
+ """
38
+ errs: ValidationErrorList[IncorrectAxNumberError | IncorrectAxSizesError] = (
39
+ ValidationErrorList([])
40
+ )
41
+
42
+ if schema == ...:
43
+ return errs
44
+
45
+ ax_sizes = ax_sizes_from_schema(schema)
46
+
47
+ n_ax_actual = len(arr.shape)
48
+ n_ax_expected = len(ax_sizes)
49
+ if n_ax_actual != n_ax_expected:
50
+ errs.append(IncorrectAxNumberError(n_ax_actual, n_ax_expected))
51
+
52
+ min_n_ax = min(n_ax_actual, n_ax_expected)
53
+
54
+ incorrect_indices: list[int] = []
55
+ for i, ax in enumerate(
56
+ [_AxComp(a, e) for a, e in zip(arr.shape[:min_n_ax], ax_sizes[:min_n_ax])]
57
+ ):
58
+ if ax.expected is None:
59
+ continue
60
+
61
+ if ax.actual not in (ax.expected, 0):
62
+ incorrect_indices.append(i)
63
+
64
+ if incorrect_indices:
65
+ errs.append(IncorrectAxSizesError(arr.shape, ax_sizes, incorrect_indices))
66
+
67
+ return errs
@@ -0,0 +1,24 @@
1
+ from valarray.core.types import ArrayIndicesT, ArrayT
2
+ from valarray.core.validators.base import ValidationResult, Validator
3
+
4
+
5
+ def apply_validator(
6
+ validator: Validator[ArrayT, ArrayIndicesT], arr: ArrayT
7
+ ) -> ValidationResult[ArrayIndicesT]:
8
+ """Call `validator.validate()` method on array, catch `ValueError` and return `ValidationResult`
9
+
10
+ (introduced ***v0.3***)
11
+ """
12
+ try:
13
+ res_or_bool_or_none = validator.validate(arr)
14
+
15
+ if isinstance(res_or_bool_or_none, ValidationResult):
16
+ res = res_or_bool_or_none
17
+ elif res_or_bool_or_none in (None, True):
18
+ res = ValidationResult("OK")
19
+ else:
20
+ res = ValidationResult("FAIL")
21
+ except ValueError as val_err:
22
+ res = ValidationResult("FAIL", msg=str(val_err))
23
+
24
+ return res
@@ -0,0 +1,13 @@
1
+ """
2
+ *import* `Validator` **ABC**
3
+
4
+ *import* `ValidationResult` **dataclass**
5
+
6
+ *import* `ComparisonValidator` **Validator**
7
+ """
8
+
9
+ # pyright: reportUnusedImport=false
10
+
11
+ # NOTE: Wanted Validator to be imported from 'valarray.core', but that caused circular imports.
12
+ from .base import ValidationResult, Validator
13
+ from .value_comparisons import ComparisonValidator
@@ -0,0 +1,72 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Generic, Literal, Optional
4
+
5
+ from valarray.core.types import ArrayIndicesT, ArrayT
6
+
7
+
8
+ @dataclass
9
+ class ValidationResult(Generic[ArrayIndicesT]):
10
+ """Result of `Validator.validate()` method.
11
+
12
+ Attributes:
13
+ status(Literal["OK", "FAIL"]): Whether validation is succesfull (`OK`) or not (`FAIL`).
14
+ indices_invalid(Optional[ArrayIndicesT]):
15
+ Optional indices of values that failed validation. Defaults to None.
16
+ msg (Optional[str]): Optional message to be added to
17
+ `valarray.core.errors_exceptions.InvalidArrayValuesError`.
18
+ Defaults to None.
19
+
20
+ (introduced ***v0.3***)
21
+ """
22
+
23
+ status: Literal["OK", "FAIL"]
24
+ indices_invalid: Optional[ArrayIndicesT] = None
25
+ msg: Optional[str] = None
26
+
27
+
28
+ class Validator(ABC, Generic[ArrayT, ArrayIndicesT]):
29
+ """Generic validator base class.
30
+
31
+ Type variables:
32
+ - `ArrayT` - type of array object
33
+ - `ArrayIndexT` - object(s) that can select values from array using `__getitem__` method.
34
+
35
+ Subclasses need to implement:
36
+ **abstract method**
37
+ - `validate` -
38
+ Method to validate an array.
39
+
40
+ Subclasses can optionally implement:
41
+ **abstract method**
42
+ - `__str__` -
43
+ String representation of validator used in error messages.
44
+ If not implemented defaults to class name.
45
+
46
+ (introduced ***v0.3***)
47
+ """
48
+
49
+ @abstractmethod
50
+ def validate(
51
+ self, arr: ArrayT
52
+ ) -> Optional[bool | ValidationResult[ArrayIndicesT]]: ...
53
+
54
+ def __str__(self) -> str:
55
+ return self.__class__.__name__
56
+
57
+ # HACK: This nonsense should ensure that every subclass has a hash method that
58
+ # returns hash created from instance attributes after initialization and class name
59
+ def __post_init__(self):
60
+ cls_name = self.__class__.__name__
61
+ name_hash = int.from_bytes(
62
+ cls_name.encode("utf-8"), byteorder="big", signed=False
63
+ )
64
+
65
+ # NOTE: hash of vars().values() is not deterministic, but converted to tuple it is
66
+ attr_hash = hash(tuple(vars(self).values()))
67
+
68
+ self.__hash = name_hash + attr_hash # pylint:disable=W0201
69
+ self.__class__.__hash__ = Validator.__hash__
70
+
71
+ def __hash__(self) -> int:
72
+ return self.__hash
@@ -0,0 +1,107 @@
1
+ from dataclasses import dataclass
2
+ from typing import Generic
3
+
4
+ from valarray.core.comparisons import Comparisons
5
+ from valarray.core.types import (
6
+ ArrayIndicesT,
7
+ ArrayT,
8
+ ComparableValueT,
9
+ ComparisonOperator,
10
+ )
11
+
12
+ from .base import ValidationResult, Validator
13
+
14
+
15
+ def _get_comparison(
16
+ operator: ComparisonOperator,
17
+ comparisons: Comparisons[ArrayT, ArrayIndicesT, ComparableValueT],
18
+ ):
19
+ lookup = {
20
+ "lt": comparisons.where_not_lt,
21
+ "le": comparisons.where_not_le,
22
+ "ge": comparisons.where_not_ge,
23
+ "gt": comparisons.where_not_gt,
24
+ "eq": comparisons.where_not_eq,
25
+ }
26
+
27
+ op = lookup.get(operator)
28
+
29
+ if op is None:
30
+ raise NotImplementedError
31
+
32
+ return op
33
+
34
+
35
+ # NOTE: One day I will learn why pylance yells at me if
36
+ # ArrayT, ArrayIndicesT are not in both Validator AND Generic
37
+ @dataclass
38
+ class ComparisonValidator(
39
+ Validator[ArrayT, ArrayIndicesT], Generic[ArrayT, ArrayIndicesT, ComparableValueT]
40
+ ):
41
+ """Validator that compares array with a value
42
+
43
+ Attributes:
44
+ operator (ComparisonOperator): ***"lt"***/***"le"***/***"ge"***/***"gt"***/***"eq"***/
45
+ value (ComparableValueT): Value to compare with. Dependent on array type.
46
+ comparisons (Comparisons[ArrayT, ArrayIndicesT, ComparableValueT]):
47
+ Array type implementation of `Comparisons()` object.
48
+
49
+ (introduced ***v0.3***)"""
50
+
51
+ operator: ComparisonOperator
52
+ value: ComparableValueT
53
+ comparisons: Comparisons[ArrayT, ArrayIndicesT, ComparableValueT]
54
+
55
+ def validate(self, arr) -> ValidationResult[ArrayIndicesT]:
56
+ """Validate that array values conform to specified constraints.
57
+
58
+ Args:
59
+ arr (ArrayT): Array to validate.
60
+
61
+ Returns:
62
+ ValidationResult[ArrayIndicesT]: Result of validation,
63
+ plus indices of invalid values if validation is unsuccessfull.
64
+
65
+ (introduced ***v0.3***)
66
+ """
67
+ comp = _get_comparison(self.operator, self.comparisons)
68
+
69
+ indices_invalid = comp(arr, self.value)
70
+
71
+ n_invalid = self.comparisons.n_indices(indices_invalid)
72
+
73
+ return ValidationResult(
74
+ status=("FAIL" if n_invalid > 0 else "OK"),
75
+ indices_invalid=indices_invalid if n_invalid > 0 else None,
76
+ )
77
+
78
+ def __str__(self) -> str:
79
+ """String representation of validator.
80
+
81
+ ### Examples:
82
+ ```
83
+ comp_val = ComparisonValidator(operator="gt", value=3, ...)
84
+ >>> str(comp_val)
85
+ >>> '> 3'
86
+
87
+ comp_val = ComparisonValidator(operator="eq", value=2.4, ...)
88
+ >>> str(comp_val)
89
+ >>> '== 2.4'
90
+ ```
91
+
92
+ (introduced ***v0.3***, last_modified ***v0.4***)
93
+ """
94
+ # NOTE: opposite operator, because the message makes more sense to me:
95
+ # eg: Invalid * values (<= 0): [-1, -2]
96
+ # instead of: Invalid * values (> 0): [-1, -2]
97
+ lookup: dict[ComparisonOperator, str] = {
98
+ "lt": ">=",
99
+ "le": ">",
100
+ "ge": "<",
101
+ "gt": "<=",
102
+ "eq": "!=",
103
+ }
104
+
105
+ op = lookup[self.operator]
106
+
107
+ return f"{op} {self.value}"
@@ -0,0 +1,24 @@
1
+ """
2
+ *import* `ValidatedNumpyArray` **ValidatedArray**
3
+
4
+ *import* `NumpyArrayTypeAdapter` **ArrayTypeAdapter**
5
+
6
+ *import* `NumpyComparisons` **Comparisons**
7
+
8
+ *import* `Field` **dataclass**
9
+
10
+ *import* `NumpyValidator` **ABC/Validator**
11
+
12
+ `.types` ***module***
13
+
14
+ `.validation_functions` ***module***
15
+
16
+ `.errors_exceptions` ***module***
17
+ """
18
+
19
+ # pyright: reportUnusedImport=false
20
+ from .array import ValidatedNumpyArray
21
+ from .array_type_adapter import NumpyArrayTypeAdapter
22
+ from .axes_and_fields import Field
23
+ from .comparisons import NumpyComparisons
24
+ from .validators import NumpyValidator