Skip to content

Releases: d3vyce/fastapi-toolsets

4.1.0

21 May 16:32

Choose a tag to compare

Highlights

advisory_lock

The new advisory_lock context manager acquires a PostgreSQL session-level advisory lock and releases it automatically on exit.

from fastapi_toolsets.db import advisory_lock

# Blocking exclusive lock
async with advisory_lock(session, key=42):
    ...

# Non-blocking — yields False immediately if already held
async with advisory_lock(session, key=42, nowait=True) as acquired:
    if not acquired:
        raise HTTPException(409, "Resource is locked")

# With a timeout
async with advisory_lock(session, key=42, timeout="5s"):
    ...

# Shared lock + two-integer key for namespacing
async with advisory_lock(session, key=(1, user_id), shared=True):
    ...

More detail in the documentation

NOWAIT/SKIP LOCKED row locking

with_for_update on get, get_or_none, get_multi, and update now accepts "nowait" and "skip_locked" in addition to True.

# Raise immediately if the row is already locked
user = await UserCrud.get(session, [User.id == user_id], with_for_update="nowait")

# Skip rows held by another transaction — ideal for job queues
jobs = await JobCrud.get_multi(session, filters=[Job.status == "pending"], with_for_update="skip_locked")

More detail in the documentation

What's Changed

