The solid_sold
package provides a generic implementation of the Repository pattern in Python, aimed at creating a flexible and robust data access layer for your applications.
- Rapid Development: Focus on your business logic from the start, without worrying about database-specific details.
- Type Safety: Leverage generics for type safety. This ensures you never forget to handle
None
values or other specific return types. - IDE Integration: Full support for mypy and IDE autocompletion, enhancing type checking and developer experience.
- Database Agnostic: Abstract design allows switching databases without altering business logic.
- Error Prevention: Strong typing catches potential issues during development, reducing runtime errors.
The AbstractStorage
class is the base for repository implementations, providing:
- Abstract CRUD methods (
get
,create
,delete
,update
). - Type inference using Python's typing system.
- Generic parameters for entities, updates, and predicates, offering great flexibility.
AbstractTransactionalStorage
extends AbstractStorage
with transactional support:
- Context manager for transaction handling (
async with
syntax). - Automated commit and rollback management.
- Thread-safe transaction tracking with
ContextVar
.
Define your entity model, such as a simple dataclass, Pydantic model, or another preferred format:
from dataclasses import dataclass
@dataclass
class User:
email: str
id: int | None = None
is_active: bool = False
Define an update model for selective updates using TypedDict
:
from typing import NotRequired, TypedDict
class UserUpdate(TypedDict):
id: NotRequired[int]
username: NotRequired[str]
email: NotRequired[str]
first_name: NotRequired[str]
last_name: NotRequired[str]
is_active: NotRequired[bool]
Create a predicate model for filtering entities:
class UserPredicate(TypedDict):
id: NotRequired[int]
username: NotRequired[str]
email: NotRequired[str]
first_name: NotRequired[str]
last_name: NotRequired[str]
is_active: NotRequired[bool]
Create a storage for your entity:
from solid_sold.base import AbstractStorage
class UserStorage(AbstractStorage[User, UserUpdate, UserPredicate]):
...
Let’s create our first service with get_user_details method that simply returns the user by its id:
class UserService:
def __init__(
self,
user_storage: UserStorage,
transaction: AbstractTransactionalContext,
) -> None:
self.user_storage = user_storage
self.transaction = transaction
async def get_user_details(self, user_id: int) -> User:
"""Returns user details by ID."""
async with self.transaction:
user = await anext(self.user_storage.get(where={"id": user_id}))
if user is None:
raise UserDoesNotExist(f"User with ID '{user_id}' does not exist.")
return user
This class is still generic, but it’s already useful even though it doesn't have a specific database implementation. The idea here is to focus implementing our business logic without focusing on specific database implementation while benefiting from type hinting:
A complete working example with SQLite support can be found in examples/user.
Contributions are welcome! Please submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.