Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/source/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,10 @@ The given _field_ must match the field under validation.

The field under validation must have a size matching the given _value_. For string data, _value_ corresponds to the number of characters. For numeric data, _value_ corresponds to a given integer value. For an array, _size_ corresponds to the `count` of the array. For files, _size_ corresponds to the file size in kilobytes.

#### sometimes

The other validations will only apply if this field is present and non-empty. Incompatible with _required_ and _nullable_.

#### starts_with:_foo_,_bar_,...

The field under validation must start with one of the given values.
Expand Down
4 changes: 4 additions & 0 deletions flask_sieve/lang/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,29 @@
'file': 'The :attribute must be less than :value_0 kilobytes.',
'string': 'The :attribute must be less than :value_0 characters.',
'array': 'The :attribute must have less than :value_0 items.',
'empty': 'The :attribute could not be validated since it is empty.'
},
'lte': {
'numeric': 'The :attribute must be less than or equal :value_0.',
'file': 'The :attribute must be less than or equal :value_0 kilobytes.',
'string': 'The :attribute must be less than or equal :value_0 characters.',
'array': 'The :attribute must not have more than :value_0 items.',
'empty': 'The :attribute could not be validated since it is empty.'
},
'max': {
'numeric': 'The :attribute may not be greater than :max_0.',
'file': 'The :attribute may not be greater than :max_0 kilobytes.',
'string': 'The :attribute may not be greater than :max_0 characters.',
'array': 'The :attribute may not have more than :max_0 items.',
'empty': 'The :attribute could not be validated since it is empty.'
},
'mime_types': 'The :attribute must be a file of type: :values_0.',
'min': {
'numeric': 'The :attribute must be at least :min_0.',
'file': 'The :attribute must be at least :min_0 kilobytes.',
'string': 'The :attribute must be at least :min_0 characters.',
'array': 'The :attribute must have at least :min_0 items.',
'empty': 'The :attribute could not be validated since it is empty.'
},
'not_in': 'The selected :attribute is invalid.',
'not_regex': 'The :attribute format is invalid.',
Expand Down
23 changes: 20 additions & 3 deletions flask_sieve/rules_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ def validate_extension(self, value, params, **kwargs):
def validate_file(value, **kwargs):
return isinstance(value, FileStorage)

@staticmethod
def validate_empty(value, **kwargs):
return value == ''

def validate_filled(self, value, attribute, nullable, **kwargs):
if self.validate_present(attribute):
return self.validate_required(value, attribute, nullable)
Expand Down Expand Up @@ -364,18 +368,24 @@ def validate_json(self, value, **kwargs):

def validate_lt(self, value, params, **kwargs):
self._assert_params_size(size=1, params=params, rule='lt')
if value == '':
return False
value = self._get_size(value)
lower = self._get_size(self._attribute_value(params[0]))
return value < lower

def validate_lte(self, value, params, **kwargs):
self._assert_params_size(size=1, params=params, rule='lte')
if value == '':
return False
value = self._get_size(value)
lower = self._get_size(self._attribute_value(params[0]))
return value <= lower

def validate_max(self, value, params, **kwargs):
self._assert_params_size(size=1, params=params, rule='max')
if value == '':
return False
value = self._get_size(value)
upper = self._get_size(params[0])
return value <= upper
Expand Down Expand Up @@ -406,6 +416,9 @@ def validate_not_regex(self, value, params, **kwargs):
def validate_nullable(value, **kwargs):
return True

def validate_sometimes(self, value, **kwargs):
return True

def validate_numeric(self, value, **kwargs):
return self._can_call_with_method(float, value)

Expand Down Expand Up @@ -527,14 +540,16 @@ def validate_uuid(value, **kwargs):
r'^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$',
str(value).lower()
) is not None

def _is_attribute_nullable(self, attribute, params, rules, **kwargs):
is_explicitly_nullable = self._has_rule(rules, 'nullable')
if is_explicitly_nullable:
return True
value = self._attribute_value(attribute)
if value is not None:
return False
is_optional = self._has_rule(rules, 'sometimes')
if is_optional and value is not None:
return True

attribute_conditional_rules = list(filter(lambda rule: rule['name'] in conditional_inclusion_rules, rules))
if len(attribute_conditional_rules) == 0:
return False
Expand Down Expand Up @@ -621,6 +636,8 @@ def _get_type_from_value(self, value):
return 'array'
elif self.validate_file(value):
return 'file'
elif self.validate_empty(value):
return 'empty'
return 'string'

def _has_any_of_rules(self, subset, rules):
Expand Down
47 changes: 46 additions & 1 deletion tests/test_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,17 @@ def validate_odd(value, **kwargs):
)
self.assertTrue(self._validator.passes())

def test_translates_validations_set_through_custom_handlers(self):
def validate_odd(value, **kwargs):
return int(value) % 2
self._validator.set_custom_handlers([
{
'handler': validate_odd,
'message':'This number must be odd.',
'params_count':0
}
])

def test_cannot_set_custom_handler_without_validate_keyword(self):
def method_odd(value, **kwargs):
return int(value) % 2
Expand All @@ -173,9 +184,43 @@ def method_odd(value, **kwargs):
params_count=0
)

def test_sometimes_request(self):
self.set_validator_params(
rules={'number': ['sometimes', 'max:5']},
request={}
)
self.assertTrue(self._validator.passes())

self.set_validator_params(
rules={'number': ['sometimes', 'max:5']},
request={'number': ''}
)
self.assertTrue(self._validator.fails())
self.assertDictEqual({
'number': [
'The number could not be validated since it is empty.'
]
}, self._validator.messages())

self.set_validator_params(
rules={'number': ['sometimes', 'max:5']},
request={'number': 2}
)
self.assertTrue(self._validator.passes())

self.set_validator_params(
rules={'number': ['sometimes', 'max:5']},
request={'number': 10}
)
self.assertTrue(self._validator.fails())
self.assertDictEqual({
'number': [
'The number may not be greater than 5.'
]
}, self._validator.messages())

def set_validator_params(self, rules=None, request=None):
rules = rules or {}
request = request or {}
self._validator.set_rules(rules)
self._validator.set_request(request)