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.
- valarray/__init__.py +6 -0
- valarray/core/__init__.py +27 -0
- valarray/core/array.py +121 -0
- valarray/core/array_type_adapter.py +109 -0
- valarray/core/axes_and_fields.py +127 -0
- valarray/core/comparisons.py +71 -0
- valarray/core/errors_exceptions/__init__.py +41 -0
- valarray/core/errors_exceptions/error_list.py +87 -0
- valarray/core/errors_exceptions/exceptions.py +75 -0
- valarray/core/errors_exceptions/generic.py +57 -0
- valarray/core/errors_exceptions/validation_errors/__init__.py +0 -0
- valarray/core/errors_exceptions/validation_errors/array_creation.py +14 -0
- valarray/core/errors_exceptions/validation_errors/axes.py +68 -0
- valarray/core/errors_exceptions/validation_errors/base.py +26 -0
- valarray/core/errors_exceptions/validation_errors/dtype.py +60 -0
- valarray/core/errors_exceptions/validation_errors/values.py +99 -0
- valarray/core/types/__init__.py +24 -0
- valarray/core/types/generics.py +40 -0
- valarray/core/types/other.py +23 -0
- valarray/core/utils.py +89 -0
- valarray/core/validation_functions/__init__.py +20 -0
- valarray/core/validation_functions/array.py +62 -0
- valarray/core/validation_functions/array_values.py +47 -0
- valarray/core/validation_functions/dtype.py +44 -0
- valarray/core/validation_functions/field_values/__init__.py +0 -0
- valarray/core/validation_functions/field_values/core.py +106 -0
- valarray/core/validation_functions/field_values/types_and_data_structures.py +90 -0
- valarray/core/validation_functions/field_values/utils.py +143 -0
- valarray/core/validation_functions/shape.py +67 -0
- valarray/core/validation_functions/utils.py +24 -0
- valarray/core/validators/__init__.py +13 -0
- valarray/core/validators/base.py +72 -0
- valarray/core/validators/value_comparisons.py +107 -0
- valarray/numpy/__init__.py +24 -0
- valarray/numpy/array.py +63 -0
- valarray/numpy/array_type_adapter.py +133 -0
- valarray/numpy/axes_and_fields.py +83 -0
- valarray/numpy/comparisons.py +135 -0
- valarray/numpy/errors_exceptions.py +172 -0
- valarray/numpy/types.py +32 -0
- valarray/numpy/validation_functions.py +170 -0
- valarray/numpy/validators.py +35 -0
- valarray-0.4.dist-info/METADATA +698 -0
- valarray-0.4.dist-info/RECORD +45 -0
- 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
|