Skip to main content

SQLAlchemy plugin for Spakky framework

Project description

Spakky SQLAlchemy

SQLAlchemy integration plugin for Spakky Framework.

Installation

pip install spakky-sqlalchemy

Or install via Spakky extras:

pip install spakky[sqlalchemy]

Configuration

Set environment variables with the SPAKKY_SQLALCHEMY__ prefix:

# Required
export SPAKKY_SQLALCHEMY__CONNECTION_STRING="postgresql+psycopg://user:pass@localhost/db"

# Engine options (optional)
export SPAKKY_SQLALCHEMY__ECHO="false"
export SPAKKY_SQLALCHEMY__ECHO_POOL="false"

# Connection pool options (optional)
export SPAKKY_SQLALCHEMY__POOL_SIZE="5"
export SPAKKY_SQLALCHEMY__POOL_MAX_OVERFLOW="10"
export SPAKKY_SQLALCHEMY__POOL_TIMEOUT="30.0"
export SPAKKY_SQLALCHEMY__POOL_RECYCLE="-1"
export SPAKKY_SQLALCHEMY__POOL_PRE_PING="false"

# Session options (optional)
export SPAKKY_SQLALCHEMY__SESSION_AUTOFLUSH="true"
export SPAKKY_SQLALCHEMY__SESSION_EXPIRE_ON_COMMIT="true"

# Transaction options (optional)
export SPAKKY_SQLALCHEMY__AUTOCOMMIT="true"

# Async support (optional)
export SPAKKY_SQLALCHEMY__SUPPORT_ASYNC_MODE="true"

Usage

Defining Tables with Domain Mapping

Use @Table decorator and inherit from AbstractTable to define ORM tables with domain model mapping:

from uuid import UUID
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
from spakky.plugins.sqlalchemy.orm.table import AbstractTable, Table
from spakky.domain.models.aggregate_root import AbstractAggregateRoot

# Domain model
class User(AbstractAggregateRoot[UUID]):
    id: UUID
    name: str
    email: str

# Table definition with domain mapping
@Table()
class UserTable(AbstractMappableTable[User]):
    __tablename__ = "users"

    id: Mapped[UUID] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(255))
    email: Mapped[str] = mapped_column(String(255))

    @classmethod
    def from_domain(cls, domain: User) -> "UserTable":
        return cls(id=domain.id, name=domain.name, email=domain.email)

    def to_domain(self) -> User:
        return User(id=self.id, name=self.name, email=self.email)

Repository Implementation

Extend AbstractGenericRepository or AbstractAsyncGenericRepository:

from uuid import UUID
from spakky.data.stereotype.repository import Repository
from spakky.plugins.sqlalchemy.persistency.repository import (
    AbstractGenericRepository,
    AbstractAsyncGenericRepository,
)

# Synchronous repository
@Repository()
class UserRepository(AbstractGenericRepository[User, UUID]):
    pass  # CRUD methods inherited

# Asynchronous repository
@Repository()
class AsyncUserRepository(AbstractAsyncGenericRepository[User, UUID]):
    pass  # Async CRUD methods inherited

Using Transactions

Use the @Transactional decorator from spakky-data:

from spakky.core.stereotype.usecase import UseCase
from spakky.data.aspects.transactional import Transactional

@UseCase()
class CreateUserUseCase:
    def __init__(self, user_repo: UserRepository) -> None:
        self._user_repo = user_repo

    @Transactional()
    def execute(self, name: str, email: str) -> User:
        user = User.create(name, email)
        return self._user_repo.save(user)

Async Transactions

The same @Transactional() decorator works for both sync and async methods. The framework automatically selects the correct aspect based on whether the method is a coroutine.

from spakky.data.aspects.transactional import Transactional

@UseCase()
class AsyncCreateUserUseCase:
    def __init__(self, user_repo: AsyncUserRepository) -> None:
        self._user_repo = user_repo

    @Transactional()
    async def execute(self, name: str, email: str) -> User:
        user = User.create(name, email)
        return await self._user_repo.save(user)

Accessing Session Directly

For complex queries, access the SQLAlchemy session directly in QueryUseCase. Following CQRS principles, queries should be implemented directly rather than adding query methods to repositories.

