Documentation Index
Fetch the complete documentation index at: https://docs.tasteful.heka.ai/llms.txt
Use this file to discover all available pages before exploring further.
Repositories
Repositories are the data access layer of Tasteful flavors. They provide an abstraction over data storage and retrieval, isolating business logic from database-specific operations. Repositories handle all interactions with databases, external APIs, and other data sources while providing a clean, testable interface for services.
What is a Repository?
A Repository in Tasteful is a class that inherits from BaseRepository or SQLModelRepository and encapsulates data access logic. Repositories are responsible for:
- Data Access Abstraction: Hiding database-specific implementation details from business logic
- CRUD Operations: Providing Create, Read, Update, and Delete functionality
- Query Management: Handling complex queries and data filtering
- Connection Management: Managing database connections and sessions
- Data Mapping: Converting between database records and domain objects
- Transaction Support: Ensuring data consistency through proper transaction handling
Repositories act as the single point of data access for each entity or aggregate, providing a clean interface that services can use without knowing about database specifics.
Core Concepts
Repository Hierarchy
Tasteful provides two base repository classes:
BaseRepository
The foundational repository class that provides basic structure:
from tasteful.repositories import BaseRepository
class BaseRepository:
def __init__(self, database_url: str = None, echo: bool = False):
self.database_url = database_url
self.echo = echo
self._engine = None
SQLModelRepository
The recommended repository class for SQLModel-based applications:
from tasteful.repositories import SQLModelRepository
class UserRepository(SQLModelRepository):
def __init__(self, database_url: str, echo: bool = False):
super().__init__(database_url=database_url, echo=echo)
Session Management
SQLModelRepository provides robust session management through context managers:
class UserRepository(SQLModelRepository):
def get_user_by_id(self, user_id: int) -> Optional[User]:
with self.get_session() as session:
return session.get(User, user_id)
def create_user(self, user_data: dict) -> User:
with self.get_session() as session:
user = User(**user_data)
session.add(user)
session.commit()
session.refresh(user)
return user
Key features of session management:
- Automatic Rollback: Sessions automatically rollback on exceptions
- Connection Pooling: Efficient database connection management
- Transaction Safety: Proper transaction boundaries for data consistency
Implementation
Basic Repository Implementation
Here’s a complete example of a repository implementation:
from typing import List, Optional
from sqlmodel import select
from tasteful.repositories import SQLModelRepository
class UserRepository(SQLModelRepository):
def __init__(self, database_url: str):
super().__init__(database_url=database_url, echo=False)
def get_by_id(self, user_id: int) -> Optional[User]:
"""Get a user by ID."""
with self.get_session() as session:
return session.get(User, user_id)
def get_by_email(self, email: str) -> Optional[User]:
"""Get a user by email address."""
with self.get_session() as session:
statement = select(User).where(User.email == email)
return session.exec(statement).first()
def get_all(self, skip: int = 0, limit: int = 100) -> List[User]:
"""Get all users with pagination."""
with self.get_session() as session:
statement = select(User).offset(skip).limit(limit)
return list(session.exec(statement).all())
def create(self, user_data: dict) -> User:
"""Create a new user."""
with self.get_session() as session:
user = User(**user_data)
session.add(user)
session.commit()
session.refresh(user)
return user
def update(self, user_id: int, user_data: dict) -> Optional[User]:
"""Update an existing user."""
with self.get_session() as session:
user = session.get(User, user_id)
if not user:
return None
for key, value in user_data.items():
setattr(user, key, value)
session.add(user)
session.commit()
session.refresh(user)
return user
def delete(self, user_id: int) -> bool:
"""Delete a user by ID."""
with self.get_session() as session:
user = session.get(User, user_id)
if not user:
return False
session.delete(user)
session.commit()
return True
Advanced Query Patterns
Repositories can implement complex queries while maintaining clean interfaces:
class UserRepository(SQLModelRepository):
def find_active_users_by_role(self, role: str) -> List[User]:
"""Find active users with a specific role."""
with self.get_session() as session:
statement = select(User).where(
User.is_active == True,
User.role == role
)
return list(session.exec(statement).all())
def get_users_created_after(self, date: datetime) -> List[User]:
"""Get users created after a specific date."""
with self.get_session() as session:
statement = select(User).where(User.created_at > date)
return list(session.exec(statement).all())
def count_users_by_status(self, status: str) -> int:
"""Count users by status."""
with self.get_session() as session:
statement = select(User).where(User.status == status)
return len(list(session.exec(statement).all()))
Database Initialization
Repositories can handle database table creation:
class UserRepository(SQLModelRepository):
def initialize_database(self):
"""Create database tables if they don't exist."""
self.create_db_and_tables()
Integration with Other Components
Service Integration
Repositories are injected into services through dependency injection:
from tasteful.base_flavor import BaseService
class UserService(BaseService):
def __init__(self, user_repository: UserRepository):
super().__init__()
self.user_repository = user_repository
def create_user(self, user_data: dict) -> User:
# Business logic validation
if not user_data.get('email'):
raise ValueError("Email is required")
# Check if user already exists
existing_user = self.user_repository.get_by_email(user_data['email'])
if existing_user:
raise ValueError("User with this email already exists")
# Delegate to repository for data persistence
return self.user_repository.create(user_data)
Configuration Integration
Repositories receive configuration through dependency injection:
class UserRepository(SQLModelRepository):
def __init__(self, app_config: AppConfig):
super().__init__(
database_url=app_config.database_url,
echo=app_config.debug_sql
)
self.app_config = app_config
Flavor Integration
Repositories are registered in flavor containers:
from tasteful.base_flavor import BaseFlavor
class UserFlavor(BaseFlavor):
def __init__(self):
super().__init__()
# Register repository
self.container.repository.add_singleton(
UserRepository,
app_config=self.container.config.app_config
)
# Register service with repository dependency
self.container.service.add_singleton(
UserService,
user_repository=self.container.repository.user_repository
)
Best Practices
Do’s
- Single Responsibility: Each repository should handle one entity or aggregate
- Interface Consistency: Use consistent method naming across repositories (get_by_id, create, update, delete)
- Session Management: Always use the
get_session() context manager for database operations
- Error Handling: Let database exceptions bubble up to services for proper business logic handling
- Query Optimization: Use appropriate indexes and query patterns for performance
- Transaction Boundaries: Keep transactions as short as possible while maintaining consistency
Don’ts
- Business Logic: Don’t put business rules in repositories - they belong in services
- Direct Database Access: Don’t bypass the repository pattern by accessing the database directly from services
- Session Leaks: Don’t store sessions as instance variables - always use context managers
- Complex Joins: Avoid overly complex queries that are hard to maintain - consider breaking them down
- Tight Coupling: Don’t make repositories dependent on other repositories directly