safeshield 1.4.3__tar.gz → 1.4.5__tar.gz

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 (37) hide show
  1. {safeshield-1.4.3 → safeshield-1.4.5}/PKG-INFO +7 -1
  2. {safeshield-1.4.3 → safeshield-1.4.5}/README.md +7 -1
  3. {safeshield-1.4.3 → safeshield-1.4.5}/safeshield.egg-info/PKG-INFO +7 -1
  4. {safeshield-1.4.3 → safeshield-1.4.5}/setup.py +1 -1
  5. {safeshield-1.4.3 → safeshield-1.4.5}/validator/core/validator.py +6 -5
  6. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/base.py +1 -0
  7. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/basic.py +2 -2
  8. safeshield-1.4.5/validator/services/rule_conflict.py +217 -0
  9. {safeshield-1.4.3 → safeshield-1.4.5}/validator/services/rule_error_handler.py +4 -1
  10. safeshield-1.4.3/validator/services/rule_conflict.py +0 -133
  11. {safeshield-1.4.3 → safeshield-1.4.5}/LICENSE +0 -0
  12. {safeshield-1.4.3 → safeshield-1.4.5}/safeshield.egg-info/SOURCES.txt +0 -0
  13. {safeshield-1.4.3 → safeshield-1.4.5}/safeshield.egg-info/dependency_links.txt +0 -0
  14. {safeshield-1.4.3 → safeshield-1.4.5}/safeshield.egg-info/requires.txt +0 -0
  15. {safeshield-1.4.3 → safeshield-1.4.5}/safeshield.egg-info/top_level.txt +0 -0
  16. {safeshield-1.4.3 → safeshield-1.4.5}/setup.cfg +0 -0
  17. {safeshield-1.4.3 → safeshield-1.4.5}/validator/__init__.py +0 -0
  18. {safeshield-1.4.3 → safeshield-1.4.5}/validator/core/__init__.py +0 -0
  19. {safeshield-1.4.3 → safeshield-1.4.5}/validator/database/__init__.py +0 -0
  20. {safeshield-1.4.3 → safeshield-1.4.5}/validator/database/detector.py +0 -0
  21. {safeshield-1.4.3 → safeshield-1.4.5}/validator/database/manager.py +0 -0
  22. {safeshield-1.4.3 → safeshield-1.4.5}/validator/exceptions.py +0 -0
  23. {safeshield-1.4.3 → safeshield-1.4.5}/validator/factory.py +0 -0
  24. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/__init__.py +0 -0
  25. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/array.py +0 -0
  26. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/boolean.py +0 -0
  27. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/comparison.py +0 -0
  28. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/date.py +0 -0
  29. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/files.py +0 -0
  30. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/format.py +0 -0
  31. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/numeric.py +0 -0
  32. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/string.py +0 -0
  33. {safeshield-1.4.3 → safeshield-1.4.5}/validator/rules/utilities.py +0 -0
  34. {safeshield-1.4.3 → safeshield-1.4.5}/validator/services/__init__.py +0 -0
  35. {safeshield-1.4.3 → safeshield-1.4.5}/validator/services/rule_preparer.py +0 -0
  36. {safeshield-1.4.3 → safeshield-1.4.5}/validator/utils/__init__.py +0 -0
  37. {safeshield-1.4.3 → safeshield-1.4.5}/validator/utils/string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeshield
3
- Version: 1.4.3
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
@@ -67,3 +67,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
67
67
 
68
68
  ## v1.4.3 (2024-07-20)
69
69
  - Fixed: Error when validation failed
70
+
71
+ ## v1.4.3 (2024-07-20)
72
+ - Fixed: Sometimes rule failed
73
+
74
+ ## v1.4.4 (2024-07-20)
75
+ - Fixed: Sometimes rule failed
@@ -47,4 +47,10 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
47
47
  - Feature: Rule can accept function as argument, Rule Chaning
48
48
 
49
49
  ## v1.4.3 (2024-07-20)
