safeshield 1.4.4__py3-none-any.whl → 1.4.5__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.
Potentially problematic release.
This version of safeshield might be problematic. Click here for more details.
- {safeshield-1.4.4.dist-info → safeshield-1.4.5.dist-info}/METADATA +4 -1
- {safeshield-1.4.4.dist-info → safeshield-1.4.5.dist-info}/RECORD +10 -10
- validator/core/validator.py +6 -5
- validator/rules/base.py +1 -0
- validator/rules/basic.py +2 -3
- validator/services/rule_conflict.py +128 -44
- validator/services/rule_error_handler.py +4 -1
- {safeshield-1.4.4.dist-info → safeshield-1.4.5.dist-info}/LICENSE +0 -0
- {safeshield-1.4.4.dist-info → safeshield-1.4.5.dist-info}/WHEEL +0 -0
- {safeshield-1.4.4.dist-info → safeshield-1.4.5.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: safeshield
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.5
|
|
4
4
|
Summary: Library for Help Validation Control
|
|
5
5
|
Home-page: https://github.com/WunsunTarniho/py-guard
|
|
6
6
|
Author: Wunsun Tarniho
|
|
@@ -80,3 +80,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
80
80
|
|
|
81
81
|
## v1.4.3 (2024-07-20)
|
|
82
82
|
- Fixed: Sometimes rule failed
|
|
83
|
+
|
|
84
|
+
## v1.4.4 (2024-07-20)
|
|
85
|
+
- Fixed: Sometimes rule failed
|
|
@@ -2,14 +2,14 @@ validator/__init__.py,sha256=udxDzUicPfxBOAQvzsnl3pHur9VUppKbWMgg35hpiww,244
|
|
|
2
2
|
validator/exceptions.py,sha256=y2v7CaXmeGFHWcnigtLl4U-sFta_jMiXkGKXWIIVglY,366
|
|
3
3
|
validator/factory.py,sha256=JruOF0Mumk5PPqendNsO5TN4ZmPEGBNS7Nivly4_oUc,772
|
|
4
4
|
validator/core/__init__.py,sha256=ZcqlXJSk03i_CVzmIN-nVe1UOyvwwO5jhbEj7f62Y_o,59
|
|
5
|
-
validator/core/validator.py,sha256=
|
|
5
|
+
validator/core/validator.py,sha256=la-kzp82iCii2bq4hV76F-7datWPb27vmyHvpg2itR0,12726
|
|
6
6
|
validator/database/__init__.py,sha256=O-cB6-MhNapJ3iwe5jvifbMfr1dPjXLtEdfNTKIu0hc,171
|
|
7
7
|
validator/database/detector.py,sha256=Vac7oVL26GjU6expGo01-6mgUtXqldr-jirzpYokZBM,9597
|
|
8
8
|
validator/database/manager.py,sha256=XJM_I0WaWfZWV710duAc32p1gtiP2or-MAj75WPw1oM,6478
|
|
9
9
|
validator/rules/__init__.py,sha256=z3Vk3R5CRgjeqyDWZxdofD2tBMTgdyYVuFmo1mKOTj4,830
|
|
10
10
|
validator/rules/array.py,sha256=AqVoBR_chSqxPec14Av5KmR9NAByovXDNNu2oeId4-U,2528
|
|
11
|
-
validator/rules/base.py,sha256=
|
|
12
|
-
validator/rules/basic.py,sha256=
|
|
11
|
+
validator/rules/base.py,sha256=LWOSigqnn65wges6HjpHEpyoYyDJhbBSabm96t8CXEs,4388
|
|
12
|
+
validator/rules/basic.py,sha256=LWYs4fPCA6_lTtOmtTgvjGqjVxxSdklj9fsCgSAMy2Y,4918
|
|
13
13
|
validator/rules/boolean.py,sha256=vy6huFJ5JidpsoJ0WSvcydiU7a8aYFj225UswggSGAE,5748
|
|
14
14
|
validator/rules/comparison.py,sha256=xkQ0xoqBjVlDSfgwTvruOAnXUmKlNt9kDjTcG5r4cFM,13473
|
|
15
15
|
validator/rules/date.py,sha256=yolYaTIvQTN1LBje5SM8i7EmNzOxV_eUwnOokmqZrMs,5812
|
|
@@ -19,13 +19,13 @@ validator/rules/numeric.py,sha256=nkYVc8VtrWJ3Kt7JDLPsbg7ZaN3F0zJMnbf8Y5gxNsk,68
|
|
|
19
19
|
validator/rules/string.py,sha256=p8ZQfd0XaWIjksg_8ta3f6PEnXlxjRzlSJx1GohZ7yk,5237
|
|
20
20
|
validator/rules/utilities.py,sha256=ns52WbIMTt6Gs85y9kwbTfBjhmX4cfNPwA5Uw6mTaT0,11924
|
|
21
21
|
validator/services/__init__.py,sha256=zzKTmqL7v4niFGWHJBfWLqgJ0iTaW_69OzYZN8uInzQ,210
|
|
22
|
-
validator/services/rule_conflict.py,sha256=
|
|
23
|
-
validator/services/rule_error_handler.py,sha256=
|
|
22
|
+
validator/services/rule_conflict.py,sha256=T3IhWLmZsRUccJ4oFO-OKRjrc5Xt7r71kktxjjj2IA8,9505
|
|
23
|
+
validator/services/rule_error_handler.py,sha256=K6vq-pdVd5e4Xw7cpTguORy3VPWUJQa3PoeZZcDwTY8,10643
|
|
24
24
|
validator/services/rule_preparer.py,sha256=4khRjdely_0Z5mxFwf1bKIid076_xDuNh2XBO_fGerE,4706
|
|
25
25
|
validator/utils/__init__.py,sha256=Yzo-xv285Be-a233M4duDdYtscuHiuBbPSX_C8yViJI,20
|
|
26
26
|
validator/utils/string.py,sha256=0YACzeEaWNEOR9_7O9A8D1ItIbtWfOJ8IfrzcB8VMYA,515
|
|
27
|
-
safeshield-1.4.
|
|
28
|
-
safeshield-1.4.
|
|
29
|
-
safeshield-1.4.
|
|
30
|
-
safeshield-1.4.
|
|
31
|
-
safeshield-1.4.
|
|
27
|
+
safeshield-1.4.5.dist-info/LICENSE,sha256=qugtRyKckyaks6hd2xyxOFSOYM6au1N80pMXuMTPvC4,1090
|
|
28
|
+
safeshield-1.4.5.dist-info/METADATA,sha256=57CDuVbaonpjdkkMIPsptAWIxn5_TKP7zCWzJM8bBiU,2313
|
|
29
|
+
safeshield-1.4.5.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
30
|
+
safeshield-1.4.5.dist-info/top_level.txt,sha256=iUtV3dlHOIiMfLuY4pruY00lFni8JzOkQ3Nh1II19OE,10
|
|
31
|
+
safeshield-1.4.5.dist-info/RECORD,,
|
validator/core/validator.py
CHANGED
|
@@ -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', 'sometimes', 'exclude_unless', 'exclude_if', 'exclude_with', 'exclude_without', 'required', 'required_if', 'required_unless',
|
|
15
|
+
'bail', 'sometimes', 'exclude', 'exclude_unless', 'exclude_if', 'exclude_with', 'exclude_without', 'required', 'required_if', 'required_unless',
|
|
16
16
|
'required_with', 'required_without'
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -61,7 +61,8 @@ class Validator:
|
|
|
61
61
|
self.data = data or {}
|
|
62
62
|
self._raw_rules = rules or {}
|
|
63
63
|
self._stop_on_first_failure = False
|
|
64
|
-
self.
|
|
64
|
+
self._field_to_exclude = []
|
|
65
|
+
|
|
65
66
|
|
|
66
67
|
# Initialize dependencies
|
|
67
68
|
self.rule_preparer = RulePreparer(RuleFactory())
|
|
@@ -119,7 +120,7 @@ class Validator:
|
|
|
119
120
|
)
|
|
120
121
|
|
|
121
122
|
for rule in rules:
|
|
122
|
-
if priority_only == (rule.rule_name in self.PRIORITY_RULES)
|
|
123
|
+
if priority_only == (rule.rule_name in self.PRIORITY_RULES):
|
|
123
124
|
rule.set_field_exists(field_exists)
|
|
124
125
|
|
|
125
126
|
# Skip validation for empty array with wildcard unless it's a required rule
|
|
@@ -127,10 +128,10 @@ class Validator:
|
|
|
127
128
|
continue
|
|
128
129
|
|
|
129
130
|
for value in values_to_validate:
|
|
130
|
-
|
|
131
|
+
if field_pattern not in self._field_to_exclude:
|
|
132
|
+
validated.append(self._apply_rule(field_pattern, value, rule))
|
|
131
133
|
|
|
132
134
|
delattr(self, '_current_actual_path')
|
|
133
|
-
self._is_exclude = False
|
|
134
135
|
|
|
135
136
|
return all(valid for valid in validated)
|
|
136
137
|
|
validator/rules/base.py
CHANGED
validator/rules/basic.py
CHANGED
|
@@ -119,8 +119,7 @@ class ProhibitsRule(Rule):
|
|
|
119
119
|
class SometimesRule(Rule):
|
|
120
120
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
121
121
|
if value is None:
|
|
122
|
-
self.validator.
|
|
123
|
-
return True
|
|
122
|
+
self.validator._field_to_exclude.append(field)
|
|
124
123
|
|
|
125
124
|
return True
|
|
126
125
|
|
|
@@ -129,7 +128,7 @@ class SometimesRule(Rule):
|
|
|
129
128
|
|
|
130
129
|
class ExcludeRule(Rule):
|
|
131
130
|
def validate(self, field: str, value: Any, params: List[str]) -> bool:
|
|
132
|
-
self.validator.
|
|
131
|
+
self.validator._field_to_exclude.append(field)
|
|
133
132
|
|
|
134
133
|
return True
|
|
135
134
|
|
|
@@ -4,70 +4,154 @@ import warnings
|
|
|
4
4
|
class RuleConflictChecker:
|
|
5
5
|
"""Class untuk mendeteksi dan menangani konflik antar validation rules."""
|
|
6
6
|
|
|
7
|
-
# Daftar konflik kritis yang akan memunculkan exception
|
|
8
7
|
CRITICAL_CONFLICTS = [
|
|
9
|
-
#
|
|
10
|
-
('required', '
|
|
11
|
-
('
|
|
12
|
-
('
|
|
8
|
+
('required', 'nullable'), # Cannot be both mandatory and optional
|
|
9
|
+
('required', 'sometimes'), # Sometimes overrides required
|
|
10
|
+
('filled', 'prohibited'), # Cannot require and prohibit simultaneously
|
|
11
|
+
('present', 'missing'), # Field can't be both present and missing
|
|
12
|
+
('accepted', 'declined'), # Values must be opposite
|
|
13
|
+
('same', 'different'), # Direct logical opposites
|
|
13
14
|
|
|
14
|
-
#
|
|
15
|
-
('
|
|
15
|
+
('required', 'nullable'), # Cannot be both mandatory and optional
|
|
16
|
+
('required', 'sometimes'), # Sometimes overrides required
|
|
17
|
+
('filled', 'prohibited'), # Cannot require and prohibit simultaneously
|
|
18
|
+
('present', 'missing'), # Field can't be both present and missing
|
|
19
|
+
('accepted', 'declined'), # Values must be opposite
|
|
20
|
+
('same', 'different'), # Direct logical opposites
|
|
21
|
+
|
|
22
|
+
# Numeric vs Other Types
|
|
16
23
|
('numeric', 'array'),
|
|
17
|
-
('
|
|
18
|
-
('
|
|
19
|
-
('
|
|
24
|
+
('numeric', 'boolean'),
|
|
25
|
+
('numeric', 'email'),
|
|
26
|
+
('numeric', 'date'),
|
|
27
|
+
|
|
28
|
+
# String vs Other Types
|
|
29
|
+
('string', 'array'),
|
|
30
|
+
('string', 'file'),
|
|
31
|
+
('string', 'json'),
|
|
20
32
|
|
|
21
|
-
#
|
|
33
|
+
# Special Types
|
|
34
|
+
('boolean', 'integer'),
|
|
35
|
+
('file', 'image'), # All images are files but not vice versa
|
|
36
|
+
('uuid', 'ulid'), # Similar but incompatible formats
|
|
37
|
+
('hex', 'alpha_num'),
|
|
38
|
+
|
|
39
|
+
# Email/URL/IP Conflicts
|
|
22
40
|
('email', 'ip'),
|
|
23
|
-
('
|
|
24
|
-
('
|
|
41
|
+
('email', 'url'),
|
|
42
|
+
('url', 'json'),
|
|
43
|
+
|
|
44
|
+
# Date/Time Conflicts
|
|
45
|
+
('date', 'timezone'),
|
|
46
|
+
('date_format', 'date_equals'),
|
|
47
|
+
('after', 'before'), # Illogical date ranges
|
|
48
|
+
|
|
49
|
+
# Special Formats
|
|
50
|
+
('regex', 'not_regex'), # Direct pattern negation
|
|
51
|
+
('ascii', 'alpha_dash'), # ASCII vs extended charset
|
|
52
|
+
('uppercase', 'lowercase'), # Case transformation conflicts
|
|
53
|
+
|
|
54
|
+
# Direct Value Checks
|
|
55
|
+
('in', 'not_in'),
|
|
56
|
+
('starts_with', 'doesnt_start_with'),
|
|
57
|
+
('ends_with', 'doesnt_end_with'),
|
|
58
|
+
|
|
59
|
+
# Range Conflicts
|
|
60
|
+
('between', 'digits_between'),
|
|
61
|
+
('min', 'gt'),
|
|
62
|
+
('max', 'lt'),
|
|
63
|
+
('size', 'max_digits'),
|
|
64
|
+
|
|
65
|
+
# Numeric Constraints
|
|
66
|
+
('multiple_of', 'digits_between'),
|
|
67
|
+
('integer', 'decimal'),
|
|
68
|
+
|
|
69
|
+
# Required Group
|
|
70
|
+
('required_if', 'exclude_if'),
|
|
71
|
+
('required_unless', 'exclude_unless'),
|
|
72
|
+
|
|
73
|
+
# Presence Group
|
|
74
|
+
('present_if', 'missing_if'),
|
|
75
|
+
('present_unless', 'missing_unless'),
|
|
76
|
+
|
|
77
|
+
# Acceptance Group
|
|
78
|
+
('accepted_if', 'declined_if'),
|
|
79
|
+
('accepted_unless', 'declined_unless'),
|
|
25
80
|
|
|
26
|
-
#
|
|
27
|
-
('
|
|
28
|
-
('
|
|
81
|
+
# Prohibited Group
|
|
82
|
+
('prohibited_if', 'required_if'),
|
|
83
|
+
('prohibited_unless', 'required_unless'),
|
|
84
|
+
|
|
85
|
+
('file', 'dimensions'), # Dimensions only for images
|
|
86
|
+
('image', 'mime_types'),
|
|
87
|
+
('extensions', 'mimes'),
|
|
88
|
+
('file', 'json'), # Can't be both file and JSON
|
|
89
|
+
|
|
90
|
+
('array', 'distinct'), # Distinct requires array
|
|
91
|
+
('in_array', 'contains'), # Similar containment checks
|
|
92
|
+
('array_keys', 'in_array'),
|
|
29
93
|
]
|
|
30
94
|
|
|
31
|
-
# Daftar konflik fungsional yang hanya memunculkan warning
|
|
32
95
|
WARNING_CONFLICTS = [
|
|
33
|
-
#
|
|
34
|
-
('
|
|
35
|
-
('
|
|
36
|
-
('
|
|
37
|
-
('
|
|
38
|
-
('
|
|
96
|
+
('integer', 'numeric'), # integer implies numeric check
|
|
97
|
+
('digits', 'digits_between'), # digits:5 is same as digits_between:5,5
|
|
98
|
+
('decimal', 'numeric'), # decimal is subset of numeric
|
|
99
|
+
('multiple_of', 'numeric'), # multiple_of requires numeric
|
|
100
|
+
('min', 'gt'), # similar lower-bound checks
|
|
101
|
+
('max', 'lt'), # similar upper-bound checks
|
|
102
|
+
('digits_between', 'between'), # overlapping digit vs value ranges
|
|
39
103
|
|
|
40
|
-
#
|
|
41
|
-
('
|
|
42
|
-
('
|
|
43
|
-
('
|
|
104
|
+
('alpha', 'alpha_num'), # alpha_num includes alpha
|
|
105
|
+
('alpha_dash', 'alpha_num'), # alpha_dash extends alpha_num
|
|
106
|
+
('ascii', 'alpha'), # alpha implies ASCII
|
|
107
|
+
('uppercase', 'lowercase'), # mutually exclusive cases
|
|
108
|
+
('starts_with', 'ends_with'), # potentially redundant patterns
|
|
109
|
+
('regex', 'ascii'), # regex might duplicate ASCII check
|
|
110
|
+
('contains', 'in_array'), # similar containment checks
|
|
44
111
|
|
|
45
|
-
#
|
|
46
|
-
('
|
|
47
|
-
('
|
|
112
|
+
('after', 'after_or_equal'), # _or_equal is more inclusive
|
|
113
|
+
('before', 'before_or_equal'), # _or_equal is more inclusive
|
|
114
|
+
('date_format', 'date'), # date implies format validation
|
|
115
|
+
('timezone', 'date_format'), # timezone implies format
|
|
116
|
+
('date_equals', 'after_or_equal'), # potential overlap
|
|
117
|
+
|
|
118
|
+
('file', 'image'), # image implies file check
|
|
119
|
+
('mime_types', 'mime_type_by_extension'), # similar type checks
|
|
120
|
+
('dimensions', 'image'), # dimensions requires image
|
|
121
|
+
|
|
122
|
+
('array', 'distinct'), # distinct requires array
|
|
123
|
+
('in_array', 'contains'), # similar element checks
|
|
124
|
+
('array_keys', 'in_array'), # similar key existence checks
|
|
125
|
+
|
|
126
|
+
('required_if', 'required_with'), # similar conditional logic
|
|
127
|
+
('prohibited_if', 'prohibited_unless'), # inverse conditions
|
|
128
|
+
('present_with', 'missing_with'), # mutually aware rules
|
|
129
|
+
|
|
130
|
+
('string', 'alpha'), # string conversion implied
|
|
131
|
+
('numeric', 'integer'), # numeric conversion implied
|
|
132
|
+
('boolean', 'accepted'), # boolean conversion implied
|
|
48
133
|
]
|
|
49
134
|
|
|
50
|
-
# Grup rules yang saling terkait
|
|
51
135
|
REQUIRED_GROUPS = {
|
|
52
|
-
'required_with', 'required_with_all',
|
|
53
|
-
'required_without', 'required_without_all'
|
|
136
|
+
'required_if', 'required_unless', 'required_with', 'required_with_all',
|
|
137
|
+
'required_without', 'required_without_all', 'required_if_accepted', 'required_if_declined'
|
|
54
138
|
}
|
|
55
|
-
|
|
139
|
+
|
|
56
140
|
PROHIBITED_GROUPS = {
|
|
57
|
-
'prohibited_if', 'prohibited_unless'
|
|
141
|
+
'prohibited_if', 'prohibited_unless', 'prohibited_if_accepted', 'prohibited_if_declined'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
EXCLUSION_GROUPS = {
|
|
145
|
+
'exclude_if', 'exclude_unless', 'exclude_with', 'exclude_without'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
PRESENCE_GROUPS = {
|
|
149
|
+
'present_if', 'present_unless', 'present_with', 'present_with_all',
|
|
150
|
+
'missing_if', 'missing_unless', 'missing_with', 'missing_with_all'
|
|
58
151
|
}
|
|
59
152
|
|
|
60
153
|
@classmethod
|
|
61
154
|
def check_conflicts(cls, rules: List['Rule']) -> None:
|
|
62
|
-
"""Main method untuk mengecek semua jenis konflik.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
rules: List of Rule objects to check
|
|
66
|
-
|
|
67
|
-
Raises:
|
|
68
|
-
ValueError: Untuk konflik kritis
|
|
69
|
-
UserWarning: Untuk konflik fungsional/potensial
|
|
70
|
-
"""
|
|
71
155
|
rule_names = {r.rule_name for r in rules}
|
|
72
156
|
params_map = {r.rule_name: r.params for r in rules}
|
|
73
157
|
|
|
@@ -162,12 +162,15 @@ class RuleErrorHandler:
|
|
|
162
162
|
# Rules with field-value pairs (field1,value1,field2,value2,...)
|
|
163
163
|
if rule in {
|
|
164
164
|
'required_if', 'required_unless', 'exclude_if', 'exclude_unless',
|
|
165
|
+
'accepted_if', 'accepted_all_if', 'declined_if', 'declined_all_if',
|
|
166
|
+
'accepted_unless', 'declined_unless',
|
|
165
167
|
'missing_if', 'missing_unless', 'present_if', 'present_unless'
|
|
166
168
|
}:
|
|
167
169
|
return self._current_params[::2] # Take every even index
|
|
168
170
|
|
|
169
|
-
# Rules with
|
|
171
|
+
# Rules with multi field references
|
|
170
172
|
if rule in {
|
|
173
|
+
'required_with', 'required_with_all', 'required_without', 'required_without_all',
|
|
171
174
|
'prohibits', 'exclude_with', 'exclude_without',
|
|
172
175
|
'missing_with', 'missing_with_all',
|
|
173
176
|
'present_with', 'present_with_all'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|