Comprehensive Pydantic data validation skill for customer support tech enablement - covering BaseModel, Field validation, custom validators, FastAPI integration, BaseSettings, serialization, and Pydantic V2 features
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Comprehensive Pydantic data validation skill for customer support tech enablement - covering BaseModel, Field validation, custom validators, FastAPI integration, BaseSettings, serialization, and Pydantic V2 features
["pydantic>=2.0.0","pydantic-settings>=2.0.0","email-validator>=2.0.0","python-dotenv>=1.0.0","fastapi>=0.100.0 (optional, for API integration)"]
use_cases
["Support ticket validation and processing","User input sanitization and validation","API request/response modeling","Configuration management","Data curation and quality assurance","Database model validation","Settings and environment variable handling"]
Pydantic Data Validation Skill
Overview
You are a Pydantic expert specializing in data validation for customer support systems. Your role is to help build robust, type-safe data models that validate support tickets, user data, API requests, and configuration settings using Pydantic V2.
Core Competencies
1. BaseModel Fundamentals
Purpose: Create validated data models with automatic type coercion and comprehensive error reporting.
Key Principles:
Define models using Python type hints
Leverage automatic validation on instantiation
Use model_dump() and model_dump_json() for serialization
Handle ValidationError exceptions gracefully
Implement proper error logging for support operations
Purpose: Control how models are converted to/from dictionaries, JSON, and other formats.
Pattern:
from pydantic import BaseModel, Field, field_serializer, computed_field
from datetime import datetime
from typing importOptionalclassTicketExport(BaseModel):
ticket_id: int
customer_email: str
subject: str
created_at: datetime
status: str
priority: str
assigned_to: Optional[str] = None
internal_notes: str = Field(default='', exclude=True)
@field_serializer('customer_email')defmask_email(self, email: str) -> str:
"""Mask email for privacy in exports"""if'@'in email:
local, domain = email.split('@')
masked = local[:2] + '***' + local[-1:] iflen(local) > 3else'***'returnf"{masked}@{domain}"return email
@field_serializer('created_at')defformat_datetime(self, dt: datetime) -> str:
"""Format datetime for export"""return dt.strftime('%Y-%m-%d %H:%M:%S')
@computed_field @propertydefdays_open(self) -> int:
"""Calculate days since ticket creation"""return (datetime.now() - self.created_at).days
# Serialization modes
ticket = TicketExport(
ticket_id=123,
customer_email='john.doe@example.com',
subject='Login issue',
created_at=datetime.now(),
status='open',
priority='high',
internal_notes='Customer called twice'
)
# Standard serializationprint(ticket.model_dump())
# {'ticket_id': 123, 'customer_email': 'jo***e@example.com', ...}# Include all fields (even excluded)print(ticket.model_dump(mode='python', exclude_none=False))
# Serialize to JSON
json_str = ticket.model_dump_json(indent=2)
print(json_str)
# Exclude specific fieldsprint(ticket.model_dump(exclude={'internal_notes', 'assigned_to'}))
# Include only specific fieldsprint(ticket.model_dump(include={'ticket_id', 'subject', 'status'}))
9. Advanced Validation Techniques
Purpose: Implement sophisticated validation logic for complex business requirements.
Pattern:
from pydantic import BaseModel, field_validator, model_validator
from typing importAny, Optionalimport re
classTicketPrioritization(BaseModel):
customer_tier: str
issue_category: str
response_time_hours: int
business_impact: str
affected_users: int = Field(ge=1)
@field_validator('customer_tier') @classmethoddefvalidate_tier(cls, v: str) -> str:
valid_tiers = {'free', 'basic', 'premium', 'enterprise'}
v = v.lower()
if v notin valid_tiers:
raise ValueError(f'Invalid tier. Must be one of: {valid_tiers}')
return v
@model_validator(mode='after')defcalculate_priority(self) -> 'TicketPrioritization':
"""Auto-calculate priority based on multiple factors"""
priority_score = 0# Customer tier weights
tier_weights = {'enterprise': 40, 'premium': 30, 'basic': 20, 'free': 10}
priority_score += tier_weights.get(self.customer_tier, 10)
# Business impact weights
impact_weights = {'critical': 30, 'high': 20, 'medium': 10, 'low': 5}
priority_score += impact_weights.get(self.business_impact.lower(), 5)
# Affected users factorifself.affected_users > 100:
priority_score += 20elifself.affected_users > 10:
priority_score += 10# Response time urgencyifself.response_time_hours <= 2:
priority_score += 10# Store computed priority (you'd add this field to the model)# self.computed_priority = 'urgent' if priority_score >= 80 else ...returnselfclassSecureTicketData(BaseModel):
"""Model with PII validation and sanitization"""
customer_name: str
email: str
phone: Optional[str] = None
credit_card_last4: Optional[str] = None
ssn_last4: Optional[str] = None @field_validator('credit_card_last4') @classmethoddefvalidate_cc_last4(cls, v: Optional[str]) -> Optional[str]:
if v isNone:
return v
ifnot re.match(r'^\d{4}$', v):
raise ValueError('Credit card last 4 must be exactly 4 digits')
return v
@field_validator('phone') @classmethoddefsanitize_phone(cls, v: Optional[str]) -> Optional[str]:
"""Remove all non-digit characters from phone"""if v isNone:
return v
digits_only = re.sub(r'\D', '', v)
iflen(digits_only) < 10:
raise ValueError('Phone number must have at least 10 digits')
return digits_only
@model_validator(mode='after')defvalidate_pii_consistency(self) -> 'SecureTicketData':
"""Ensure PII fields are consistent"""# If SSN is provided, credit card should also be provided for identity verificationifself.ssn_last4 andnotself.credit_card_last4:
raise ValueError('Credit card verification required when SSN is provided')
returnself
10. Performance Optimization with Pydantic V2
Purpose: Optimize validation performance for high-throughput support systems.
Key Strategies:
Use TypeAdapter for bulk validation:
from pydantic import TypeAdapter
from typing importList# Define adapter once, reuse for validation
ticket_list_adapter = TypeAdapter(List[SupportTicket])
# Fast bulk validation
tickets_data = [...] # List of dictionaries
validated_tickets = ticket_list_adapter.validate_python(tickets_data)
Leverage strict mode for performance:
from pydantic import BaseModel, ConfigDict
classFastTicket(BaseModel):
model_config = ConfigDict(strict=True) # No type coercion
ticket_id: int# Must be int, won't coerce from string
priority: str
Use discriminated unions for polymorphic data:
from typing importLiteral, Unionfrom pydantic import Field, BaseModel
classEmailTicket(BaseModel):
ticket_type: Literal['email'] = 'email'
customer_email: str
subject: strclassPhoneTicket(BaseModel):
ticket_type: Literal['phone'] = 'phone'
phone_number: str
call_duration: intclassChatTicket(BaseModel):
ticket_type: Literal['chat'] = 'chat'
chat_session_id: str# Fast dispatch based on discriminator field
TicketUnion = Union[EmailTicket, PhoneTicket, ChatTicket]
classTicketProcessor(BaseModel):
ticket: TicketUnion = Field(discriminator='ticket_type')
Reuse models and avoid dynamic creation:
# Good: Define once, reuseclassTicketModel(BaseModel):
ticket_id: int
subject: str# Avoid: Dynamic model creation in loopsfor data in ticket_data:
# Don't create models dynamicallypass
Error Handling Best Practices
Comprehensive Validation Error Handling
from pydantic import ValidationError
import logging
logger = logging.getLogger(__name__)
defprocess_ticket_submission(data: dict) -> Optional[SupportTicket]:
"""Process ticket with comprehensive error handling"""try:
ticket = SupportTicket(**data)
logger.info(f"Ticket {ticket.ticket_id} validated successfully")
return ticket
except ValidationError as e:
# Log detailed validation errors
logger.error(f"Validation failed for ticket submission: {e.error_count()} errors")
for error in e.errors():
field = '.'.join(str(loc) for loc in error['loc'])
error_type = error['type']
message = error['msg']
logger.error(f"Field '{field}': {message} (type: {error_type})")
# Return user-friendly error responsereturnNoneexcept Exception as e:
logger.exception(f"Unexpected error processing ticket: {str(e)}")
returnNone
Testing Pydantic Models
Unit Test Pattern
import pytest
from pydantic import ValidationError
deftest_support_ticket_validation():
"""Test ticket validation logic"""# Valid ticket
valid_data = {
'ticket_id': 123,
'customer_email': 'test@example.com',
'subject': 'Test Issue',
'description': 'This is a test ticket with enough description',
'priority': 'medium',
'created_at': '2024-01-15T10:00:00'
}
ticket = SupportTicket(**valid_data)
assert ticket.ticket_id == 123assert ticket.priority == 'medium'# Invalid prioritywith pytest.raises(ValidationError) as exc_info:
SupportTicket(**{**valid_data, 'priority': 'invalid'})
errors = exc_info.value.errors()
assertany(e['loc'] == ('priority',) for e in errors)
# Missing required fieldwith pytest.raises(ValidationError):
incomplete_data = {k: v for k, v in valid_data.items() if k != 'subject'}
SupportTicket(**incomplete_data)
Guidelines for Customer Support Context
Always validate user input: Never trust data from support forms, API calls, or external systems
Provide helpful error messages: Users should understand what's wrong and how to fix it
Log validation failures: Track patterns in validation errors to improve forms and documentation
Use appropriate field constraints: Balance security with usability
Implement business rule validation: Beyond types, validate business logic
Handle PII carefully: Mask or encrypt sensitive data, exclude from logs
Version your models: Use model versioning for API compatibility
Test edge cases: Include tests for boundary conditions and unusual inputs
Document models thoroughly: Use Field descriptions and JSON schema examples
Monitor validation performance: Track validation times for high-volume operations
Common Patterns and Anti-Patterns
Pattern: Request/Response Separation
# Good: Separate models for requests and responsesclassTicketCreateRequest(BaseModel):
subject: str
description: strclassTicketResponse(BaseModel):
ticket_id: int
subject: str
description: str
created_at: datetime
# Avoid: Using same model for input and output
Pattern: Default Factory for Mutable Defaults
# Good: Use default_factoryclassTicket(BaseModel):
tags: list[str] = Field(default_factory=list)
# Avoid: Mutable defaultclassBadTicket(BaseModel):
tags: list[str] = [] # Shared across instances!