Validation¶
Keywords: validation rules, custom validations, error messages, request validation, form validator, required validation
Asok has a built-in validator. It’s used automatically by Form, but you can also use it standalone.
With Form (recommended)¶
form = Form({
'email': Form.email('Email', 'required|email'),
}, request)
if form.validate():
# Valid
Fail-fast validation¶
By default, validate() returns a boolean. If you prefer exceptions, use raise_error=True:
from asok import ValidationError
try:
form.validate(raise_error=True)
except ValidationError as e:
# Handle error...
print(e.errors) # {'email': 'Invalid email address.'}
When raise_error=True is used and the form is invalid, a ValidationError is raised. This is useful for APIs or when you want the global exception handler to catch and display the error automatically.
See Forms for full docs.
Standalone usage¶
from asok import Validator
v = Validator(request.form, request.files)
v.rules({
'name': 'required|min:2',
'email': 'required|email',
'age': 'required|numeric',
})
if v.validate():
# All good
else:
errors = v.errors # {'email': 'Invalid email address.'}
Manual Error Injection¶
Sometimes you need to add an error based on complex logic that isn’t covered by a standard rule. You can manually set the _error attribute of any form field after calling validate():
if form.validate():
# Additional manual check
if request.user.is_blacklisted(form.email.value):
form.email._error = "This email is not allowed."
# Check if manual errors were added
if not form.errors:
# Proceed with success
Available rules¶
Rule |
Example |
Description |
|---|---|---|
|
|
Field must not be empty |
|
|
Must be a valid email |
|
|
Minimum character length |
|
|
Maximum character length |
|
|
Must be a numeric value |
|
|
Must be exactly N digits |
|
|
Must be a boolean (true, false, 1, 0, yes, no) |
|
|
Must be a valid URL (http/https) |
|
|
Must be a valid URL slug (a-z, 0-9, dashes) |
|
|
Must be a valid UUID (v4 format) |
|
|
Must be a valid date (YYYY-MM-DD) |
|
|
Must be a valid month (YYYY-MM) |
|
|
Numeric value between min and max |
|
|
Must be one of the listed values |
|
|
Must match a regex pattern |
|
|
Only letters allowed |
|
|
Letters and numbers allowed |
|
|
Field |
|
|
Must match another field’s value |
|
|
Must not exist in database (Model,field) |
|
|
File extension whitelist |
|
|
Max file size (K, M, G) |
|
|
Must be valid base64 encoded data |
|
|
Must be a valid telephone number |
|
|
Must be a valid hex color (#RRGGBB or #RGB) |
|
|
Must be valid JSON format |
|
|
Validates start <= end in range JSON |
|
|
Date must be >= today |
Combining rules¶
Separate rules with |:
'required|email|max:255'
'required|min:8|confirmed'
'numeric|min:0|max:100'
Custom error messages¶
v.rule('email', 'required|email', {
'required': 'We need your email.',
'email': 'That doesn\'t look like an email.',
})
Or with rules():
v.rules({
'email': ('required|email', {
'required': 'We need your email.',
'email': 'Invalid email.',
}),
'name': 'required|min:2', # Default messages
})
Password confirmation¶
# Form fields: password and password_confirmation
v.rules({
'password': 'required|min:8|confirmed',
})
# Checks that request.form['password_confirmation'] == request.form['password']
File validation¶
v = Validator(request.form, request.files)
v.rules({
'avatar': 'required|ext:jpg,png|size:2M',
})
Database uniqueness¶
v.rules({
'email': 'required|email|unique:User,email',
})
# Checks User.find(email=value) — fails if found
i18n¶
When used via Form.validate(), error messages are automatically translated using request.__(). No extra setup needed.
Asok looks up locale keys with a v_ prefix. Add these to your locale files:
// src/locales/en.json
{
"v_required": "This field is required.",
"v_email": "Invalid email address.",
"v_min": "Minimum {arg} characters.",
"v_max": "Maximum {arg} characters.",
"v_numeric": "Must be a numeric value.",
"v_digits": "Must be exactly {arg} digits.",
"v_url": "Invalid URL.",
"v_slug": "Invalid slug format.",
"v_uuid": "Invalid UUID format.",
"v_boolean": "Must be a boolean value.",
"v_date": "Invalid date format.",
"v_month": "Invalid month format (use YYYY-MM).",
"v_confirmed": "Confirmation does not match.",
"v_unique": "This value is already taken.",
"v_base64": "Invalid base64 encoded data.",
"v_tel": "Invalid telephone number.",
"v_color": "Invalid color format (use #RRGGBB).",
"v_json": "Invalid JSON format.",
"v_daterange_order": "End date cannot be before start date.",
"v_daterange_future": "Date range must start in the future.",
"v_daterange_invalid": "Invalid date range format."
}
// src/locales/fr.json
{
"v_required": "Ce champ est obligatoire.",
"v_email": "Adresse email invalide.",
"v_min": "Minimum {arg} caractères.",
"v_max": "Maximum {arg} caractères.",
"v_numeric": "Doit être une valeur numérique.",
"v_digits": "Doit contenir exactement {arg} chiffres.",
"v_url": "URL invalide.",
"v_slug": "Format de slug invalide.",
"v_uuid": "Format de UUID invalide.",
"v_boolean": "Doit être une valeur booléenne.",
"v_date": "Format de date invalide.",
"v_month": "Format de mois invalide (utilisez YYYY-MM).",
"v_confirmed": "La confirmation ne correspond pas.",
"v_unique": "Cette valeur est déjà prise.",
"v_base64": "Données base64 invalides.",
"v_tel": "Numéro de téléphone invalide.",
"v_color": "Format de couleur invalide (utilisez #RRGGBB).",
"v_json": "Format JSON invalide.",
"v_daterange_order": "La date de fin ne peut pas être avant la date de début.",
"v_daterange_future": "La période doit commencer dans le futur.",
"v_daterange_invalid": "Format de période invalide."
}
The {arg} placeholder is replaced with the rule argument (e.g., "Minimum 2 characters.").
Resolution order: custom message per-field > translated default (v_ key) > hardcoded English default.
Custom rules¶
Register a global custom rule using register_rule(name, fn, message).
The function fn receives (value, arg, data) and should return True if the value is valid.
from asok import register_rule
# Register a rule that checks if a string is uppercase
def is_uppercase(value, arg, data):
return str(value).isupper()
register_rule('uppercase', is_uppercase, 'Must be uppercase.')
Usage in rules:
v.rules({
'code': 'required|uppercase',
})
You can also access the arg (the value after the :):
# Rule: startswith:ABC
def starts_with(value, arg, data):
return str(value).startswith(arg)
register_rule('startswith', starts_with, 'Must start with {arg}.')