Skip to main content

Pico-ioc integration for SQLAlchemy. Adds Spring-style transactional support, configuration, and helpers.

Project description

๐Ÿ“ฆ pico-sqlalchemy

PyPI Ask DeepWiki License: MIT CI (tox matrix) codecov Quality Gate Status Duplicated Lines (%) Maintainability Rating

Pico-SQLAlchemy

Pico-SQLAlchemy integrates Pico-IoC with SQLAlchemy, providing real inversion of control for your persistence layer, with declarative repositories, transactional boundaries, and clean architectural isolation.

It brings constructor-based dependency injection, transparent transaction management, and a repository pattern inspired by the elegance of Spring Data โ€” but using pure Python, Pico-IoC, and SQLAlchemyโ€™s ORM.

๐Ÿ Requires Python 3.10+ ๐Ÿš€ Async-Native: Built entirely on SQLAlchemy's async ORM (AsyncSession, create_async_engine). ๐Ÿงฉ Works with SQLAlchemy 2.0+ ORM ๐Ÿ”„ Automatic async transaction management ๐Ÿงช Fully testable without a running DB

With Pico-SQLAlchemy you get the expressive power of SQLAlchemy with proper IoC, clean layering, and annotation-driven transactions.


๐ŸŽฏ Why pico-sqlalchemy

SQLAlchemy is powerful, but most applications end up with raw session handling, manual transaction scopes, or ad-hoc repository patterns.

Pico-SQLAlchemy provides:

  • Constructor-injected repositories and services
  • Declarative @transactional boundaries
  • REQUIRES_NEW, READ_ONLY, MANDATORY, and all familiar propagation modes
  • SessionManager that centralizes engine/session lifecycle
  • Clean decoupling from frameworks (FastAPI, Flask, CLI, workers)
Concern SQLAlchemy Default pico-sqlalchemy
Managing sessions Manual AsyncSession() Automatic
Transactions Explicit await commit() / await rollback() Declarative @transactional
Repository pattern DIY, inconsistent First-class @repository
Dependency injection None IoC-driven constructor injection
Testability Manual setup Container-managed + overrides

๐Ÿงฑ Core Features

  • Repository classes with @repository
  • Declarative transactions via @transactional
  • Full propagation semantics (REQUIRED, REQUIRES_NEW, MANDATORY, etc.)
  • Automatic AsyncSession lifecycle
  • Centralized AsyncEngine + session factory via SessionManager
  • Transaction-aware get_session() for repository methods
  • Plug-and-play integration with any Pico-IoC app (FastAPI, CLI tools, workers, event handlers)

๐Ÿ“ฆ Installation

pip install pico-sqlalchemy

Also install pico-ioc, sqlalchemy, and an async driver:

pip install pico-ioc sqlalchemy
pip install aiosqlite  # For SQLite
# pip install asyncpg    # For PostgreSQL

๐Ÿš€ Quick Example

Define your model:

from sqlalchemy import Integer, String
from pico_sqlalchemy import AppBase, Mapped, mapped_column

class User(AppBase):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    username: Mapped[str] = mapped_column(String(50))

Define a repository:

from sqlalchemy.future import select
from pico_sqlalchemy import repository, transactional, get_session, SessionManager

@repository
class UserRepository:
    def __init__(self, manager: SessionManager):
        self.manager = manager

    @transactional
    async def save(self, user: User) -> User:
        session = get_session(self.manager)
        session.add(user)
        return user

    @transactional(read_only=True)
    async def find_all(self) -> list[User]:
        session = get_session(self.manager)
        stmt = select(User).order_by(User.username)
        result = await session.scalars(stmt)
        return list(result.all())

Define a service:

from pico_ioc import component

@component
class UserService:
    def __init__(self, repo: UserRepository):
        self.repo = repo

    @transactional
    async def create(self, name: str) -> User:
        user = User(username=name)
        user = await self.repo.save(user)
        
        session = get_session(self.repo.manager)
        await session.flush()
        await session.refresh(user)
        return user

Initialize Pico-IoC and run:

import asyncio
from pico_ioc import init, configuration, DictSource

config = configuration(DictSource({
    "database": {
        "url": "sqlite+aiosqlite:///:memory:", # Async URL
        "echo": False
    }
}))

container = init(
    modules=["services", "repositories", "pico_sqlalchemy"],
    config=config,
)