50
- - Fixed: Error when validation failed
50
+ - Fixed: Error when validation failed
51
+
52
+ ## v1.4.3 (2024-07-20)
53
+ - Fixed: Sometimes rule failed
54
+
55
+ ## v1.4.4 (2024-07-20)
56
+ - Fixed: Sometimes rule failed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: safeshield
3
- Version: 1.4.3
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
@@ -67,3 +67,9 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
67
67
 
68
68
  ## v1.4.3 (2024-07-20)
69
69
  - Fixed: Error when validation failed
70
+
71
+ ## v1.4.3 (2024-07-20)
72
+ - Fixed: Sometimes rule failed
73
+
74
+ ## v1.4.4 (2024-07-20)
75
+ - Fixed: Sometimes rule failed
@@ -7,7 +7,7 @@ def get_requirements():
7
7
 
8
8
  setup(
9
9
  name="safeshield", # Nama package di PyPI
10
- version="1.4.3",
10
+ version="1.4.5",
11
11
  packages=find_packages(),
12
12
  install_requires=get_requirements(),
13
13
  author="Wunsun Tarniho",
@@ -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._is_exclude = False
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) and not self._is_exclude:
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
- validated.append(self._apply_rule(field_pattern, value, rule))
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
 
@@ -16,6 +16,7 @@ class Rule(ABC):
16
16
 
17
17
  def __init_subclass__(cls, name=None):
18
18
  cls.rule_name = cls.pascal_to_snake(cls.__name__) if not hasattr(cls, '_name') else cls._name
19
+ print(cls.__name__)
19
20
  cls._register_rule_class()
20
21
  cls._generate_rule_methods()
21
22
 
@@ -119,7 +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._is_exclude = True
122
+ self.validator._field_to_exclude.append(field)
123
123
 
124
124
  return True
125
125
 
@@ -128,7 +128,7 @@ class SometimesRule(Rule):
128
128
 
129
129
  class ExcludeRule(Rule):
130
130
  def validate(self, field: str, value: Any, params: List[str]) -> bool:
131
- self.validator._is_exclude = True
131
+ self.validator._field_to_exclude.append(field)
132
132
 
133
133
  return True
134
134
 
