Schema Integration¶
Advanced Alchemy services support multiple schema libraries for data transformation and validation.
Prerequisites¶
Understanding of Basics recommended.
Schema Libraries¶
Advanced Alchemy services support:
Pydantic - Popular validation library with extensive features
Msgspec - High-performance serialization with native validation
attrs - Lightweight data classes with optional cattrs integration
Pydantic Models¶
Pydantic provides robust validation with extensive ecosystem support:
from pydantic import BaseModel, EmailStr, constr
from typing import Optional
from datetime import datetime
class UserCreate(BaseModel):
name: constr(min_length=1, max_length=100)
email: EmailStr
age: Optional[int] = None
class UserResponse(BaseModel):
id: int
name: str
email: str
age: Optional[int]
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
# Use with service
user_data = service.to_schema(user_model, schema_type=UserResponse)
from pydantic import BaseModel, EmailStr, constr
from datetime import datetime
class UserCreate(BaseModel):
name: constr(min_length=1, max_length=100)
email: EmailStr
age: int | None = None
class UserResponse(BaseModel):
id: int
name: str
email: str
age: int | None
created_at: datetime
updated_at: datetime
model_config = {"from_attributes": True}
# Use with service
user_data = service.to_schema(user_model, schema_type=UserResponse)
Pydantic features:
Comprehensive validation rules
Email, URL, and custom validators
Nested models support
JSON serialization built-in
OpenAPI integration
Note: Pydantic v2 required. Configure with model_config = {"from_attributes": True} for SQLAlchemy compatibility.
Msgspec Structs¶
Msgspec provides high-performance serialization:
from msgspec import Struct
from typing import Optional
from datetime import datetime
class UserCreate(Struct):
name: str
email: str
age: Optional[int] = None
class UserResponse(Struct):
id: int
name: str
email: str
age: Optional[int]
created_at: datetime
updated_at: datetime
# Use with service
user_data = service.to_schema(user_model, schema_type=UserResponse)
from msgspec import Struct
from datetime import datetime
class UserCreate(Struct):
name: str
email: str
age: int | None = None
class UserResponse(Struct):
id: int
name: str
email: str
age: int | None
created_at: datetime
updated_at: datetime
# Use with service
user_data = service.to_schema(user_model, schema_type=UserResponse)
Msgspec characteristics:
Fast serialization/deserialization
Native validation
Type-safe
Smaller memory footprint than Pydantic
Supports JSON, MessagePack, YAML
Attrs Classes¶
Attrs provides lightweight data classes:
from attrs import define
from typing import Optional
from datetime import datetime
@define
class UserCreate:
name: str
email: str
age: Optional[int] = None
@define
class UserResponse:
id: int
name: str
email: str
age: Optional[int]
created_at: datetime
updated_at: datetime
# Use with service
user_data = service.to_schema(user_model, schema_type=UserResponse)
from attrs import define
from datetime import datetime
@define
class UserCreate:
name: str
email: str
age: int | None = None
@define
class UserResponse:
id: int
name: str
email: str
age: int | None
created_at: datetime
updated_at: datetime
# Use with service
user_data = service.to_schema(user_model, schema_type=UserResponse)
Note
Enhanced attrs Support with cattrs: When both attrs and cattrs are installed,
Advanced Alchemy automatically uses cattrs.structure() and cattrs.unstructure()
for improved performance and type-aware serialization. This provides better handling of
complex types, nested structures, and custom converters.
Attrs characteristics:
Lightweight, minimal dependencies
Simple API
cattrs integration for advanced features
Good performance
Schema Conversion¶
to_schema Method¶
Convert models to schemas:
# Single record
user_schema = service.to_schema(user_model, schema_type=UserResponse)
# Multiple records
users_schemas = service.to_schema(users_list, schema_type=UserResponse)
# SQLAlchemy Row objects
row_schema = service.to_schema(row_object, schema_type=UserResponse)
# With pagination
results, total = await service.list_and_count(*filters)
paginated_response = service.to_schema(
results,
total,
filters=filters,
schema_type=UserResponse
)
Supports:
SQLAlchemy model instances
SQLAlchemy Row objects
RowMapping objects
Lists of any of the above
Pagination results with total count and filter metadata
to_model Method¶
Convert schemas to models:
# Automatic in CRUD operations
user = await service.create(UserCreate(name="Alice", email="alice@example.com"))
# Manual conversion (advanced)
user_model = await service.to_model(user_create_data, operation="create")
Operation parameter values:
"create"- Preparing for insertion"update"- Preparing for modificationNone- General conversion
SQLAlchemy Query Result Support¶
Services handle SQLAlchemy query results directly:
from sqlalchemy import select
# Direct support for SQLAlchemy Row objects
query_results = await session.execute(select(User))
rows = query_results.fetchall() # Returns list[Row[Any]]
# Convert Row objects to schema types
user_data = service.to_schema(rows[0], schema_type=UserSchema)
users_paginated = service.to_schema(rows, schema_type=UserSchema)
# Also supports RowMapping objects
row_mapping_results = await session.execute(select(User)).mappings()
mapping_data = service.to_schema(row_mapping_results.first(), schema_type=UserSchema)
Characteristics:
No manual conversion needed
Works with complex queries
Handles aggregations
Preserves type safety
Implementation Patterns¶
Schema Validation in Services¶
Services validate input automatically:
from pydantic import BaseModel, validator
class PostCreate(BaseModel):
title: str
content: str
published: bool = False
@validator("title")
def title_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError("title cannot be empty")
return v
# Validation happens automatically
try:
post = await post_service.create(PostCreate(title="", content="Test"))
except ValueError as e:
print(f"Validation error: {e}")
Pydantic validators run before database operations.
Nested Schemas¶
Handle nested relationships:
from pydantic import BaseModel
class TagResponse(BaseModel):
id: int
name: str
slug: str
model_config = {"from_attributes": True}
class PostResponse(BaseModel):
id: int
title: str
content: str
tags: list[TagResponse] # Nested schema
model_config = {"from_attributes": True}
# Automatic nested conversion
post_with_tags = await post_service.get(post_id)
response = post_service.to_schema(post_with_tags, schema_type=PostResponse)
# response.tags is list of TagResponse instances
Nested schemas convert automatically when models have relationships.
Partial Updates¶
Handle optional fields for updates:
from pydantic import BaseModel
from typing import Optional
class PostUpdate(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
published: Optional[bool] = None
# Update only provided fields
await post_service.update(
post_id,
PostUpdate(title="New Title"), # Only updates title
auto_commit=True
)
from pydantic import BaseModel
class PostUpdate(BaseModel):
title: str | None = None
content: str | None = None
published: bool | None = None
# Update only provided fields
await post_service.update(
post_id,
PostUpdate(title="New Title"), # Only updates title
auto_commit=True
)
Optional fields allow partial updates.
Technical Constraints¶
Schema Library Comparison¶
Different libraries have distinct characteristics:
Pydantic
Validation: Comprehensive validators, custom rules
Performance: Moderate (validation overhead)
Features: Extensive ecosystem, OpenAPI support
Serialization: JSON via
model_dump_json()
Msgspec
Validation: Native type validation
Performance: High (optimized Rust/C core)
Features: Multiple formats (JSON, MessagePack, YAML)
Serialization: Fast native serialization
Attrs
Validation: Basic (with cattrs enhancement)
Performance: High (minimal overhead)
Features: Lightweight, simple API
Serialization: Via cattrs or manual
Choose based on requirements.
Pydantic from_attributes Requirement¶
Pydantic v2 requires explicit configuration:
# ✅ Correct - enables ORM mode
class UserResponse(BaseModel):
id: int
name: str
model_config = {"from_attributes": True}
# ❌ Incorrect - missing configuration
class UserResponse(BaseModel):
id: int
name: str
# Will fail when converting from SQLAlchemy models
Always include model_config = {"from_attributes": True} for Pydantic schemas.
Schema Field Matching¶
Schema fields must match model attributes:
# ✅ Correct - fields match model
class UserResponse(BaseModel):
id: int
name: str # Matches User.name
email: str # Matches User.email
# ❌ Incorrect - field name mismatch
class UserResponse(BaseModel):
id: int
username: str # User model has 'name', not 'username'
# Will fail during conversion
Field names must match model attributes or use aliases.
Next Steps¶
For complex business logic, see Advanced.