Features

  • add advisory_lock context manager (#283)
  • add NOWAIT/SKIP LOCKED row locking support and extend with_for_update to get_multi and update (#281)

Internal

  • bump pydantic from 2.13.3 to 2.13.4 (#279)
  • bump coverage from 7.13.5 to 7.14.0 (#276)
  • bump zensical from 0.0.40 to 0.0.41 (#278)
  • bump ty from 0.0.33 to 0.0.35 (#277)
  • bump ruff from 0.15.12 to 0.15.13 (#288)
  • bump prek from 0.3.13 to 0.4.1 (#286)
  • bump idna from 3.11 to 3.15 (#285)
  • bump pymdown-extensions from 10.21.2 to 10.21.3 (#284)
  • bump ty from 0.0.35 to 0.0.38 (#287)

Full Changelog: v4.0.0...v4.1.0

4.0.0

08 May 11:17
189e9e1

Choose a tag to compare

Highlights

Since v3.0.0, features have been added. Here a quick summary and the related changelog:

Before upgrading check the migration guide in the documentation: Migrating to v4.0.

No breaking changes will be added before the next major release (v5.0.0).

To install the module and avoid upgrading to the next major version, you can use the following command:

uv add "fastapi-toolsets>=4,<5"

lock_tables now takes a session_maker instead of a session

lock_tables previously accepted an AsyncSession directly. It now takes the async_sessionmaker, opens a dedicated session internally, and yields it to the caller.

# Before
async with lock_tables(session=session, tables=[User]):
    ...

# After
async with lock_tables(session_maker=session_maker, tables=[User]) as session:
    ...

The dedicated session enforces RAII lock semantics: the PostgreSQL lock is held for exactly the lifetime of the async with block.

More detail in the documentation

Security module

The new security module provides composable, OpenAPI-aware authentication helpers for FastAPI built around a simple validator pattern:

from fastapi import Security
from fastapi_toolsets.security import BearerTokenAuth

async def verify_token(token: str) -> User:
    user = await db.get_by_token(token)
    if not user:
        raise UnauthorizedError()
    return user

bearer = BearerTokenAuth(verify_token)

@app.get("/me")
async def me(user: User = Security(bearer)):
    return user

Four auth sources are available out of the box: BearerTokenAuth (Authorization: Bearer), CookieAuth (named cookie), APIKeyHeaderAuth (custom header), and MultiAuth (tries sources in order, short-circuits on first success). OAuth 2.0 / OIDC helpers are also included for implementing authorization code flows.

More detail in the documentation

What's Changed

Breaking Changes

  • lock_tables now takes a session_maker instead of a session (#263)

Features

  • add security module ( #111)

Docs

  • fix Zensical warnings (#263)

Internal

  • bump pydantic from 2.13.1 to 2.13.3 (#259)
  • bump fastapi from 0.135.3 to 0.136.0 (#258)
  • bump typer from 0.24.1 to 0.24.2 (#257)
  • bump prek from 0.3.9 to 0.3.10 (#255)
  • bump typer from 0.24.2 to 0.25.0 (#266)
  • bump ruff from 0.15.10 to 0.15.12 (#265)
  • bump prek from 0.3.10 to 0.3.11 (#264)
  • bump zensical from 0.0.33 to 0.0.37 (#268)
  • bump ty from 0.0.31 to 0.0.33 (#267)
  • bump zensical from 0.0.37 to 0.0.40 (#271)
  • bump fastapi from 0.136.0 to 0.136.1 (#272)
  • bump prometheus-client from 0.24.1 to 0.25.0 (#273)
  • bump prek from 0.3.11 to 0.3.13 (#274)
  • bump typer from 0.25.0 to 0.25.1 (#275)

Full Changelog: v3.1.1...v4.0.0

3.1.1

16 Apr 19:40

Choose a tag to compare

What's Changed

Bug Fixes

  • KeyError on fixture load with SQLAlchemy joined-table inheritance (#254)

Internal

  • bump ruff from 0.15.9 to 0.15.10 (#252)
  • bump prek from 0.3.8 to 0.3.9 (#251)
  • bump zensical from 0.0.32 to 0.0.33 (#249)
  • bump pydantic from 2.12.5 to 2.13.1 (#250)
  • bump ty from 0.0.29 to 0.0.31 (#248)

Full Changelog: v3.1.0...v3.1.1

3.1.0

12 Apr 16:54

Choose a tag to compare

Highlights

M2M helpers

Three new helpers in fastapi_toolsets.db for managing Many-to-Many associations without touching the ORM collection — safe to use inside nested transactions and lock_tables contexts where lazy-loading would raise MissingGreenlet.

  • m2m_add — insert one or more associations into the secondary table (ON CONFLICT DO NOTHING available via ignore_conflicts=True)
  • m2m_remove — delete specific associations; idempotent if the association doesn't exist
  • m2m_set — atomically replace the full association set (delete all, then insert the new set); passing no related instances clears the association entirely

More detail in the documentation

What's Changed

Features

  • auto eager-load relationships in register_fixtures (#243)
  • add get_field_by_attr fixtures helper (#245)
  • add M2M helpers (#247)

Internal

  • bump pytest from 9.0.2 to 9.0.3 (#238)
  • bump ty from 0.0.27 to 0.0.29 (#239)
  • bump zensical from 0.0.31 to 0.0.32 (#240)
  • bump ruff from 0.15.8 to 0.15.9 (#241)

Full Changelog: v3.0.3...v3.1.0

3.0.3

07 Apr 09:45

Choose a tag to compare

What's Changed

Features

  • normalize enum facet values to member names and accept name strings in filter_by (#235)

Bug Fixes

  • coerce plain int values to enum member when filtering on IntEnum column (#231)

Internal

  • restrict ci.yml workflow permissions to contents: read (#233)

Full Changelog: v3.0.2...v3.0.3

3.0.2

04 Apr 23:15

Choose a tag to compare

What's Changed

Features

  • expose order_columns in paginated response (#225)
  • support relation tuples in order_fields for cross-table sorting (#227)

Bug Fixes

  • apply default_load_options after create() and update() to prevent MissingGreenlet on relationship access (#229)

Full Changelog: v3.0.1...v3.0.2

3.0.1

03 Apr 09:46

Choose a tag to compare

What's Changed

Bug Fixes

  • coerce string values to bool for Boolean facet field filtering (#219)
  • widen JoinType to accept aliased and polymorphic targets (#221)

Docs

  • rework versioning to keep latest feature version per major (#223)

Full Changelog: v3.0.0...v3.0.1

3.0.0

02 Apr 09:38
bbe63ed

Choose a tag to compare

Highlights

Since v2.0.0, many features have been added. Here a quick summary and the related changelog:

Before upgrading check the migration guide in the documentation: Migrating to v3.0.

No breaking changes will be added before the next major release (v4.0.0).

To install the module and avoid upgrading to the next major version, you can use the following command:

uv add "fastapi-toolsets>=3,<4"

Rework async event system

The lifecycle event system has been rewritten. Model method callbacks (on_create, on_delete, on_update) and the @watch decorator are replaced by a module-level @listens_for decorator and an EventSession that dispatches events after commit.

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"])

More detail in the documentation

Fixtures multi-variant contexts and custom Enum support

The fixtures module now supports registering the same fixture name under different context sets, custom Enum types as contexts and default contexts on a registry.

from enum import Enum
from fastapi_toolsets.fixtures import FixtureRegistry, Context

class AppContext(str, Enum):
    STAGING = "staging"

# Same name, different contexts — merged when both are loaded
@fixtures.register(contexts=[Context.BASE])
def users():
    return [User(id=1, username="admin")]

@fixtures.register(contexts=[Context.TESTING])
def users():
    return [User(id=2, username="tester")]

# Custom enum contexts work everywhere
@fixtures.register(contexts=[AppContext.STAGING])
def staging_data():
    return [Config(key="feature_x", enabled=True)]

More detail in the documentation

add search_column

A new search_column query parameter lets callers restrict a search to a single field instead of searching across all searchable_fields. The response now includes a search_columns list so clients can populate a column picker.

GET /articles/offset?search=fastapi&search_column=title

result = await ArticleCrud.offset_paginate(
    session,
    search="fastapi",
    search_column="title",
    schema=ArticleRead,
)

More detail in the documentation

consolidate *_params dependencies

The six individual dependency methods (offset_params, cursor_params, paginate_params, filter_params, search_params, order_params) are replaced by three consolidated methods. Each bundles pagination, search, filter, and order into a single Depends() call with boolean feature toggles.

@router.get("/offset")
async def list_articles_offset(
    session: SessionDep,
    params: Annotated[
        dict,
        Depends(
            ArticleCrud.offset_paginate_params(
                default_page_size=20,
                max_page_size=100,
                default_order_field=Article.created_at,
            )
        ),
    ],
) -> OffsetPaginatedResponse[ArticleRead]:
    return await ArticleCrud.offset_paginate(session=session, **params, schema=ArticleRead)

More detail in the documentation

What's Changed

Breaking Changes

  • consolidate *_params dependencies into per-paginate-style methods with feature toggles (#209)
  • rework async event system (#196)

Features

  • fixtures multi-variant contexts, custom Enum support, and context-filtered loading (#187)
  • add search_column parameter and search_columns response field for targeted search (#207)

Refactors

  • batch insert fixtures (#188)

Docs

  • add documentation versioning (#125)

Bug Fixes

  • create_db_session commits via real transaction, not savepoint (#184)
  • deduplicate relationship joins when searchable_fields and facet_fields reference the same model (#217)
  • handle boolean and ARRAY column types in filter_by facet filtering (#203)
  • suppress UPDATE callbacks for objects deleted in the same transaction (#205)
  • batch insert normalizes away omitted nullable columns (#198)
  • facet keys always use full relation chain (#190)
  • normalize batch insert rows to prevent silent data loss for nullable columns (#192)
  • snapshot nullable columns correctly in WatchedFieldsMixin callback (#194)

Internal

  • bump pygments from 2.19.2 to 2.20.0 (#199)
  • bump codecov/codecov-action from 5 to 6 (#211)
  • bump zensical from 0.0.30 to 0.0.31 (#212)
  • bump ruff from 0.15.7 to 0.15.8 (#213)
  • bump fastapi from 0.135.1 to 0.135.3 (#214)
  • bump ty from 0.0.25 to 0.0.27 (#215)

Full Changelog: v2.4.3...v3.0.0

2.4.3

26 Mar 12:01

Choose a tag to compare

What's Changed

Bug Fixes

  • force auto-begin in create_db_dependency so lock_tables always uses savepoints (#176)

Internal

  • bump pytest-cov from 7.0.0 to 7.1.0 (#182)
  • bump ruff from 0.15.6 to 0.15.7 (#180)
  • bump zensical from 0.0.27 to 0.0.29 (#179)
  • bump actions/deploy-pages from 4 to 5 (#177)
  • bump ty from 0.0.23 to 0.0.25 (#178)
  • update uv-build requirement from <0.11.0,>=0.10 to >=0.10,<0.12.0 (#181)

Full Changelog: v2.4.2...v2.4.3

2.4.2

24 Mar 19:42

Choose a tag to compare

What's Changed

Bug Fixes

  • suppress on_create/on_delete for objects created and deleted within the same transaction (#166)
  • await any awaitable callback return value, not only coroutines (#168)
  • inherit @watch field filter from parent classes via MRO traversal (#170)
  • defer on_create/on_update/on_delete dispatch until outermost transaction commits (#172)

Internal

  • test suite cleanup and simplification (#174)

Full Changelog: v2.4.1...v2.4.2