safeshield 1.2.2__py3-none-any.whl → 1.4.2__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.
- {safeshield-1.2.2.dist-info → safeshield-1.4.2.dist-info}/METADATA +9 -1
- safeshield-1.4.2.dist-info/RECORD +31 -0
- validator/core/validator.py +10 -10
- validator/factory.py +4 -4
- validator/rules/__init__.py +6 -8
- validator/rules/array.py +27 -34
- validator/rules/base.py +32 -8
- validator/rules/basic.py +105 -10
- validator/rules/boolean.py +157 -0
- validator/rules/comparison.py +130 -40
- validator/rules/date.py +36 -20
- validator/rules/files.py +179 -67
- validator/rules/format.py +122 -31
- validator/rules/numeric.py +188 -0
- validator/rules/string.py +71 -19
- validator/rules/utilities.py +233 -133
- validator/services/rule_conflict.py +2 -2
- validator/services/rule_error_handler.py +221 -24
- validator/services/rule_preparer.py +12 -25
- safeshield-1.2.2.dist-info/RECORD +0 -31
- validator/rules/conditional.py +0 -332
- validator/rules/type.py +0 -42
- {safeshield-1.2.2.dist-info → safeshield-1.4.2.dist-info}/LICENSE +0 -0
- {safeshield-1.2.2.dist-info → safeshield-1.4.2.dist-info}/WHEEL +0 -0
- {safeshield-1.2.2.dist-info → safeshield-1.4.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: safeshield
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.2
|
|
4
4
|
Summary: Library for Help Validation Control
|
|
5
5
|
Home-page: https://github.com/WunsunTarniho/py-guard
|
|
6
6
|
Author: Wunsun Tarniho
|
|
@@ -24,6 +24,8 @@ Requires-Dist: python-dateutil==2.9.0.post0
|
|
|
24
24
|
Requires-Dist: setuptools==63.2.0
|
|
25
25
|
Requires-Dist: SQLAlchemy==2.0.41
|
|
26
26
|
Requires-Dist: Werkzeug==1.0.1
|
|
27
|
+
Requires-Dist: dnspython==2.4.2
|
|
28
|
+
Requires-Dist: idna==3.4
|
|
27
29
|
|
|
28
30
|
## License
|
|
29
31
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
|
|
@@ -66,3 +68,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
66
68
|
|
|
67
69
|
## v1.2.2 (2024-07-20)
|
|
68
70
|
- Fixed: Error when connect use postgres.
|
|
71
|
+
|
|
72
|
+
## v1.3.2 (2024-07-20)
|
|
73
|
+
- Refinement: Groups Inheritance Rule Class.
|
|
74
|
+
|
|
75
|
+
## v1.4.2 (2024-07-20)
|
|
76
|
+
- Feature: Rule can accept function as argument, Rule Chaning
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
validator/__init__.py,sha256=udxDzUicPfxBOAQvzsnl3pHur9VUppKbWMgg35hpiww,244
|
|
2
|
+
validator/exceptions.py,sha256=y2v7CaXmeGFHWcnigtLl4U-sFta_jMiXkGKXWIIVglY,366
|
|
3
|
+
validator/factory.py,sha256=JruOF0Mumk5PPqendNsO5TN4ZmPEGBNS7Nivly4_oUc,772
|
|
4
|
+
validator/core/__init__.py,sha256=ZcqlXJSk03i_CVzmIN-nVe1UOyvwwO5jhbEj7f62Y_o,59
|
|
5
|
+
validator/core/validator.py,sha256=pbfbTu6OvJiqyNu58OnIZI97a8x2r9T25IrWkBMJKTs,12684
|
|
6
|
+
validator/database/__init__.py,sha256=O-cB6-MhNapJ3iwe5jvifbMfr1dPjXLtEdfNTKIu0hc,171
|
|
7
|
+
validator/database/detector.py,sha256=Vac7oVL26GjU6expGo01-6mgUtXqldr-jirzpYokZBM,9597
|
|
8
|
+
validator/database/manager.py,sha256=XJM_I0WaWfZWV710duAc32p1gtiP2or-MAj75WPw1oM,6478
|
|
9
|
+
validator/rules/__init__.py,sha256=z3Vk3R5CRgjeqyDWZxdofD2tBMTgdyYVuFmo1mKOTj4,830
|
|
10
|
+
validator/rules/array.py,sha256=AqVoBR_chSqxPec14Av5KmR9NAByovXDNNu2oeId4-U,2528
|
|
11
|
+
validator/rules/base.py,sha256=PadT5Ko3Zs_yr5EPxOfTw-IdS7uJKhGpaekF1k4tvYk,4359
|
|
12
|
+
validator/rules/basic.py,sha256=GFdffZKW3Dzw7EOeZTyF1Oma59ZMo9zNK4gddkUMMc4,4892
|
|
13
|
+
validator/rules/boolean.py,sha256=vy6huFJ5JidpsoJ0WSvcydiU7a8aYFj225UswggSGAE,5748
|
|
14
|
+
validator/rules/comparison.py,sha256=xkQ0xoqBjVlDSfgwTvruOAnXUmKlNt9kDjTcG5r4cFM,13473
|
|
15
|
+
validator/rules/date.py,sha256=yolYaTIvQTN1LBje5SM8i7EmNzOxV_eUwnOokmqZrMs,5812
|
|
16
|
+
validator/rules/files.py,sha256=c7deO8fEiFNCx4jq1B2sJXhxTqGzTVq4kK2EscSNhKI,10946
|
|
17
|
+
validator/rules/format.py,sha256=Medw57PJwfElxA-DgxiZ5GHqvqPhUCQPFMGCGTDne8w,7070
|
|
18
|
+
validator/rules/numeric.py,sha256=nkYVc8VtrWJ3Kt7JDLPsbg7ZaN3F0zJMnbf8Y5gxNsk,6824
|
|
19
|
+
validator/rules/string.py,sha256=p8ZQfd0XaWIjksg_8ta3f6PEnXlxjRzlSJx1GohZ7yk,5237
|
|
20
|
+
validator/rules/utilities.py,sha256=ns52WbIMTt6Gs85y9kwbTfBjhmX4cfNPwA5Uw6mTaT0,11924
|
|
21
|
+
validator/services/__init__.py,sha256=zzKTmqL7v4niFGWHJBfWLqgJ0iTaW_69OzYZN8uInzQ,210
|
|
22
|
+
validator/services/rule_conflict.py,sha256=JVOgZWIGOviTfcCC69sQ1Bq8nzwtW0UFOx-bbF35uTM,4923
|
|
23
|
+
validator/services/rule_error_handler.py,sha256=A9wtjpqAQsz13qUN5sh9wz7d1wc3S3RWms-BcRCECu0,10279
|
|
24
|
+
validator/services/rule_preparer.py,sha256=4khRjdely_0Z5mxFwf1bKIid076_xDuNh2XBO_fGerE,4706
|
|
25
|
+
validator/utils/__init__.py,sha256=Yzo-xv285Be-a233M4duDdYtscuHiuBbPSX_C8yViJI,20
|
|
26
|
+
validator/utils/string.py,sha256=0YACzeEaWNEOR9_7O9A8D1ItIbtWfOJ8IfrzcB8VMYA,515
|
|
27
|
+
safeshield-1.4.2.dist-info/LICENSE,sha256=qugtRyKckyaks6hd2xyxOFSOYM6au1N80pMXuMTPvC4,1090
|
|
28
|
+
safeshield-1.4.2.dist-info/METADATA,sha256=rQBoOn-D8UTPn31TwaR_h8WStPE0aqtJ9g-x4p6oT3I,2132
|
|
29
|
+
safeshield-1.4.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
30
|
+
safeshield-1.4.2.dist-info/top_level.txt,sha256=iUtV3dlHOIiMfLuY4pruY00lFni8JzOkQ3Nh1II19OE,10
|
|
31
|
+
safeshield-1.4.2.dist-info/RECORD,,
|
validator/core/validator.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Optional, Dict, Any, List, Tuple, Union
|
|
|
2
2
|
from validator.exceptions import ValidationException, RuleNotFoundException
|
|
3
3
|
from validator.factory import RuleFactory
|
|
4
4
|
from validator.database import DatabaseManager, DatabaseAutoDetector
|
|
5
|
-
from validator.rules import
|
|
5
|
+
from validator.rules import Rule
|
|
6
6
|
from validator.services.rule_conflict import RuleConflictChecker
|
|
7
7
|
from validator.services.rule_error_handler import RuleErrorHandler
|
|
8
8
|
from validator.services.rule_preparer import RulePreparer
|
|
@@ -12,7 +12,7 @@ from collections.abc import Mapping
|
|
|
12
12
|
class Validator:
|
|
13
13
|
"""Main validation class with proper abstractions"""
|
|
14
14
|
PRIORITY_RULES = {
|
|
15
|
-
'bail', 'exclude_unless', 'exclude_if', 'exclude_with', 'exclude_without', 'required', 'required_if', 'required_unless',
|
|
15
|
+
'bail', 'sometimes', 'exclude_unless', 'exclude_if', 'exclude_with', 'exclude_without', 'required', 'required_if', 'required_unless',
|
|
16
16
|
'required_with', 'required_without'
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -23,7 +23,7 @@ class Validator:
|
|
|
23
23
|
str,
|
|
24
24
|
List[Union[
|
|
25
25
|
str,
|
|
26
|
-
|
|
26
|
+
Rule,
|
|
27
27
|
Tuple[str, Union[
|
|
28
28
|
str,
|
|
29
29
|
Union[
|
|
@@ -38,7 +38,7 @@ class Validator:
|
|
|
38
38
|
]],
|
|
39
39
|
Tuple[Union[
|
|
40
40
|
str,
|
|
41
|
-
|
|
41
|
+
Rule,
|
|
42
42
|
Tuple[str, Union[
|
|
43
43
|
str,
|
|
44
44
|
Union[
|
|
@@ -94,7 +94,7 @@ class Validator:
|
|
|
94
94
|
return False
|
|
95
95
|
return True
|
|
96
96
|
|
|
97
|
-
def _validate_rules(self, prepared_rules: Dict[str, List[
|
|
97
|
+
def _validate_rules(self, prepared_rules: Dict[str, List[Rule]], priority_only: bool):
|
|
98
98
|
validated = []
|
|
99
99
|
for field_pattern, rules in prepared_rules.items():
|
|
100
100
|
concrete_paths = self._resolve_wildcard_paths(field_pattern) if '*' in field_pattern else [field_pattern]
|
|
@@ -217,22 +217,22 @@ class Validator:
|
|
|
217
217
|
|
|
218
218
|
return _check(self.data, parts)
|
|
219
219
|
|
|
220
|
-
def _apply_rule(self, field: str, value: Any, rule:
|
|
220
|
+
def _apply_rule(self, field: str, value: Any, rule: Rule) -> bool:
|
|
221
221
|
try:
|
|
222
222
|
rule.set_validator(self)
|
|
223
223
|
|
|
224
|
-
# Format field name untuk pesan error (otomatis tangkap path aktual)
|
|
225
224
|
display_field = getattr(self, '_current_actual_path', field)
|
|
226
225
|
|
|
227
226
|
if not rule.validate(field, value, getattr(rule, 'params', [])):
|
|
228
227
|
msg = rule.message(display_field, getattr(rule, 'params', []))
|
|
229
|
-
self.error_handler.add_error(display_field, rule.rule_name, msg, value)
|
|
228
|
+
self.error_handler.add_error(display_field, rule.rule_name, getattr(rule, 'params', []), msg, value)
|
|
229
|
+
|
|
230
230
|
return False
|
|
231
231
|
else:
|
|
232
232
|
return True
|
|
233
233
|
except Exception as e:
|
|
234
234
|
display_field = getattr(self, '_current_actual_path', field)
|
|
235
|
-
self.error_handler.add_error(display_field, rule.rule_name, str(e), value)
|
|
235
|
+
self.error_handler.add_error(display_field, rule.rule_name, getattr(rule, 'params', []), str(e), value)
|
|
236
236
|
return False
|
|
237
237
|
|
|
238
238
|
def _get_nested_value(self, path: str) -> Any:
|
|
@@ -275,7 +275,7 @@ class Validator:
|
|
|
275
275
|
|
|
276
276
|
return result
|
|
277
277
|
|
|
278
|
-
def add_rule(self, field: str, rules: Union[str, List[Union[str,
|
|
278
|
+
def add_rule(self, field: str, rules: Union[str, List[Union[str, Rule]], Rule]):
|
|
279
279
|
"""Add new rules to a field"""
|
|
280
280
|
new_rules = self.rule_preparer._convert_to_rules(rules)
|
|
281
281
|
existing_rules = self.rule_preparer._convert_to_rules(self._raw_rules.get(field, []))
|
validator/factory.py
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# factory.py
|
|
2
2
|
from typing import Type, Dict, List
|
|
3
3
|
from validator.rules import all_rules
|
|
4
|
-
from validator.rules.base import
|
|
4
|
+
from validator.rules.base import Rule
|
|
5
5
|
|
|
6
6
|
class RuleFactory:
|
|
7
|
-
_rules: Dict[str, Type[
|
|
7
|
+
_rules: Dict[str, Type[Rule]] = all_rules
|
|
8
8
|
|
|
9
9
|
@classmethod
|
|
10
|
-
def create_rule(cls, rule_name: str) ->
|
|
10
|
+
def create_rule(cls, rule_name: str) -> Rule:
|
|
11
11
|
try:
|
|
12
12
|
return cls._rules[rule_name]()
|
|
13
13
|
except KeyError:
|
|
14
14
|
raise ValueError(f"Unknown validation rule: {rule_name}")
|
|
15
15
|
|
|
16
16
|
@classmethod
|
|
17
|
-
def register_rule(cls, name: str, rule_class: Type[
|
|
17
|
+
def register_rule(cls, name: str, rule_class: Type[Rule]):
|
|
18
18
|
cls._rules[name] = rule_class
|
|
19
19
|
|
|
20
20
|
@classmethod
|
validator/rules/__init__.py
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
from .base import
|
|
3
|
-
from . import array, basic, comparison, date,
|
|
2
|
+
from .base import Rule
|
|
3
|
+
from . import array, basic, comparison, date, files, format, boolean, string, numeric, utilities
|
|
4
4
|
|
|
5
5
|
def _collect_rules():
|
|
6
|
-
modules = [array, basic, comparison, date,
|
|
6
|
+
modules = [array, basic, comparison, date, files, format, boolean, string, numeric, utilities]
|
|
7
7
|
rules = {}
|
|
8
8
|
|
|
9
9
|
for module in modules:
|
|
10
10
|
for name, obj in inspect.getmembers(module):
|
|
11
11
|
if (inspect.isclass(obj) and
|
|
12
|
-
issubclass(obj,
|
|
13
|
-
obj !=
|
|
12
|
+
issubclass(obj, Rule) and
|
|
13
|
+
obj != Rule):
|
|
14
14
|
rules[obj.rule_name] = obj
|
|
15
15
|
return rules
|
|
16
16
|
|
|
@@ -21,6 +21,4 @@ for name, cls in all_rules.items():
|
|
|
21
21
|
globals()[name] = cls # Export rule name
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
__all__ = ['
|
|
25
|
-
list(all_rules.keys()) + \
|
|
26
|
-
[cls.__name__.replace('Rule', '') for cls in all_rules.values()]
|
|
24
|
+
__all__ = ['Rule'] + [cls.__name__.replace('Rule', '') for cls in all_rules.values()]
|
validator/rules/array.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
from .base import
|
|
1
|
+
from .base import Rule
|
|
2
2
|
from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
|
|
3
3
|
|
|
4
|
-
class ArrayRule(
|
|
4
|
+
class ArrayRule(Rule):
|
|
5
5
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
6
|
-
|
|
7
6
|
self._missing_keys = params
|
|
8
7
|
|
|
9
8
|
if params:
|
|
@@ -19,44 +18,38 @@ class ArrayRule(ValidationRule):
|
|
|
19
18
|
|
|
20
19
|
def message(self, field: str, params: List[str]) -> str:
|
|
21
20
|
if params:
|
|
22
|
-
return f"The
|
|
23
|
-
return f"The
|
|
24
|
-
|
|
25
|
-
class
|
|
21
|
+
return f"The :attribute must contain the keys: {', '.join(self._missing_keys)}."
|
|
22
|
+
return f"The :attribute must be an array."
|
|
23
|
+
|
|
24
|
+
class DistinctRule(Rule):
|
|
26
25
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
27
|
-
if not
|
|
26
|
+
if not isinstance(value, (list, tuple, set)):
|
|
28
27
|
return False
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
ignore_case = 'ignore_case' in params
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# Array contains element
|
|
37
|
-
if isinstance(value, (list, tuple, set)):
|
|
38
|
-
return search_value in value
|
|
31
|
+
seen = set()
|
|
32
|
+
for item in value:
|
|
33
|
+
compare_val = item
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return search_value in value.keys()
|
|
35
|
+
if ignore_case and isinstance(item, str):
|
|
36
|
+
compare_val = item.lower()
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return f"The :name must contain {params[0]}"
|
|
48
|
-
|
|
49
|
-
class DistinctRule(ValidationRule):
|
|
50
|
-
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
51
|
-
if not isinstance(value, (list, tuple, set)):
|
|
52
|
-
return False
|
|
38
|
+
if compare_val in seen:
|
|
39
|
+
return False
|
|
40
|
+
seen.add(compare_val)
|
|
53
41
|
|
|
54
|
-
return
|
|
42
|
+
return True
|
|
55
43
|
|
|
56
44
|
def message(self, field: str, params: List[str]) -> str:
|
|
57
|
-
|
|
45
|
+
base_msg = f"The :attribute must contain unique values"
|
|
46
|
+
|
|
47
|
+
if 'ignore_case' in params:
|
|
48
|
+
return f"{base_msg} (case insensitive)"
|
|
49
|
+
else:
|
|
50
|
+
return f"{base_msg} (strict comparison)"
|
|
58
51
|
|
|
59
|
-
class InArrayRule(
|
|
52
|
+
class InArrayRule(Rule):
|
|
60
53
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
61
54
|
if not params:
|
|
62
55
|
return False
|
|
@@ -64,9 +57,9 @@ class InArrayRule(ValidationRule):
|
|
|
64
57
|
return str(value) in params
|
|
65
58
|
|
|
66
59
|
def message(self, field: str, params: List[str]) -> str:
|
|
67
|
-
return f"The :
|
|
60
|
+
return f"The :attribute must be one of: {', '.join(params)}"
|
|
68
61
|
|
|
69
|
-
class InArrayKeysRule(
|
|
62
|
+
class InArrayKeysRule(Rule):
|
|
70
63
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
71
64
|
if not params or not isinstance(value, dict):
|
|
72
65
|
return False
|
|
@@ -74,4 +67,4 @@ class InArrayKeysRule(ValidationRule):
|
|
|
74
67
|
return any(key in value for key in params)
|
|
75
68
|
|
|
76
69
|
def message(self, field: str, params: List[str]) -> str:
|
|
77
|
-
return f"The :
|
|
70
|
+
return f"The :attribute must contain at least one of these keys: {', '.join(params)}"
|
validator/rules/base.py
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
|
|
3
3
|
from enum import Enum
|
|
4
|
-
# from .string import pascal_to_snake
|
|
5
4
|
import inspect
|
|
6
5
|
import re
|
|
7
6
|
|
|
8
|
-
class
|
|
7
|
+
class Rule(ABC):
|
|
9
8
|
"""Abstract base class for all validation rules"""
|
|
10
9
|
|
|
10
|
+
_rule_classes: Dict[str, Type['Rule']] = {}
|
|
11
|
+
_rule_cache: Dict[str, 'Rule'] = {}
|
|
12
|
+
|
|
11
13
|
def __init__(self, *params: str):
|
|
12
14
|
self._validator: Optional['Validator'] = None
|
|
13
15
|
self._params: List[str] = list(params)
|
|
14
16
|
|
|
15
|
-
def __init_subclass__(cls):
|
|
16
|
-
cls.rule_name = cls.pascal_to_snake(cls.__name__)
|
|
17
|
+
def __init_subclass__(cls, name=None):
|
|
18
|
+
cls.rule_name = cls.pascal_to_snake(cls.__name__) if not hasattr(cls, '_name') else cls._name
|
|
19
|
+
cls._register_rule_class()
|
|
20
|
+
cls._generate_rule_methods()
|
|
17
21
|
|
|
22
|
+
@classmethod
|
|
23
|
+
def _register_rule_class(cls):
|
|
24
|
+
cls._rule_classes[cls.rule_name] = cls
|
|
25
|
+
|
|
18
26
|
@property
|
|
19
|
-
def params(self)
|
|
20
|
-
|
|
27
|
+
def params(self):
|
|
28
|
+
params = []
|
|
29
|
+
for rule_set in self._params:
|
|
30
|
+
if isinstance(rule_set, (list, tuple)):
|
|
31
|
+
params.append(tuple(rule_set))
|
|
32
|
+
else:
|
|
33
|
+
params.append(rule_set)
|
|
34
|
+
return tuple(params)
|
|
21
35
|
|
|
36
|
+
@classmethod
|
|
37
|
+
def _generate_rule_methods(cls):
|
|
38
|
+
for rule_name, rule_class in cls._rule_classes.items():
|
|
39
|
+
@classmethod
|
|
40
|
+
def method(cls, *params, rule_class=rule_class):
|
|
41
|
+
return rule_class(*params)
|
|
42
|
+
|
|
43
|
+
setattr(cls.__class__, rule_name, method)
|
|
44
|
+
|
|
22
45
|
@params.setter
|
|
23
46
|
def params(self, value: List[str]) -> None:
|
|
24
47
|
self._params = value
|
|
@@ -64,9 +87,10 @@ class ValidationRule(ABC):
|
|
|
64
87
|
"""Return the name of the rule for error messages."""
|
|
65
88
|
pass
|
|
66
89
|
|
|
67
|
-
def _parse_option_values(self, field: str, params: List[str]) -> List[Any]:
|
|
90
|
+
def _parse_option_values(self, field: str, params: List[str], raise_for_error=True) -> List[Any]:
|
|
68
91
|
"""Parse parameters into allowed values, supporting both Enum class and literal values"""
|
|
69
|
-
|
|
92
|
+
|
|
93
|
+
if not params and raise_for_error:
|
|
70
94
|
raise ValueError(
|
|
71
95
|
f"{self.rule_name} rule requires parameters. "
|
|
72
96
|
f"Use '({self.rule_name}, EnumClass)' or '{self.rule_name}:val1,val2'"
|
validator/rules/basic.py
CHANGED
|
@@ -1,41 +1,136 @@
|
|
|
1
|
-
from .base import
|
|
1
|
+
from .base import Rule
|
|
2
2
|
from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
|
|
3
3
|
|
|
4
4
|
# =============================================
|
|
5
5
|
# BASIC VALIDATION RULES
|
|
6
6
|
# =============================================
|
|
7
7
|
|
|
8
|
-
class
|
|
8
|
+
class AnyOfRule(Rule):
|
|
9
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
10
|
+
self._last_failed_rules = [] # Reset failed rules
|
|
11
|
+
|
|
12
|
+
for i, rule_set in enumerate(params):
|
|
13
|
+
if isinstance(rule_set, str):
|
|
14
|
+
rule_set = [rule_set]
|
|
15
|
+
|
|
16
|
+
from validator import Validator
|
|
17
|
+
|
|
18
|
+
temp_validator = Validator(
|
|
19
|
+
{field: value},
|
|
20
|
+
{field: rule_set},
|
|
21
|
+
db_config=getattr(self._validator, 'db_config', None)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if temp_validator.validate():
|
|
25
|
+
return True
|
|
26
|
+
else:
|
|
27
|
+
self._last_failed_rules.append({
|
|
28
|
+
'rules': rule_set,
|
|
29
|
+
'errors': temp_validator.errors.get(field, [])
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
35
|
+
if not self._last_failed_rules:
|
|
36
|
+
return f"The :attribute field is invalid."
|
|
37
|
+
|
|
38
|
+
error_messages = []
|
|
39
|
+
|
|
40
|
+
for i, failed in enumerate(self._last_failed_rules, 1):
|
|
41
|
+
rules_str = "|".join(
|
|
42
|
+
r if isinstance(r, str) else getattr(r, 'rule_name', str(r))
|
|
43
|
+
for r in failed['rules']
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
sub_errors = []
|
|
47
|
+
for j, err_msg in enumerate(failed['errors'], 1):
|
|
48
|
+
sub_errors.append(f" {j}. {err_msg}")
|
|
49
|
+
|
|
50
|
+
error_messages.append(
|
|
51
|
+
f"Option {i} (Rules: {rules_str}):\n" +
|
|
52
|
+
"\n".join(sub_errors)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
f"The :attribute must satisfy at least one of these conditions:\n" +
|
|
57
|
+
"\n".join(error_messages)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
class BailRule(Rule):
|
|
61
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
62
|
+
self.validator._stop_on_first_failure = True
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
class RequiredRule(Rule):
|
|
9
69
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
10
70
|
return not self.is_empty(value)
|
|
11
71
|
|
|
12
72
|
def message(self, field: str, params: List[str]) -> str:
|
|
13
|
-
return f"The :
|
|
73
|
+
return f"The :attribute field is required."
|
|
14
74
|
|
|
15
|
-
class
|
|
75
|
+
class ProhibitedRule(Rule):
|
|
76
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
77
|
+
return self.is_empty(value)
|
|
78
|
+
|
|
79
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
80
|
+
return "The :attribute field is must be empty."
|
|
81
|
+
|
|
82
|
+
class NullableRule(Rule):
|
|
16
83
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
17
84
|
return True
|
|
18
85
|
|
|
19
86
|
def message(self, field: str, params: List[str]) -> str:
|
|
20
|
-
return f"The :
|
|
87
|
+
return f"The :attribute may be null."
|
|
21
88
|
|
|
22
|
-
class FilledRule(
|
|
89
|
+
class FilledRule(Rule):
|
|
23
90
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
24
91
|
return value not in ('', None)
|
|
25
92
|
|
|
26
93
|
def message(self, field: str, params: List[str]) -> str:
|
|
27
|
-
return f"The :
|
|
94
|
+
return f"The :attribute field must have a value."
|
|
28
95
|
|
|
29
|
-
class PresentRule(
|
|
96
|
+
class PresentRule(Rule):
|
|
30
97
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
31
98
|
return field in self.validator.data
|
|
32
99
|
|
|
33
100
|
def message(self, field: str, params: List[str]) -> str:
|
|
34
|
-
return f"The :
|
|
101
|
+
return f"The :attribute field must be present."
|
|
102
|
+
|
|
103
|
+
class MissingRule(Rule):
|
|
104
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
105
|
+
return value is None
|
|
106
|
+
|
|
107
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
108
|
+
return f"The :attribute field must be missing."
|
|
109
|
+
|
|
110
|
+
class ProhibitsRule(Rule):
|
|
111
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
112
|
+
if not params or value is None:
|
|
113
|
+
return True
|
|
114
|
+
return all(self.get_field_value(param, param) in (None, 'None') for param in params)
|
|
115
|
+
|
|
116
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
117
|
+
return f"When :attribute is present, {', '.join(params)} must be absent."
|
|
35
118
|
|
|
36
|
-
class SometimesRule(
|
|
119
|
+
class SometimesRule(Rule):
|
|
37
120
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
121
|
+
if value is None:
|
|
122
|
+
self.validator._is_exclude = True
|
|
123
|
+
|
|
38
124
|
return True
|
|
39
125
|
|
|
40
126
|
def message(self, field: str, params: List[str]) -> str:
|
|
41
127
|
return ""
|
|
128
|
+
|
|
129
|
+
class ExcludeRule(Rule):
|
|
130
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
131
|
+
self.validator._is_exclude = True
|
|
132
|
+
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
136
|
+
return ""
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from .base import Rule
|
|
2
|
+
from typing import Any, Dict, List, Optional, Set, Union, Tuple, Type
|
|
3
|
+
|
|
4
|
+
class BooleanRule(Rule):
|
|
5
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
6
|
+
if isinstance(value, bool):
|
|
7
|
+
return True
|
|
8
|
+
if isinstance(value, str):
|
|
9
|
+
return value.lower() in ['true', 'false', '1', '0', 'yes', 'no', 'on', 'off']
|
|
10
|
+
if isinstance(value, int):
|
|
11
|
+
return value in [0, 1]
|
|
12
|
+
return False
|
|
13
|
+
|
|
14
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
15
|
+
return f"The :attribute field must be true or false."
|
|
16
|
+
|
|
17
|
+
class AcceptedRule(Rule):
|
|
18
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
19
|
+
value = self.get_field_value(value, value)
|
|
20
|
+
|
|
21
|
+
if isinstance(value, str):
|
|
22
|
+
return value.lower() in ['yes', 'on', '1', 1, True, 'true', 'True']
|
|
23
|
+
if isinstance(value, int):
|
|
24
|
+
return value == 1
|
|
25
|
+
if isinstance(value, bool):
|
|
26
|
+
return value
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
30
|
+
return f"The :attribute must be accepted."
|
|
31
|
+
|
|
32
|
+
class DeclinedRule(Rule):
|
|
33
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
34
|
+
value = self.get_field_value(value, value)
|
|
35
|
+
|
|
36
|
+
if isinstance(value, str):
|
|
37
|
+
return value.lower() in ['no', 'off', '0', 0, False, 'false', 'False']
|
|
38
|
+
if isinstance(value, int):
|
|
39
|
+
return value == 0
|
|
40
|
+
if isinstance(value, bool):
|
|
41
|
+
return not value
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
def message(self, field: str, params: List[str]) -> str:
|
|
45
|
+
return f"The :attribute must be declined."
|
|
46
|
+
|
|
47
|
+
class AcceptedIfRule(AcceptedRule):
|
|
48
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
49
|
+
condition_met = False
|
|
50
|
+
|
|
51
|
+
if len(params) == 1 and callable(params[0]):
|
|
52
|
+
condition_met = params[0](self.validator.data)
|
|
53
|
+
elif len(params) == 1 and isinstance(params[0], bool):
|
|
54
|
+
condition_met = params[0]
|
|
55
|
+
else:
|
|
56
|
+
conditions = list(zip(params[::2], params[1::2]))
|
|
57
|
+
|
|
58
|
+
for other_field, expected_value in conditions:
|
|
59
|
+
if not other_field or expected_value is None:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
actual_value = self.get_field_value(other_field, '')
|
|
63
|
+
|
|
64
|
+
if actual_value == expected_value:
|
|
65
|
+
condition_met = True
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
if condition_met:
|
|
69
|
+
return super().validate(field, value, params)
|
|
70
|
+
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
class AcceptedUnlessRule(AcceptedRule):
|
|
74
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
75
|
+
if len(params) < 2:
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
other_field, other_value = params[0], params[1]
|
|
79
|
+
actual_value = self.get_field_value(other_field, '')
|
|
80
|
+
|
|
81
|
+
if actual_value == other_value:
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
return super().validate(field, value, params)
|
|
85
|
+
|
|
86
|
+
class AcceptedAllIfRule(AcceptedRule):
|
|
87
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
88
|
+
if len(params) < 2:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
conditions = [(f.strip(), v.strip()) for f, v in zip(params[::2], params[1::2])]
|
|
92
|
+
|
|
93
|
+
all_conditions_met = all(
|
|
94
|
+
self.get_field_value(f) == v
|
|
95
|
+
for f, v in conditions
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if not all_conditions_met:
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
return super().validate(field, value, params)
|
|
102
|
+
|
|
103
|
+
class DeclinedIfRule(DeclinedRule):
|
|
104
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
105
|
+
condition_met = False
|
|
106
|
+
|
|
107
|
+
if len(params) == 1 and callable(params[0]):
|
|
108
|
+
condition_met = params[0](self.validator.data)
|
|
109
|
+
elif len(params) == 1 and isinstance(params[0], bool):
|
|
110
|
+
condition_met = params[0]
|
|
111
|
+
else:
|
|
112
|
+
conditions = list(zip(params[::2], params[1::2]))
|
|
113
|
+
|
|
114
|
+
for other_field, expected_value in conditions:
|
|
115
|
+
if not other_field or expected_value is None:
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
actual_value = self.get_field_value(other_field, '')
|
|
119
|
+
|
|
120
|
+
if actual_value == expected_value:
|
|
121
|
+
condition_met = True
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
if condition_met:
|
|
125
|
+
return super().validate(field, value, params)
|
|
126
|
+
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
class DeclinedUnlessRule(DeclinedRule):
|
|
130
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
131
|
+
if len(params) < 2:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
other_field, other_value = params[0], params[1]
|
|
135
|
+
actual_value = self.get_field_value(other_field, '')
|
|
136
|
+
|
|
137
|
+
if actual_value == other_value:
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
return super().validate(field, value, params)
|
|
141
|
+
|
|
142
|
+
class DeclinedAllIfRule(DeclinedRule):
|
|
143
|
+
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
144
|
+
if len(params) < 2:
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
conditions = [(f.strip(), v.strip()) for f, v in zip(params[::2], params[1::2])]
|
|
148
|
+
|
|
149
|
+
all_conditions_met = all(
|
|
150
|
+
self.get_field_value(f) == v
|
|
151
|
+
for f, v in conditions
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
if not all_conditions_met:
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
return super().validate(field, value, params)
|