@@ -0,0 +1,217 @@
1
+ from typing import List, Set, Dict, Tuple
2
+ import warnings
3
+
4
+ class RuleConflictChecker:
5
+ """Class untuk mendeteksi dan menangani konflik antar validation rules."""
6
+
7
+ CRITICAL_CONFLICTS = [
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
14
+
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
23
+ ('numeric', 'array'),
24
+ ('numeric', 'boolean'),
25
+ ('numeric', 'email'),
26
+ ('numeric', 'date'),
27
+
28
+ # String vs Other Types
29
+ ('string', 'array'),
30
+ ('string', 'file'),
31
+ ('string', 'json'),
32
+
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
40
+ ('email', 'ip'),
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'),
80
+
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'),
93
+ ]
94
+
95
+ WARNING_CONFLICTS = [
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
103
+
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
111
+
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
133
+ ]
134
+
135
+ REQUIRED_GROUPS = {
136
+ 'required_if', 'required_unless', 'required_with', 'required_with_all',
137
+ 'required_without', 'required_without_all', 'required_if_accepted', 'required_if_declined'
138
+ }
139
+
140
+ PROHIBITED_GROUPS = {
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'
151
+ }
152
+
153
+ @classmethod
154
+ def check_conflicts(cls, rules: List['Rule']) -> None:
155
+ rule_names = {r.rule_name for r in rules}
156
+ params_map = {r.rule_name: r.params for r in rules}
157
+
158
+ cls._check_critical_conflicts(rule_names)
159
+ cls._check_warning_conflicts(rule_names)
160
+ cls._check_parameter_conflicts(rule_names, params_map)
161
+ cls._check_special_cases(rule_names)
162
+
163
+ @classmethod
164
+ def _check_critical_conflicts(cls, rule_names: Set[str]) -> None:
165
+ """Cek konflik kritis yang akan memunculkan exception."""
166
+ for rule1, rule2 in cls.CRITICAL_CONFLICTS:
167
+ if rule1 in rule_names and rule2 in rule_names:
168
+ raise ValueError(
169
+ f"Critical rule conflict: '{rule1}' cannot be used with '{rule2}'"
170
+ )
171
+
172
+ @classmethod
173
+ def _check_warning_conflicts(cls, rule_names: Set[str]) -> None:
174
+ """Cek konflik fungsional yang hanya memunculkan warning."""
175
+ for rule1, rule2 in cls.WARNING_CONFLICTS:
176
+ if rule1 in rule_names and rule2 in rule_names:
177
+ warnings.warn(f"Potential overlap: '{rule1}' and '{rule2}' may validate similar things", UserWarning, stacklevel=2)
178
+
179
+ @classmethod
180
+ def _check_parameter_conflicts(cls, rule_names: Set[str], params_map: Dict[str, List[str]]) -> None:
181
+ """Cek konflik parameter antar rules."""
182
+ # Range conflicts
183
+ if 'min' in rule_names and 'max' in rule_names:
184
+ min_val = float(params_map['min'][0])
185
+ max_val = float(params_map['max'][0])
186
+ if min_val > max_val:
187
+ raise ValueError(f"Invalid range: min ({min_val}) > max ({max_val})")
188
+
189
+ if 'between' in rule_names:
190
+ between_vals = params_map['between']
191
+
192
+ if len(between_vals) != 2:
193
+ raise ValueError("Between rule requires exactly 2 values")
194
+ min_val, max_val = map(float, between_vals)
195
+ if min_val >= max_val:
196
+ raise ValueError(f"Invalid between range: {min_val} >= {max_val}")
197
+
198
+ # Size vs length checks
199
+ if 'size' in rule_names and ('min' in rule_names or 'max' in rule_names):
200
+ warnings.warn("'size' already implies exact dimension, 'min/max' may be redundant", UserWarning)
201
+
202
+ @classmethod
203
+ def _check_special_cases(cls, rule_names: Set[str]) -> None:
204
+ """Cek special cases dan grup rules."""
205
+ # Required_with/without group conflicts
206
+ if len(cls.REQUIRED_GROUPS & rule_names) > 1:
207
+ warnings.warn(
208
+ "Multiple required_* conditions may cause unexpected behavior",
209
+ UserWarning
210
+ )
211
+
212
+ # Prohibited_if/unless conflicts
213
+ if len(cls.PROHIBITED_GROUPS & rule_names) > 1:
214
+ warnings.warn(
215
+ "Using both prohibited_if and prohibited_unless may be confusing",
216
+ UserWarning
217
+ )
@@ -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 just field references
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'
@@ -1,133 +0,0 @@
1
- from typing import List, Set, Dict, Tuple
2
- import warnings
3
-
4
- class RuleConflictChecker:
5
- """Class untuk mendeteksi dan menangani konflik antar validation rules."""
6
-
7
- # Daftar konflik kritis yang akan memunculkan exception
8
- CRITICAL_CONFLICTS = [
9
- # Mutually exclusive presence
10
- ('required', 'nullable'),
11
- ('required', 'sometimes'),
12
- ('filled', 'prohibited_if'),
13
-
14
- # Type conflicts
15
- ('numeric', 'email'),
16
- ('numeric', 'array'),
17
- ('boolean', 'integer'),
18
- ('boolean', 'numeric'),
19
- ('array', 'string'),
20
-
21
- # Format conflicts
22
- ('email', 'ip'),
23
- ('uuid', 'ulid'),
24
- ('json', 'timezone'),
25
-
26
- # Value requirement conflicts
27
- ('accepted', 'declined'),
28
- ('same', 'different')
29
- ]
30
-
31
- # Daftar konflik fungsional yang hanya memunculkan warning
32
- WARNING_CONFLICTS = [
33
- # Overlapping validation
34
- ('between', 'digits_between'),
35
- ('min', 'size'),
36
- ('max', 'size'),
37
- ('confirmed', 'same'),
38
- ('in', 'not_in'),
39
-
40
- # Redundant type checks
41
- ('integer', 'numeric'),
42
- ('alpha_num', 'alpha_dash'),
43
- ('starts_with', 'ends_with'),
44
-
45
- # Similar format checks
46
- ('url', 'json'),
47
- ('ulid', 'uuid')
48
- ]
49
-
50
- # Grup rules yang saling terkait
51
- REQUIRED_GROUPS = {
52
- 'required_with', 'required_with_all',
53
- 'required_without', 'required_without_all'
54
- }
55
-
56
- PROHIBITED_GROUPS = {
57
- 'prohibited_if', 'prohibited_unless'
58
- }
59
-
60
- @classmethod
61
- 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
- rule_names = {r.rule_name for r in rules}
72
- params_map = {r.rule_name: r.params for r in rules}
73
-
74
- cls._check_critical_conflicts(rule_names)
75
- cls._check_warning_conflicts(rule_names)
76
- cls._check_parameter_conflicts(rule_names, params_map)
77
- cls._check_special_cases(rule_names)
78
-
79
- @classmethod
80
- def _check_critical_conflicts(cls, rule_names: Set[str]) -> None:
81
- """Cek konflik kritis yang akan memunculkan exception."""
82
- for rule1, rule2 in cls.CRITICAL_CONFLICTS:
83
- if rule1 in rule_names and rule2 in rule_names:
84
- raise ValueError(
85
- f"Critical rule conflict: '{rule1}' cannot be used with '{rule2}'"
86
- )
87
-
88
- @classmethod
89
- def _check_warning_conflicts(cls, rule_names: Set[str]) -> None:
90
- """Cek konflik fungsional yang hanya memunculkan warning."""
91
- for rule1, rule2 in cls.WARNING_CONFLICTS:
92
- if rule1 in rule_names and rule2 in rule_names:
93
- warnings.warn(f"Potential overlap: '{rule1}' and '{rule2}' may validate similar things", UserWarning, stacklevel=2)
94
-
95
- @classmethod
96
- def _check_parameter_conflicts(cls, rule_names: Set[str], params_map: Dict[str, List[str]]) -> None:
97
- """Cek konflik parameter antar rules."""
98
- # Range conflicts
99
- if 'min' in rule_names and 'max' in rule_names:
100
- min_val = float(params_map['min'][0])
101
- max_val = float(params_map['max'][0])
102
- if min_val > max_val:
103
- raise ValueError(f"Invalid range: min ({min_val}) > max ({max_val})")
104
-
105
- if 'between' in rule_names:
106
- between_vals = params_map['between']
107
-
108
- if len(between_vals) != 2:
109
- raise ValueError("Between rule requires exactly 2 values")
110
- min_val, max_val = map(float, between_vals)
111
- if min_val >= max_val:
112
- raise ValueError(f"Invalid between range: {min_val} >= {max_val}")
113
-
114
- # Size vs length checks
115
- if 'size' in rule_names and ('min' in rule_names or 'max' in rule_names):
116
- warnings.warn("'size' already implies exact dimension, 'min/max' may be redundant", UserWarning)
117
-
118
- @classmethod
119
- def _check_special_cases(cls, rule_names: Set[str]) -> None:
120
- """Cek special cases dan grup rules."""
121
- # Required_with/without group conflicts
122
- if len(cls.REQUIRED_GROUPS & rule_names) > 1:
123
- warnings.warn(
124
- "Multiple required_* conditions may cause unexpected behavior",
125
- UserWarning
126
- )
127
-
128
- # Prohibited_if/unless conflicts
129
- if len(cls.PROHIBITED_GROUPS & rule_names) > 1:
130
- warnings.warn(
131
- "Using both prohibited_if and prohibited_unless may be confusing",
132
- UserWarning
133
- )
File without changes
File without changes