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-5.0.1.tar.gz (9.9 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-5.0.1-py3-none-any.whl (15.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for spakky_sqlalchemy-5.0.1.tar.gz
Algorithm Hash digest
SHA256 ba358cf32d25897a80e1a98acec5ef721c48ade5b7abf5ec01c1f02ac3ef5d40
MD5 830530e22a3c8c8f1558dd9f41c8dfa2
BLAKE2b-256 048861af9110224dd344ba85670e271d1f154ae63171f9f5bf7e7e176e12708d

See more details on using hashes here.

Provenance

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

Publisher: publish-package.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-5.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for spakky_sqlalchemy-5.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b5358915a6b0ba80ee7afbdf79833f7bb63480b92195fda07b4178eadac5e4c3
MD5 69dbdae42de923df9cfd378a59c855b2
BLAKE2b-256 5f93a99ab9d94065816ef6155d9236af63d9bdddaa12b0fcd4f78362970cf726

See more details on using hashes here.

Provenance

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

Publisher: publish-package.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