Validation
Ryx validates data at two levels: field-level (automatic) and model-level (custom).
Field-Level Validationโ
Built into field definitions:
class Product(Model):
name = CharField(
max_length=200,
min_length=3,
blank=False,
)
price = DecimalField(
max_digits=10,
decimal_places=2,
min_value=0,
max_value=999999,
)
status = CharField(
max_length=20,
choices=["draft", "published", "archived"],
)
email = CharField(max_length=254, validators=[EmailValidator()])
website = CharField(max_length=200, null=True, validators=[URLValidator()])
Built-in Validatorsโ
| Validator | Description |
|---|---|
MaxLengthValidator(n) | String/list length โค n |
MinLengthValidator(n) | String/list length โฅ n |
MaxValueValidator(n) | Value โค n |
MinValueValidator(n) | Value โฅ n |
RangeValidator(min, max) | Value in range |
NotBlankValidator() | Not empty string |
NotNullValidator() | Not None |
RegexValidator(pattern) | Matches regex |
EmailValidator() | Valid email format |
URLValidator() | Valid URL format |
ChoicesValidator(values) | Value in allowed list |
UniqueValueValidator() | Unique in table (DB-enforced) |
Custom Validator (FunctionValidator)โ
from ryx import FunctionValidator
def must_be_even(value):
if value % 2 != 0:
raise ValidationError("Value must be even")
class Product(Model):
code = CharField(
max_length=10,
validators=[FunctionValidator(must_be_even, message="Code must be even")],
)
Model-Level Validationโ
Override clean() for cross-field validation:
class Event(Model):
start_date = DateTimeField()
end_date = DateTimeField()
status = CharField(max_length=20, choices=["draft", "published"])
async def clean(self):
errors = {}
if self.end_date and self.end_date < self.start_date:
errors["end_date"] = ["End date must be after start date"]
if self.status == "published" and not self.start_date:
errors["start_date"] = ["Published events need a start date"]
if errors:
raise ValidationError(errors)
Running Validationโ
product = Product(name="x", price=-1)
# Manual
try:
await product.full_clean()
except ValidationError as e:
print(e.errors)
# โ {
# "name": ["Ensure this value has at least 3 characters."],
# "price": ["Ensure this value is greater than or equal to 0."],
# }
# Automatic โ save() calls full_clean() by default
await product.save() # Raises ValidationError
# Skip validation (for bulk ops)
await product.save(validate=False)
Merging Validation Errorsโ
from ryx import ValidationError
try:
await product.full_clean()
except ValidationError as e:
# e.errors โ {"name": ["Too short"], "price": ["Negative"]}
pass
# Merge multiple errors
errors = ValidationError({"name": ["Invalid"]})
errors.merge(ValidationError({"price": ["Out of range"]}))
# errors.errors โ {"name": ["Invalid"], "price": ["Out of range"]}
Collecting All Errorsโ
full_clean() collects ALL errors from ALL fields before raising โ you get the complete picture at once:
try:
await product.full_clean()
except ValidationError as e:
for field, errors in e.errors.items():
for error in errors:
print(f"{field}: {error}")
Next Stepsโ
โ Signals โ Lifecycle event observers