* feat: rework async event system * docs: add v3 migration guide * feat: add cache * enhancements
3.4 KiB
Migrating to v3.0
This page covers every breaking change introduced in v3.0 and the steps required to update your code.
Models
The lifecycle event system has been rewritten. Callbacks are now registered with a module-level listens_for decorator and dispatched by EventSession, replacing the mixin-based approach from v2.
WatchedFieldsMixin and @watch removed
Importing WatchedFieldsMixin or watch will raise ImportError.
Model method callbacks (on_create, on_delete, on_update) and the @watch decorator are replaced by:
__watched_fields__— a plain class attribute to restrict which field changes triggerUPDATEevents (replaces@watch).@listens_for— a module-level decorator to register callbacks for one or moreModelEventtypes (replaceson_create/on_delete/on_updatemethods).
=== "Before (v2)"
```python
from fastapi_toolsets.models import WatchedFieldsMixin, watch
@watch("status")
class Order(Base, UUIDMixin, WatchedFieldsMixin):
__tablename__ = "orders"
status: Mapped[str]
async def on_create(self):
await notify_new_order(self.id)
async def on_update(self, changes):
if "status" in changes:
await notify_status_change(self.id, changes["status"])
async def on_delete(self):
await notify_order_cancelled(self.id)
```
=== "Now (v3)"
```python
from fastapi_toolsets.models import ModelEvent, UUIDMixin, listens_for
class Order(Base, UUIDMixin):
__tablename__ = "orders"
__watched_fields__ = ("status",)
status: Mapped[str]
@listens_for(Order, [ModelEvent.CREATE])
async def on_order_created(order: Order, event_type: ModelEvent, changes: None):
await notify_new_order(order.id)
@listens_for(Order, [ModelEvent.UPDATE])
async def on_order_updated(order: Order, event_type: ModelEvent, changes: dict):
if "status" in changes:
await notify_status_change(order.id, changes["status"])
@listens_for(Order, [ModelEvent.DELETE])
async def on_order_deleted(order: Order, event_type: ModelEvent, changes: None):
await notify_order_cancelled(order.id)
```
EventSession now required
Without EventSession, lifecycle callbacks will silently stop firing.
Callbacks are now dispatched inside EventSession.commit() rather than via background tasks. Pass it as the session class when creating your session factory:
=== "Before (v2)"
```python
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
engine = create_async_engine("postgresql+asyncpg://...")
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
```
=== "Now (v3)"
```python
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from fastapi_toolsets.models import EventSession
engine = create_async_engine("postgresql+asyncpg://...")
SessionLocal = async_sessionmaker(engine, expire_on_commit=False, class_=EventSession)
```
!!! note
If you use create_db_session from fastapi_toolsets.pytest, the session already uses EventSession — no changes needed in tests.