Pico-ioc integration for SQLAlchemy. Adds Spring-style transactional support, configuration, and helpers.
Project description
๐ฆ pico-sqlalchemy
Pico-SQLAlchemy
Pico-SQLAlchemy integrates Pico-IoC with SQLAlchemy, providing a true inversion of control persistence layer with Spring Data-style declarative features.
It brings constructor-based dependency injection, implicit transaction management, and powerful declarative queries using pure Python and SQLAlchemyโs Async ORM.
๐ Requires Python 3.11+ ๐ Async-Native: Built entirely on
AsyncSessionandcreate_async_engine. โจ Zero-Boilerplate: Repositories are transactional by default. ๐ Declarative Queries: Define SQL or expressions in decorators; the library executes them for you.
๐ฏ Why pico-sqlalchemy?
Most Python apps suffer from manual session handling (async with session...), scattered transaction logic, and verbose repository patterns.
Pico-SQLAlchemy solves this by offering:
| Feature | SQLAlchemy Default | pico-sqlalchemy |
|---|---|---|
| Transactions | Manual commit() / rollback() |
Implicit (Auto-managed) |
| Repositories | DIY Classes | @repository (Transactional by default) |
| Queries | Manual implementation | @query (Declarative execution) |
| Injection | None / Global variables | Constructor Injection (IoC) |
| Pagination | Manual calculation | Automatic (PageRequest / Page) |
๐งฑ Core Features
- Implicit Transactions: Methods inside
@repositoryare automatically Read-Write transactional. - Declarative Queries: Use
@queryto run SQL or Expressions automatically (defaults to Read-Only). - AOP-Based Propagation:
REQUIRED,REQUIRES_NEW,MANDATORY,NEVER, etc. - Session Lifecycle: Centralized
SessionManagerhandles engine creation and cleanup. - Pagination: Built-in support for paged results via
@query(paged=True).
๐ฆ Installation
pip install pico-sqlalchemy
You will also need an async database driver:
pip install aiosqlite # for SQLite
pip install asyncpg # for PostgreSQL
๐ Quick Example
1. Define 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))
2. Define Repository (The "Magic" Part)
Notice we don't need @transactional here.
save: Automatically runs in a Read-Write transaction.find_by_name: Automatically runs in a Read-Only transaction and executes the query logic.
from pico_sqlalchemy import repository, query, SessionManager, get_session
@repository(entity=User)
class UserRepository:
def __init__(self, manager: SessionManager):
self.manager = manager
# IMPLICIT: Read-Write Transaction
async def save(self, user: User) -> User:
session = get_session(self.manager)
session.add(user)
return user
# DECLARATIVE: Read-Only Transaction + Auto-Execution
@query(expr="username = :username", unique=True)
async def find_by_name(self, username: str) -> User | None:
... # Body is ignored; the library executes the query
3. Define Service
Use @transactional here to define business logic boundaries.
from pico_ioc import component
from pico_sqlalchemy import transactional
@component
class UserService:
def __init__(self, repo: UserRepository):
self.repo = repo
@transactional
async def create(self, name: str) -> User:
# 1. Check existence (Read-Only tx from repo)
existing = await self.repo.find_by_name(name)
if existing:
raise ValueError("User exists")
# 2. Save new user (Joins current transaction)
return await self.repo.save(User(username=name))
4. Run it
import asyncio
from pico_ioc import init, configuration, DictSource
config = configuration(DictSource({
"database": {
"url": "sqlite+aiosqlite:///:memory:",
"echo": False
}
}))
async def main():
container = init(modules=["pico_sqlalchemy", "__main__"], config=config)
service = await container.aget(UserService)
user = await service.create("alice")
print(f"Created: {user.id}")
await container.cleanup_all_async()
if __name__ == "__main__":
asyncio.run(main())
โก Transaction Hierarchy & Rules
Pico-SQLAlchemy applies a "Best Effort" strategy to determine transaction configuration. The priority order (highest wins) is:
| Priority | Decorator | Default Mode | Use Case |
|---|---|---|---|
| 1 (High) | @transactional(...) |
Explicit Config | Overriding defaults, Service layer logic. |
| 2 | @query(...) |
Read-Only | Efficient data fetching. |
| 3 (Base) | @repository |
Read-Write | Default for CRUD (saves, updates, deletes). |
Example Scenarios
-
Plain Method in Repository:
async def update_user(self): ...
๐ Result: Active Read-Write Transaction (Implicit from
@repository). -
Query Method:
@query("SELECT ...") async def get_data(self): ...
๐ Result: Active Read-Only Transaction (Implicit from
@query). -
Manual Override:
@transactional(read_only=True) async def complex_report(self): ...
๐ Result: Active Read-Only Transaction (Explicit override).
๐ Declarative Queries in Depth
The @query decorator eliminates boilerplate for common fetches.
Expression Mode (expr)
Requires @repository(entity=Model). Injects the expression into a SELECT * FROM table WHERE ....
@query(expr="age > :min_age", unique=False)
async def find_adults(self, min_age: int) -> list[User]: ...
SQL Mode (sql)
Executes raw SQL. Useful for complex joins or specific DTOs.
@query(sql="SELECT count(*) as cnt FROM users")
async def count_users(self) -> int: ...
Automatic Pagination
Just add paged=True and a page: PageRequest parameter.
from pico_sqlalchemy import Page, PageRequest
@query(expr="active = true", paged=True)
async def find_active(self, page: PageRequest) -> Page[User]: ...
๐งช Testing
Testing is simple because you can override the configuration or the components easily using Pico-IoC.
@pytest.mark.asyncio
async def test_service():
# Setup container with in-memory DB
container = ...
service = await container.aget(UserService)
user = await service.create("test")
assert user.id is not None
๐ก Architecture Overview
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your App โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ
Constructor Injection
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โ Pico-IoC โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโ
โ pico-sqlalchemy โ
โ 1. Implicit Repo Transactionsโ
โ 2. Declarative @query โ
โ 3. Explicit @transactional โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโ
โ
SQLAlchemy
(Async ORM)
๐ค Claude Code Skills
This project includes pre-designed skills for Claude Code, enabling AI-assisted development with pico-sqlalchemy patterns.
| Skill | Command | Description |
|---|---|---|
| Pico SQLAlchemy Repository | /pico-sqlalchemy |
Creates models, repositories and services with DI |
| Pico Test Generator | /pico-tests |
Generates tests for pico-framework components |
See Skills documentation for full details and installation instructions.
๐ License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pico_sqlalchemy-0.2.1.dev0.tar.gz.
File metadata
- Download URL: pico_sqlalchemy-0.2.1.dev0.tar.gz
- Upload date:
- Size: 49.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0c6809cb3686045e47c517c30acfce9893abfb1157d1fcbcfe5198db457b704
|
|
| MD5 |
0dca556d0f27d435e14cd7882102ee45
|
|
| BLAKE2b-256 |
f71c98070183bc591c6f98149bb590aceb389fde5924197e813246252ffc8176
|
File details
Details for the file pico_sqlalchemy-0.2.1.dev0-py3-none-any.whl.
File metadata
- Download URL: pico_sqlalchemy-0.2.1.dev0-py3-none-any.whl
- Upload date:
- Size: 15.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
951496978b94edca320172b8233385ecafb47f9806a0e140c543febd469c0cfa
|
|
| MD5 |
6b91765ad1c2ee89dd569771351fd63a
|
|
| BLAKE2b-256 |
94c07833af1db9953bfb4618ca770ddb0f65f9a906dc7896d90dd356c0127083
|