One pattern for accessing data powered by SQLAlchemy & Pydantic.
Project description
OnePattern
One pattern for accessing data powered by SQLAlchemy & Pydantic.
Features
- CRUD: Create, read, update, delete operations.
- Pagination: Built-in support for pagination & sorting.
- Validation: Automatic validation using Pydantic models.
- Bulk operations: Create, update, delete multiple records at once.
- Unit of work: Transactional support for multiple operations.
Requirements
OnePattern stands on the shoulders of giants:
Installation
pip install onepattern
Get Started
Let's write a simple CRUD API for managing users to demonstrate the power of OnePattern.
Create models:
from datetime import datetime
from pydantic import BaseModel, ConfigDict
from sqlalchemy import Identity
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Identity(), primary_key=True)
name: Mapped[str]
age: Mapped[int]
salary: Mapped[int]
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
default=datetime.now, onupdate=datetime.now
)
class UserBase(BaseModel):
name: str
age: int
salary: int
model_config = ConfigDict(from_attributes=True)
class UserCreate(UserBase):
pass
class UserRead(UserBase):
id: int
created_at: datetime
updated_at: datetime
Create repository:
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from docs.gs_models import User, UserRead
from onepattern import AlchemyRepository
class UserRepository(AlchemyRepository[User, UserRead]):
model_type = User
schema_type = UserRead
async_engine = create_async_engine("sqlite+aiosqlite://", echo=True)
async_session = async_sessionmaker(async_engine)
async def get_users() -> UserRepository:
async with async_session() as session:
async with session.begin():
yield UserRepository(session)
Use it in your app:
from contextlib import asynccontextmanager
from typing import Annotated, Any
from fastapi import FastAPI, Depends, HTTPException
from docs.gs_models import Base, UserCreate, UserRead
from docs.gs_repository import UserRepository, async_engine, get_user_repo
from onepattern import PageParams, Page
@asynccontextmanager
async def lifespan(_app: FastAPI) -> Any:
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
app = FastAPI(lifespan=lifespan)
@app.post("/users/")
async def create_user(
user: UserCreate, users: Annotated[UserRepository, Depends(get_user_repo)]
) -> UserRead:
return await users.create(user)
async def get_user_dep(
user_id: int, users: Annotated[UserRepository, Depends(get_user_repo)]
) -> UserRead:
user = await users.get(user_id)
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.get("/users/{user_id}")
async def get_user(
user: Annotated[UserRead, Depends(get_user_dep)],
) -> UserRead:
return user
@app.put("/users/{user_id}")
async def update_user(
update: UserCreate,
user: Annotated[UserRead, Depends(get_user_dep)],
users: Annotated[UserRepository, Depends(get_user_repo)],
) -> UserRead:
return await users.update(user.id, update)
@app.delete("/users/{user_id}")
async def delete_user(
user: Annotated[UserRead, Depends(get_user_dep)],
users: Annotated[UserRepository, Depends(get_user_repo)],
) -> UserRead:
return await users.delete(user.id)
@app.get("/users/")
async def get_users(
params: Annotated[PageParams, Depends()],
users: Annotated[UserRepository, Depends(get_user_repo)],
) -> Page[UserRead]:
return await users.get_many(params=params)
Run app:
uvicorn docs.gs_app:app --host 0.0.0.0 --port 8000 --reload
Pagination example:
Extending models
OnePattern provides multiple ways to extend models:
- Use mixins to add commonly-used columns:
from sqlalchemy.orm import DeclarativeBase, Mapped
from onepattern.models import HasID, HasTimestamp
class Base(DeclarativeBase):
pass
class UserMixins(Base, HasID, HasTimestamp):
__tablename__ = "users"
name: Mapped[str]
age: Mapped[int]
salary: Mapped[int]
Tip: See
onepattern.schemas
for similar pydantic mixins.
- Use pre-configured base model:
from datetime import datetime
from sqlalchemy import Identity
from sqlalchemy.orm import Mapped, mapped_column
from onepattern import AlchemyBase
class UserAlchemyBase(AlchemyBase):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Identity(), primary_key=True)
name: Mapped[str]
age: Mapped[int]
salary: Mapped[int]
created_at: Mapped[datetime] = mapped_column(default=datetime.now)
updated_at: Mapped[datetime] = mapped_column(
default=datetime.now, onupdate=datetime.now
)
- Use entity model with commonly-used columns:
from sqlalchemy.orm import Mapped
from onepattern import AlchemyEntity
class UserAlchemyEntity(AlchemyEntity):
__tablename__ = "users"
name: Mapped[str]
age: Mapped[int]
salary: Mapped[int]
# id, created_at and updated_at are added automatically
Tip: See
onepattern.schemas.EntityModel
for similar pydantic base.
Made with love ❤️
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
Built Distribution
File details
Details for the file onepattern-0.2.3.tar.gz
.
File metadata
- Download URL: onepattern-0.2.3.tar.gz
- Upload date:
- Size: 11.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.2 CPython/3.12.3 Windows/11
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | c85c752d7456b1ff0a042167453d4450a883a6fb9573dc519230c7865879010d |
|
MD5 | d781e0a025b12c90432959d75e13f346 |
|
BLAKE2b-256 | d1bbf34496a729bea0848ba5f910bc7cea787b935b2287af91bcf21b0e0f3c1d |
File details
Details for the file onepattern-0.2.3-py3-none-any.whl
.
File metadata
- Download URL: onepattern-0.2.3-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.2 CPython/3.12.3 Windows/11
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | fa812a41811f6a17fb033e7ab66bac47b3449cfdc24438bb5d0d05b0e1091cf6 |
|
MD5 | 40620e884086f0fde38293c2e0e5eaa9 |
|
BLAKE2b-256 | 94a86cb2c2b77b8828fd6854efac89ac1cf23993b56d527b4d38334f2fee2961 |