Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

def test_startup():
app_mod.engine = create_engine("sqlite://")
app_mod.on_startup()
app_mod.create_db_and_tables()
insp: Inspector = inspect(app_mod.engine)
assert insp.has_table(str(app_mod.Hero.__tablename__))

Expand Down
18 changes: 13 additions & 5 deletions docs_src/tutorial/fastapi/update/tutorial002_py310.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,21 @@ def create_db_and_tables():
SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables()
yield
def hash_password(password: str) -> str:
return f"not really hashed {password} hehehe"


def get_session():
with Session(engine) as session:
yield session


app = FastAPI(lifespan=lifespan)
app = FastAPI()


@app.on_event("startup")
def on_startup():
create_db_and_tables()


@app.post("/heroes/", response_model=HeroPublic)
Expand Down
8 changes: 7 additions & 1 deletion sqlmodel/soft_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@


class SoftDeleteMixin:
deleted_at: Optional[datetime] = Field(default=None, nullable=True)
"""
Mixin that adds a `deleted_at` timestamp column.

Usage:
class MyModel(SQLModel, SoftDeleteMixin, table=True): ...
"""
deleted_at: Optional[datetime] = Field(default=None)
4 changes: 2 additions & 2 deletions sqlmodel/soft_delete_session.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Any

from sqlmodel.orm.session import Session

from sqlmodel.soft_delete import SoftDeleteMixin


Expand All @@ -15,4 +14,5 @@ def delete(self, instance: Any, hard_delete: bool = False) -> Any:

def _now(self) -> Any:
from datetime import datetime, timezone
return datetime.now(timezone.utc)

return datetime.now(timezone.utc)
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def cov_tmp_path(tmp_path: Path) -> Generator[Path, None, None]:

def coverage_run(*, module: str, cwd: str | Path) -> subprocess.CompletedProcess:
import os

env = os.environ.copy()
env["PYTHONPATH"] = str(top_level_path)
# On Windows, coverage causes asyncio issues, so run python directly
Expand Down
12 changes: 5 additions & 7 deletions tests/test_session_delete.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import pytest
from datetime import datetime
from typing import Optional

from sqlmodel import SQLModel, create_engine, Field
from sqlmodel.main import default_registry
import pytest
from sqlmodel import Field, SQLModel, create_engine
from sqlmodel.soft_delete import SoftDeleteMixin
from sqlmodel.soft_delete_session import SoftDeleteSession

Expand All @@ -17,7 +15,7 @@ def clear_sqlmodel():
def test_soft_delete():
# Define model inside test to avoid registry conflicts
class SoftDeleteHero(SQLModel, SoftDeleteMixin, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
id: int | None = Field(default=None, primary_key=True)
name: str

engine = create_engine("sqlite:///:memory:")
Expand Down Expand Up @@ -45,7 +43,7 @@ def test_hard_delete():
# Define model inside test to avoid registry conflicts
class HardDeleteHero(SQLModel, SoftDeleteMixin, table=True):
__tablename__ = "hard_delete_heroes"
id: Optional[int] = Field(default=None, primary_key=True)
id: int | None = Field(default=None, primary_key=True)
name: str

engine = create_engine("sqlite:///:memory:")
Expand All @@ -62,4 +60,4 @@ class HardDeleteHero(SQLModel, SoftDeleteMixin, table=True):

# Check hard deleted (would raise if not handled, but since hard delete, it's gone)
# In SQLAlchemy, after hard delete, the object is detached
assert hero.id is None or session.get(HardDeleteHero, hero.id) is None
assert hero.id is None or session.get(HardDeleteHero, hero.id) is None
Loading