ValidatedModel API Reference
The ValidatedModel
class is the core component of PyValidX that extends Pydantic's BaseModel
to support custom field validation.
Class Definition
class ValidatedModel(BaseModel):
'''
Model for validation.
Args:
**data (Any): The data to validate.
Raises:
ValidationException: If the validation fails.
'''
Configuration
The model comes with the following default configuration:
model_config = ConfigDict(
validate_assignment=True, # Validates data when fields are assigned
extra='forbid', # Prevents extra fields not defined in the model
str_strip_whitespace=True, # Automatically strips whitespace from strings
)
Configuration Options
validate_assignment: bool
When True
, validates field values when they are assigned after model creation.
Default: True
Example:
user = User(name='John', email='john@example.com')
user.email = 'invalid-email' # Raises ValidationException
extra: str
Controls how extra fields (not defined in the model) are handled.
Options:
- 'forbid'
: Raises an error if extra fields are provided
- 'allow'
: Allows extra fields
- 'ignore'
: Ignores extra fields silently
Default: 'forbid'
str_strip_whitespace: bool
When True
, automatically strips leading and trailing whitespace from string fields.
Default: True
Example:
Methods
__init__(**data) -> None
Creates a new instance of the validated model.
Parameters:
- **data
: Keyword arguments representing field values
Raises:
- ValidationException
: If any field validation fails
Example:
dict() -> Dict[str, Any]
Returns the model as a dictionary (inherited from Pydantic).
Returns:
- Dict[str, Any]
: Dictionary representation of the model
Example:
user = User(name='John', email='john@example.com')
user_dict = user.dict()
# {'name': 'John', 'email': 'john@example.com'}
json() -> str
Returns the model as a JSON string (inherited from Pydantic).
Returns:
- str
: JSON representation of the model
Example:
user = User(name='John', email='john@example.com')
user_json = user.json()
# '{'name': 'John', 'email': 'john@example.com'}'
copy() -> 'ValidatedModel'
Creates a copy of the model (inherited from Pydantic).
Returns:
- ValidatedModel
: A new instance with the same field values
Example:
Class Methods
parse_obj(obj: Dict[str, Any]) -> 'ValidatedModel'
Creates a model instance from a dictionary (inherited from Pydantic).
Parameters:
- obj
: Dictionary containing field values
Returns:
- ValidatedModel
: New model instance
Raises:
- ValidationException
: If validation fails
Example:
parse_raw(data: str) -> 'ValidatedModel'
Creates a model instance from a JSON string (inherited from Pydantic).
Parameters:
- data
: JSON string containing field values
Returns:
- ValidatedModel
: New model instance
Raises:
- ValidationException
: If validation fails
Example:
Usage Examples
Basic Model Definition
from pyvalidx import ValidatedModel, field_validated
from pyvalidx.core import is_required, min_length
from pyvalidx.string import is_email
class User(ValidatedModel):
name: str = field_validated(is_required(), min_length(2))
email: str = field_validated(is_required(), is_email())
age: int
bio: str = None # Optional field without validation
Custom Configuration
from pydantic import ConfigDict
class FlexibleUser(ValidatedModel):
model_config = ConfigDict(
validate_assignment=False, # Don't validate on assignment
extra='allow', # Allow extra fields
str_strip_whitespace=False # Don't strip whitespace
)
name: str = field_validated(is_required())
email: str = field_validated(is_email())
Inheritance
class BaseUser(ValidatedModel):
name: str = field_validated(is_required(), min_length(2))
email: str = field_validated(is_required(), is_email())
class AdminUser(BaseUser):
admin_level: int = field_validated(is_required(), min_value(1))
permissions: list = field_validated(is_required(), is_not_empty())
class SuperUser(AdminUser):
super_powers: list = field_validated(is_required())
Optional Fields
from typing import Optional
class UserProfile(ValidatedModel):
# Required fields
username: str = field_validated(is_required(), min_length(3))
email: str = field_validated(is_required(), is_email())
# Optional fields with validation
bio: Optional[str] = field_validated(max_length(500))
website: Optional[str] = field_validated(is_url())
# Optional fields without validation
avatar_url: Optional[str] = None
last_login: Optional[str] = None
Complex Validation
from pyvalidx.core import same_as, required_if
from pyvalidx.string import is_strong_password
class RegistrationModel(ValidatedModel):
username: str = field_validated(
is_required('Username is required'),
min_length(3, 'Username must be at least 3 characters'),
max_length(20, 'Username must be at most 20 characters')
)
email: str = field_validated(
is_required('Email is required'),
is_email('Please provide a valid email address')
)
password: str = field_validated(
is_required('Password is required'),
min_length(8, 'Password must be at least 8 characters'),
is_strong_password('Password is not strong enough')
)
confirm_password: str = field_validated(
is_required('Password confirmation is required'),
same_as('password', 'Passwords must match')
)
terms_accepted: bool = field_validated(
is_required('You must accept the terms'),
custom(lambda x, _: x is True, 'Terms must be accepted')
)
Model with Custom Validators
from pyvalidx.core import custom
def validate_age_range(value, context=None) -> bool:
'''
Custom validator for age range.
Args:
value (Any): Value to validate
context (Dict[str, Any], optional): Context containing other field values
Returns:
bool: True if validation passes, False otherwise
'''
if value is None:
return True
return 13 <= value <= 120
def validate_username_format(value, context=None) -> bool:
'''
Custom validator for username format.
Args:
value (Any): Value to validate
context (Dict[str, Any], optional): Context containing other field values
Returns:
bool: True if validation passes, False otherwise
'''
if value is None:
return True
import re
return re.match(r'^[a-zA-Z0-9_]+$', value) is not None
class CustomUser(ValidatedModel):
username: str = field_validated(
is_required(),
custom(validate_username_format, 'Username can only contain letters, numbers, and underscores')
)
age: int = field_validated(
is_required(),
custom(validate_age_range, 'Age must be between 13 and 120')
)
Error Handling
from pyvalidx.exception import ValidationException
def create_user_safely(user_data: dict[str, Any]) -> dict[str, Any]:
'''
Safely create a user with proper error handling.
Args:
user_data (dict[str, Any]): User data to create
Returns:
dict[str, Any]: Result of the operation
'''
try:
user = User(**user_data)
return {'success': True, 'user': user}
except ValidationException as e:
return {
'success': False,
'errors': e.validations,
'status_code': e.status_code
}
# Usage
result = create_user_safely({
'name': '',
'email': 'invalid-email'
})
if not result['success']:
print('Validation failed:')
for field, error in result['errors'].items():
print(f'- {field}: {error}')
Testing Models
import pytest
def test_valid_user_creation() -> None:
'''
Test creating a valid user.
Returns:
None
'''
user = User(
name='John Doe',
email='john@example.com',
age=25
)
assert user.name == 'John Doe'
assert user.email == 'john@example.com'
assert user.age == 25
def test_invalid_user_creation() -> None:
'''
Test validation errors on invalid data.
Returns:
None
'''
with pytest.raises(ValidationException) as exc_info:
User(name='', email='invalid-email', age=-5)
errors = exc_info.value.validations
assert 'name' in errors
assert 'email' in errors
def test_user_serialization() -> None:
'''
Test model serialization methods.
Returns:
None
'''
user = User(name='John', email='john@example.com', age=25)
# Test dict conversion
user_dict = user.dict()
assert isinstance(user_dict, dict)
assert user_dict['name'] == 'John'
# Test JSON conversion
user_json = user.json()
assert isinstance(user_json, str)
assert 'John' in user_json
Best Practices
- Use type hints: Always provide proper type annotations for fields
- Validate required fields: Use
is_required()
for mandatory fields - Order validators logically: Place fast validators before slow ones
- Provide meaningful error messages: Help users understand validation failures
- Test thoroughly: Include tests for both valid and invalid data
- Document complex models: Add docstrings for business logic
- Use inheritance wisely: Share common validation rules through base classes
- Handle optional fields properly: Use
Optional
type hints and consider None values