Easily add soft-deletion to your SQLAlchemy Models and automatically filter out soft-deleted objects from your queries and relationships.
This package can generate a tailor-made SQLAlchemy Mixin that can be added to your SQLAlchemy Models, making them contain a field that, when set, will mark the entity as being soft-deleted.
The library also installs a hook which dynamically rewrites all selects which are sent to the database for all tables that implement the soft-delete mixin, providing a seamless experience in both manual queries and model relationship accesses.
Mixin generation is fully customizable and you can choose the field name, its type, and the presence of (soft-)delete/undelete methods.
The default implementation will generate a deleted_at field in your models, of type DateTime(timezone=True), and will also provide a .delete(v: Optional = datetime.utcnow()) and .undelete() methods.
pip install sqlalchemy-easy-softdelete
from sqlalchemy_easy_softdelete.mixin import generate_soft_delete_mixin_class
from sqlalchemy_easy_softdelete.hook import IgnoredTable
from sqlalchemy.orm import declarative_base, Mapped
from sqlalchemy import Column, Integer
from datetime import datetime
# Create a Class that inherits from our class builder
class SoftDeleteMixin(
generate_soft_delete_mixin_class( # type: ignore[misc]
# This table will be ignored by the hook
# even if the table has the soft-delete column
ignored_tables=[IgnoredTable(table_schema="public", name="cars"),]
)
):
# type: ignore[misc] is required because the mixin is dynamically generated
# Type hint for IDE autocomplete and type checker support.
# Using Mapped[T | None] ensures type checkers understand this is a
# SQLAlchemy column that supports query operations like .where()
deleted_at: Mapped[datetime | None]
# Optional: Add method stubs for delete/undelete for type checker support.
# The actual implementations are provided by the generated mixin class.
def delete(self, v: datetime | None = None) -> None:
super().delete(v) # type: ignore[misc]
def undelete(self) -> None:
super().undelete() # type: ignore[misc]
# Apply the mixin to your Models
Base = declarative_base()
class Fruit(Base, SoftDeleteMixin):
__tablename__ = "fruit"
id = Column(Integer)all_active_fruits = session.query(Fruit).all()This will generate a query with an automatic WHERE fruit.deleted_at IS NULL condition added to it.
all_fruits = session.query(Fruit).execution_options(include_deleted=True).all()Setting include_deleted=True (attribute name can be customized) in the query disables soft delete for that query.
Contributions are welcome! See CONTRIBUTING.md for development setup and guidelines.
- BSD-3-Clause