Releases: d3vyce/fastapi-toolsets
4.1.0
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_lockcontext manager (#283) - add
NOWAIT/SKIP LOCKEDrow locking support and extendwith_for_updatetoget_multiandupdate(#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
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 userFour 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_tablesnow takes asession_makerinstead of asession(#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
What's Changed
Bug Fixes
KeyErroron 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
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 NOTHINGavailable viaignore_conflicts=True)m2m_remove— delete specific associations; idempotent if the association doesn't existm2m_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_attrfixtures 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
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
IntEnumcolumn (#231)
Internal
- restrict
ci.ymlworkflow permissions tocontents: read(#233)
Full Changelog: v3.0.2...v3.0.3
3.0.2
What's Changed
Features
- expose
order_columnsin paginated response (#225) - support relation tuples in
order_fieldsfor cross-table sorting (#227)
Bug Fixes
- apply
default_load_optionsaftercreate()andupdate()to preventMissingGreenleton relationship access (#229)
Full Changelog: v3.0.1...v3.0.2
3.0.1
What's Changed
Bug Fixes
- coerce
stringvalues toboolfor Boolean facet field filtering (#219) - widen
JoinTypeto 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
Highlights
Since v2.0.0, many features have been added. Here a quick summary and the related changelog:
v2.1-create_database/cleanup_tablesv2.2- Crudget_or_nonev2.3- Unified paginate and AsyncCrud subclass stylev2.4-WatchedFieldsMixin
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
*_paramsdependencies 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_columnparameter andsearch_columnsresponse field for targeted search (#207)
Refactors
- batch insert fixtures (#188)
Docs
- add documentation versioning (#125)
Bug Fixes
create_db_sessioncommits via real transaction, not savepoint (#184)- deduplicate relationship joins when
searchable_fieldsandfacet_fieldsreference the same model (#217) - handle boolean and ARRAY column types in
filter_byfacet 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
WatchedFieldsMixincallback (#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
What's Changed
Bug Fixes
- force auto-begin in
create_db_dependencysolock_tablesalways 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
What's Changed
Bug Fixes
- suppress
on_create/on_deletefor objects created and deleted within the same transaction (#166) - await any awaitable callback return value, not only coroutines (#168)
- inherit
@watch fieldfilter from parent classes via MRO traversal (#170) - defer
on_create/on_update/on_deletedispatch until outermost transaction commits (#172)
Internal
- test suite cleanup and simplification (#174)
Full Changelog: v2.4.1...v2.4.2