Database & Persistence
Lexigram uses a protocol-first approach to data persistence. By abstracting the database layer behind the Repository Pattern, your domain logic stays clean and testable.
For complete API references and advanced performance tuning, see the lexigram-sql package.
1. The Repository Pattern
Section titled “1. The Repository Pattern”Instead of leaking ORM details into your services, you define Protocols (Contracts) that describe how data is accessed.
Defining the Protocol
Section titled “Defining the Protocol”from typing import Protocol, runtime_checkablefrom lexigram.result import Resultfrom my_app.domain.models import Product
@runtime_checkableclass ProductRepository(Protocol): async def find(self, product_id: str) -> Result[Product, str]: ... async def save(self, product: Product) -> Result[None, str]: ...Implementing with SQL
Section titled “Implementing with SQL”In your infrastructure layer, implement the protocol using your preferred database client.
from lexigram import singletonfrom lexigram.result import Resultfrom my_app.domain.models import Productfrom my_app.repositories.base import ProductRepository
@singletonclass SqlProductRepository(ProductRepository): """SQLAlchemy implementation of the ProductRepository contract."""
async def find(self, product_id: str) -> Result[Product, str]: # Implementation here pass
async def save(self, product: Product) -> Result[None, str]: # Implementation here passRegistering with DI
Section titled “Registering with DI”Use a Service Provider to bind your implementation to the protocol. This allows you to swap the real database for an in-memory version during testing.
async def register(self, container: ContainerRegistrarProtocol) -> None: container.singleton(ProductRepository, SqlProductRepository)For more on registration patterns, see Dependency Injection.
2. Model Definitions
Section titled “2. Model Definitions”Lexigram models are standard dataclasses or SQLAlchemy models that mirror your domain entities.
from dataclasses import dataclass
@dataclassclass Product: id: str name: str stock: int = 03. Database Configuration
Section titled “3. Database Configuration”Configure your database connection in application.yaml:
database: url: "postgresql+asyncpg://user:pass@localhost/dbname" echo: false pool_size: 20 pool_timeout: 304. Async Connection Pattern
Section titled “4. Async Connection Pattern”Lexigram uses async database connections throughout:
from lexigram import Applicationfrom lexigram.config import LexigramConfig
async def main(): config = LexigramConfig.from_yaml() db_config = config.get_section("database", DatabaseConfig)
async with Application.boot(config=config) as app: db = await app.container.resolve(DatabaseProviderProtocol) # Use db.session() for scoped transactions5. Transaction Management
Section titled “5. Transaction Management”Use scoped sessions for request-bound transactions:
async def register(self, container: ContainerRegistrarProtocol) -> None: container.scoped(DbSession, create_session_factory())Then resolve within a scope:
async with container.scope() as scoped: session = await scoped.resolve(DbSession) result = await session.execute("SELECT * FROM products")