from spakky.core.common.mutability import immutable
from spakky.core.stereotype.usecase import UseCase
from spakky.domain.application.query import AbstractQuery, IAsyncQueryUseCase
from spakky.plugins.sqlalchemy.persistency.session_manager import AsyncSessionManager


@immutable
class FindUserByEmailQuery(AbstractQuery):
    email: str


@immutable
class UserDTO:
    id: UUID
    name: str
    email: str


@UseCase()
class FindUserByEmailUseCase(IAsyncQueryUseCase[FindUserByEmailQuery, UserDTO | None]):
    def __init__(self, session_manager: AsyncSessionManager) -> None:
        self._session_manager = session_manager

    async def run(self, query: FindUserByEmailQuery) -> UserDTO | None:
        result = await self._session_manager.session.execute(
            select(UserTable).where(UserTable.email == query.email)
        )
        table = result.scalar_one_or_none()
        if table is None:
            return None
        return UserDTO(id=table.id, name=table.name, email=table.email)

Schema Registry

Access SchemaRegistry to get table metadata for migrations:

from spakky.plugins.sqlalchemy.orm.schema_registry import SchemaRegistry

@Pod()
class MigrationService:
    def __init__(self, schema_registry: SchemaRegistry) -> None:
        self._schema_registry = schema_registry

    def get_metadata(self) -> MetaData:
        return self._schema_registry.metadata

Features

  • Domain-Table mapping: Bidirectional conversion between domain models and ORM tables
  • Generic repositories: Pre-built CRUD operations with composite PK support
  • Sync and Async support: Full support for both synchronous and asynchronous operations
  • Scoped sessions: Thread/context-safe session management
  • Optimistic locking: Built-in VersionConflictError for concurrent updates
  • Schema registry: Centralized table metadata management

Components

Component Description
@Table Decorator for registering ORM tables with domain mapping
AbstractTable Base class for ORM tables with from_domain/to_domain
AbstractGenericRepository Sync repository with CRUD operations
AbstractAsyncGenericRepository Async repository with CRUD operations
SchemaRegistry Central registry for table-domain mappings
SessionManager Sync scoped session management
AsyncSessionManager Async scoped session management
Transaction Sync transaction implementation
AsyncTransaction Async transaction implementation
ConnectionManager Sync SQLAlchemy engine lifecycle
AsyncConnectionManager Async SQLAlchemy engine lifecycle
SQLAlchemyConnectionConfig Configuration via environment variables

Repository Methods

Both sync and async repositories provide:

Method Description
get(id) Get aggregate by ID, raises EntityNotFoundError if not found
get_or_none(id) Get aggregate by ID, returns None if not found
contains(id) Check if aggregate exists
range(ids) Get multiple aggregates by ID list
save(aggregate) Save (insert or update) an aggregate
save_all(aggregates) Save multiple aggregates
delete(aggregate) Delete an aggregate
delete_all(aggregates) Delete multiple aggregates

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spakky_sqlalchemy-6.2.0.tar.gz (12.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spakky_sqlalchemy-6.2.0-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

File details

Details for the file spakky_sqlalchemy-6.2.0.tar.gz.

File metadata

  • Download URL: spakky_sqlalchemy-6.2.0.tar.gz
  • Upload date:
  • Size: 12.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spakky_sqlalchemy-6.2.0.tar.gz
Algorithm Hash digest
SHA256 a754054e52fcb998385c872649d179e625c5a1a9c14ebcaf5dcc52f7dd56d1a4
MD5 c8a7ad578684c5f7178d0147c22c02aa
BLAKE2b-256 f8e8e315e5ba2b06a2053bbbc2900ec15a6b20e74b0704a2e06ef8e07b80e438

See more details on using hashes here.

Provenance

The following attestation bundles were made for spakky_sqlalchemy-6.2.0.tar.gz:

Publisher: release.yml on E5presso/spakky-framework

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spakky_sqlalchemy-6.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for spakky_sqlalchemy-6.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2ec8067ff599b1982854d85886de72cc641842fd3c0ceaa9283938ef71bfb3c3
MD5 aa016144a110c36586315897aab45108
BLAKE2b-256 bc3f2d8917f03532e7500020b0c9c32b1471c67eca40b2a83ca540618e862627

See more details on using hashes here.

Provenance

The following attestation bundles were made for spakky_sqlalchemy-6.2.0-py3-none-any.whl:

Publisher: release.yml on E5presso/spakky-framework

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page