mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-04-16 06:36:26 +02:00
118 lines
4.1 KiB
Markdown
118 lines
4.1 KiB
Markdown
# Migrating to v3.0
|
|
|
|
This page covers every breaking change introduced in **v3.0** and the steps required to update your code.
|
|
|
|
---
|
|
|
|
## CRUD
|
|
|
|
### Facet keys now always use the full relationship chain
|
|
|
|
In `v2`, relationship facet fields used only the terminal column key (e.g. `"name"` for `Role.name`) and only prepended the relationship name when two facet fields shared the same column key. In `v3`, facet keys **always** include the full relationship chain joined by `__`, regardless of collisions.
|
|
|
|
=== "Before (`v2`)"
|
|
|
|
```
|
|
User.status -> status
|
|
(User.role, Role.name) -> name
|
|
(User.role, Role.permission, Permission.name) -> name
|
|
```
|
|
|
|
=== "Now (`v3`)"
|
|
|
|
```
|
|
User.status -> status
|
|
(User.role, Role.name) -> role__name
|
|
(User.role, Role.permission, Permission.name) -> role__permission__name
|
|
```
|
|
|
|
---
|
|
|
|
## Models
|
|
|
|
The lifecycle event system has been rewritten. Callbacks are now registered with a module-level [`listens_for`](../reference/models.md#fastapi_toolsets.models.listens_for) decorator and dispatched by [`EventSession`](../reference/models.md#fastapi_toolsets.models.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:
|
|
|
|
1. **`__watched_fields__`** — a plain class attribute to restrict which field changes trigger `UPDATE` events (replaces `@watch`).
|
|
2. **`@listens_for`** — a module-level decorator to register callbacks for one or more [`ModelEvent`](../reference/models.md#fastapi_toolsets.models.ModelEvent) types (replaces `on_create` / `on_delete` / `on_update` methods).
|
|
|
|
=== "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.
|