async def main():
    # Use await container.aget() for async resolution
    service = await container.aget(UserService)
    
    # Await the async service method
    user = await service.create("alice")
    print(f"Created user: {user.id}")

    # Clean up async resources
    await container.cleanup_all_async()

if __name__ == "__main__":
    asyncio.run(main())

๐Ÿ”„ Transaction Propagation Modes

Pico-SQLAlchemy supports the core Spring-inspired semantics:

Mode Behavior
REQUIRED Join existing tx or create new
REQUIRES_NEW Suspend parent and start new tx
SUPPORTS Join if exists, else run without tx
MANDATORY Requires existing tx
NOT_SUPPORTED Run without tx, suspending parent
NEVER Fail if a tx exists

Example:

@transactional(propagation="REQUIRES_NEW")
async def write_audit(self, entry: AuditEntry):
    ...

๐Ÿงช Testing with Pico-IoC

You can override repositories, engines, or services easily:

import pytest
import pytest_asyncio
from pico_ioc import init, configuration, DictSource
from pico_sqlalchemy import SessionManager, AppBase

# In conftest.py
@pytest_asyncio.fixture
async def container():
    cfg = configuration(DictSource({"database": {"url": "sqlite+aiosqlite:///:memory:"}}))
    c = init(modules=["pico_sqlalchemy", "myapp"], config=cfg)
    
    # Setup the in-memory database
    sm = await c.aget(SessionManager)
    async with sm.engine.begin() as conn:
        await conn.run_sync(AppBase.metadata.create_all)

    yield c
    
    # Clean up all async components
    await c.cleanup_all_async()

# In your test
@pytest.mark.asyncio
async def test_my_service(container):
    service = await container.aget(UserService)
    user = await service.create("test")
    assert user.id is not None

๐Ÿงฌ Example: Custom Database Configurer

from pico_sqlalchemy import DatabaseConfigurer, AppBase
from pico_ioc import component
import asyncio

@component
class TableCreationConfigurer(DatabaseConfigurer):
    priority = 10
    def __init__(self, base: AppBase):
        self.base = base
        
    def configure(self, engine):
        # This configure method is called by the factory.
        # We need to run the async setup.
        async def setup():
            async with engine.begin() as conn:
                await conn.run_sync(self.base.metadata.create_all)
        
        asyncio.run(setup())

Pico-SQLAlchemy will detect it and call configure during initialization.


โš™๏ธ How It Works

  • SessionManager is created by Pico-IoC (SqlAlchemyFactory)
  • A global session context is established via contextvars
  • @transactional automatically opens/closes async transactions
  • @repository registers a class as a singleton component
  • All dependencies (repositories, services, configurers) are resolved by Pico-IoC

No globals. No implicit singletons. No framework coupling.


๐Ÿ’ก Architecture Overview

                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚         Your App            โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                โ”‚
                      Constructor Injection
                                โ”‚
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚          Pico-IoC            โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                โ”‚
                 SessionManager / SqlAlchemyFactory
                                โ”‚
                 โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                 โ”‚       pico-sqlalchemy        โ”‚
                 โ”‚ Transactional Decorators     โ”‚
                 โ”‚ Repository Metadata          โ”‚
                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                โ”‚
                             SQLAlchemy
                           (Async ORM)

๐Ÿ“ License

MIT

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

pico_sqlalchemy-0.1.0.tar.gz (21.5 kB view details)

Uploaded Source

Built Distribution

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

pico_sqlalchemy-0.1.0-py3-none-any.whl (11.8 kB view details)

Uploaded Python 3

File details

Details for the file pico_sqlalchemy-0.1.0.tar.gz.

File metadata

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

File hashes

Hashes for pico_sqlalchemy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 bebced4ef67ad2387b97f56e31ac4045e8089ef6527844520008e620a57310ce
MD5 6201257f2f81b19656435d68cbde8112
BLAKE2b-256 e1457a74cbe5a22eb7d9a925cf75e1b493a0230ad7d1a8659d8991ad8ec8f869

See more details on using hashes here.

File details

Details for the file pico_sqlalchemy-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pico_sqlalchemy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f80cb29f79c51f9c446cd3fbca3ee85b70cbe3ae61c5e494db0109cb52fbc82a
MD5 42909ecc3613d5527daa5d35fbd50108
BLAKE2b-256 1734a9631b6c3e67f522fc2279cb47b3d566154c1d6e08ae8f18a86a72007213

See more details on using hashes here.

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