mirror of
https://github.com/d3vyce/fastapi-toolsets.git
synced 2026-08-09 09:54:09 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1bd4e42d7 | ||
|
|
4c8448629d | ||
|
|
e3c3f5f8a3 | ||
|
|
c785b4c1ef |
@@ -278,17 +278,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
return None
|
return None
|
||||||
return search_field_keys(fields)
|
return search_field_keys(fields)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _resolve_sort_columns(
|
|
||||||
cls: type[Self],
|
|
||||||
order_fields: Sequence[QueryableAttribute[Any]] | None,
|
|
||||||
) -> list[str] | None:
|
|
||||||
"""Return sort column keys, or None if no order fields configured."""
|
|
||||||
fields = order_fields if order_fields is not None else cls.order_fields
|
|
||||||
if not fields:
|
|
||||||
return None
|
|
||||||
return sorted(f.key for f in fields)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _build_paginate_params(
|
def _build_paginate_params(
|
||||||
cls: type[Self],
|
cls: type[Self],
|
||||||
@@ -1219,7 +1208,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: str | SearchConfig | None = None,
|
search: str | SearchConfig | None = None,
|
||||||
search_fields: Sequence[SearchFieldType] | None = None,
|
search_fields: Sequence[SearchFieldType] | None = None,
|
||||||
search_column: str | None = None,
|
search_column: str | None = None,
|
||||||
order_fields: Sequence[QueryableAttribute[Any]] | None = None,
|
|
||||||
facet_fields: Sequence[FacetFieldType] | None = None,
|
facet_fields: Sequence[FacetFieldType] | None = None,
|
||||||
filter_by: dict[str, Any] | BaseModel | None = None,
|
filter_by: dict[str, Any] | BaseModel | None = None,
|
||||||
schema: type[BaseModel],
|
schema: type[BaseModel],
|
||||||
@@ -1240,7 +1228,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: Search query string or SearchConfig object
|
search: Search query string or SearchConfig object
|
||||||
search_fields: Fields to search in (overrides class default)
|
search_fields: Fields to search in (overrides class default)
|
||||||
search_column: Restrict search to a single column key.
|
search_column: Restrict search to a single column key.
|
||||||
order_fields: Fields allowed for sorting (overrides class default).
|
|
||||||
facet_fields: Columns to compute distinct values for (overrides class default)
|
facet_fields: Columns to compute distinct values for (overrides class default)
|
||||||
filter_by: Dict of {column_key: value} to filter by declared facet fields.
|
filter_by: Dict of {column_key: value} to filter by declared facet fields.
|
||||||
Keys must match the column.key of a facet field. Scalar → equality,
|
Keys must match the column.key of a facet field. Scalar → equality,
|
||||||
@@ -1321,7 +1308,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
session, facet_fields, filters, search_joins
|
session, facet_fields, filters, search_joins
|
||||||
)
|
)
|
||||||
search_columns = cls._resolve_search_columns(search_fields)
|
search_columns = cls._resolve_search_columns(search_fields)
|
||||||
sort_columns = cls._resolve_sort_columns(order_fields)
|
|
||||||
|
|
||||||
return OffsetPaginatedResponse(
|
return OffsetPaginatedResponse(
|
||||||
data=items,
|
data=items,
|
||||||
@@ -1333,7 +1319,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
),
|
),
|
||||||
filter_attributes=filter_attributes,
|
filter_attributes=filter_attributes,
|
||||||
search_columns=search_columns,
|
search_columns=search_columns,
|
||||||
sort_columns=sort_columns,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1351,7 +1336,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: str | SearchConfig | None = None,
|
search: str | SearchConfig | None = None,
|
||||||
search_fields: Sequence[SearchFieldType] | None = None,
|
search_fields: Sequence[SearchFieldType] | None = None,
|
||||||
search_column: str | None = None,
|
search_column: str | None = None,
|
||||||
order_fields: Sequence[QueryableAttribute[Any]] | None = None,
|
|
||||||
facet_fields: Sequence[FacetFieldType] | None = None,
|
facet_fields: Sequence[FacetFieldType] | None = None,
|
||||||
filter_by: dict[str, Any] | BaseModel | None = None,
|
filter_by: dict[str, Any] | BaseModel | None = None,
|
||||||
schema: type[BaseModel],
|
schema: type[BaseModel],
|
||||||
@@ -1373,7 +1357,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: Search query string or SearchConfig object.
|
search: Search query string or SearchConfig object.
|
||||||
search_fields: Fields to search in (overrides class default).
|
search_fields: Fields to search in (overrides class default).
|
||||||
search_column: Restrict search to a single column key.
|
search_column: Restrict search to a single column key.
|
||||||
order_fields: Fields allowed for sorting (overrides class default).
|
|
||||||
facet_fields: Columns to compute distinct values for (overrides class default).
|
facet_fields: Columns to compute distinct values for (overrides class default).
|
||||||
filter_by: Dict of {column_key: value} to filter by declared facet fields.
|
filter_by: Dict of {column_key: value} to filter by declared facet fields.
|
||||||
Keys must match the column.key of a facet field. Scalar → equality,
|
Keys must match the column.key of a facet field. Scalar → equality,
|
||||||
@@ -1485,7 +1468,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
session, facet_fields, filters, search_joins
|
session, facet_fields, filters, search_joins
|
||||||
)
|
)
|
||||||
search_columns = cls._resolve_search_columns(search_fields)
|
search_columns = cls._resolve_search_columns(search_fields)
|
||||||
sort_columns = cls._resolve_sort_columns(order_fields)
|
|
||||||
|
|
||||||
return CursorPaginatedResponse(
|
return CursorPaginatedResponse(
|
||||||
data=items,
|
data=items,
|
||||||
@@ -1497,7 +1479,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
),
|
),
|
||||||
filter_attributes=filter_attributes,
|
filter_attributes=filter_attributes,
|
||||||
search_columns=search_columns,
|
search_columns=search_columns,
|
||||||
sort_columns=sort_columns,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
@@ -1519,7 +1500,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: str | SearchConfig | None = ...,
|
search: str | SearchConfig | None = ...,
|
||||||
search_fields: Sequence[SearchFieldType] | None = ...,
|
search_fields: Sequence[SearchFieldType] | None = ...,
|
||||||
search_column: str | None = ...,
|
search_column: str | None = ...,
|
||||||
order_fields: Sequence[QueryableAttribute[Any]] | None = ...,
|
|
||||||
facet_fields: Sequence[FacetFieldType] | None = ...,
|
facet_fields: Sequence[FacetFieldType] | None = ...,
|
||||||
filter_by: dict[str, Any] | BaseModel | None = ...,
|
filter_by: dict[str, Any] | BaseModel | None = ...,
|
||||||
schema: type[BaseModel],
|
schema: type[BaseModel],
|
||||||
@@ -1544,7 +1524,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: str | SearchConfig | None = ...,
|
search: str | SearchConfig | None = ...,
|
||||||
search_fields: Sequence[SearchFieldType] | None = ...,
|
search_fields: Sequence[SearchFieldType] | None = ...,
|
||||||
search_column: str | None = ...,
|
search_column: str | None = ...,
|
||||||
order_fields: Sequence[QueryableAttribute[Any]] | None = ...,
|
|
||||||
facet_fields: Sequence[FacetFieldType] | None = ...,
|
facet_fields: Sequence[FacetFieldType] | None = ...,
|
||||||
filter_by: dict[str, Any] | BaseModel | None = ...,
|
filter_by: dict[str, Any] | BaseModel | None = ...,
|
||||||
schema: type[BaseModel],
|
schema: type[BaseModel],
|
||||||
@@ -1568,7 +1547,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: str | SearchConfig | None = None,
|
search: str | SearchConfig | None = None,
|
||||||
search_fields: Sequence[SearchFieldType] | None = None,
|
search_fields: Sequence[SearchFieldType] | None = None,
|
||||||
search_column: str | None = None,
|
search_column: str | None = None,
|
||||||
order_fields: Sequence[QueryableAttribute[Any]] | None = None,
|
|
||||||
facet_fields: Sequence[FacetFieldType] | None = None,
|
facet_fields: Sequence[FacetFieldType] | None = None,
|
||||||
filter_by: dict[str, Any] | BaseModel | None = None,
|
filter_by: dict[str, Any] | BaseModel | None = None,
|
||||||
schema: type[BaseModel],
|
schema: type[BaseModel],
|
||||||
@@ -1597,7 +1575,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search: Search query string or :class:`.SearchConfig` object.
|
search: Search query string or :class:`.SearchConfig` object.
|
||||||
search_fields: Fields to search in (overrides class default).
|
search_fields: Fields to search in (overrides class default).
|
||||||
search_column: Restrict search to a single column key.
|
search_column: Restrict search to a single column key.
|
||||||
order_fields: Fields allowed for sorting (overrides class default).
|
|
||||||
facet_fields: Columns to compute distinct values for (overrides
|
facet_fields: Columns to compute distinct values for (overrides
|
||||||
class default).
|
class default).
|
||||||
filter_by: Dict of ``{column_key: value}`` to filter by declared
|
filter_by: Dict of ``{column_key: value}`` to filter by declared
|
||||||
@@ -1627,7 +1604,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search=search,
|
search=search,
|
||||||
search_fields=search_fields,
|
search_fields=search_fields,
|
||||||
search_column=search_column,
|
search_column=search_column,
|
||||||
order_fields=order_fields,
|
|
||||||
facet_fields=facet_fields,
|
facet_fields=facet_fields,
|
||||||
filter_by=filter_by,
|
filter_by=filter_by,
|
||||||
schema=schema,
|
schema=schema,
|
||||||
@@ -1648,7 +1624,6 @@ class AsyncCrud(Generic[ModelType]):
|
|||||||
search=search,
|
search=search,
|
||||||
search_fields=search_fields,
|
search_fields=search_fields,
|
||||||
search_column=search_column,
|
search_column=search_column,
|
||||||
order_fields=order_fields,
|
|
||||||
facet_fields=facet_fields,
|
facet_fields=facet_fields,
|
||||||
filter_by=filter_by,
|
filter_by=filter_by,
|
||||||
schema=schema,
|
schema=schema,
|
||||||
|
|||||||
@@ -163,7 +163,6 @@ class PaginatedResponse(BaseResponse, Generic[DataT]):
|
|||||||
pagination_type: PaginationType | None = None
|
pagination_type: PaginationType | None = None
|
||||||
filter_attributes: dict[str, list[Any]] | None = None
|
filter_attributes: dict[str, list[Any]] | None = None
|
||||||
search_columns: list[str] | None = None
|
search_columns: list[str] | None = None
|
||||||
sort_columns: list[str] | None = None
|
|
||||||
|
|
||||||
_discriminated_union_cache: ClassVar[dict[Any, Any]] = {}
|
_discriminated_union_cache: ClassVar[dict[Any, Any]] = {}
|
||||||
|
|
||||||
|
|||||||
@@ -247,45 +247,6 @@ class TestResolveSearchColumns:
|
|||||||
assert "username" not in result
|
assert "username" not in result
|
||||||
|
|
||||||
|
|
||||||
class TestResolveSortColumns:
|
|
||||||
"""Tests for _resolve_sort_columns logic."""
|
|
||||||
|
|
||||||
def test_returns_none_when_no_order_fields(self):
|
|
||||||
"""Returns None when cls.order_fields is None and no order_fields passed."""
|
|
||||||
|
|
||||||
class AbstractCrud(AsyncCrud[User]):
|
|
||||||
pass
|
|
||||||
|
|
||||||
assert AbstractCrud._resolve_sort_columns(None) is None
|
|
||||||
|
|
||||||
def test_returns_none_when_empty_order_fields_passed(self):
|
|
||||||
"""Returns None when an empty list is passed explicitly."""
|
|
||||||
crud = CrudFactory(User)
|
|
||||||
assert crud._resolve_sort_columns([]) is None
|
|
||||||
|
|
||||||
def test_returns_keys_from_class_order_fields(self):
|
|
||||||
"""Returns sorted column keys from cls.order_fields when no override passed."""
|
|
||||||
crud = CrudFactory(User, order_fields=[User.username])
|
|
||||||
result = crud._resolve_sort_columns(None)
|
|
||||||
assert result is not None
|
|
||||||
assert "username" in result
|
|
||||||
|
|
||||||
def test_order_fields_override_takes_priority(self):
|
|
||||||
"""Explicit order_fields override cls.order_fields."""
|
|
||||||
crud = CrudFactory(User, order_fields=[User.username])
|
|
||||||
result = crud._resolve_sort_columns([User.email])
|
|
||||||
assert result is not None
|
|
||||||
assert "email" in result
|
|
||||||
assert "username" not in result
|
|
||||||
|
|
||||||
def test_returns_sorted_keys(self):
|
|
||||||
"""Keys are returned in sorted order."""
|
|
||||||
crud = CrudFactory(User, order_fields=[User.email, User.username])
|
|
||||||
result = crud._resolve_sort_columns(None)
|
|
||||||
assert result is not None
|
|
||||||
assert result == sorted(result)
|
|
||||||
|
|
||||||
|
|
||||||
class TestDefaultLoadOptionsIntegration:
|
class TestDefaultLoadOptionsIntegration:
|
||||||
"""Integration tests for default_load_options with real DB queries."""
|
"""Integration tests for default_load_options with real DB queries."""
|
||||||
|
|
||||||
|
|||||||
@@ -1516,101 +1516,6 @@ class TestSearchColumns:
|
|||||||
assert result.data[0].username == "bob"
|
assert result.data[0].username == "bob"
|
||||||
|
|
||||||
|
|
||||||
class TestSortColumns:
|
|
||||||
"""Tests for sort_columns in paginated responses."""
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_sort_columns_returned_in_offset_paginate(
|
|
||||||
self, db_session: AsyncSession
|
|
||||||
):
|
|
||||||
"""offset_paginate response includes sort_columns."""
|
|
||||||
UserSortCrud = CrudFactory(User, order_fields=[User.username, User.email])
|
|
||||||
await UserCrud.create(
|
|
||||||
db_session, UserCreate(username="alice", email="a@test.com")
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await UserSortCrud.offset_paginate(db_session, schema=UserRead)
|
|
||||||
|
|
||||||
assert result.sort_columns is not None
|
|
||||||
assert "username" in result.sort_columns
|
|
||||||
assert "email" in result.sort_columns
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_sort_columns_returned_in_cursor_paginate(
|
|
||||||
self, db_session: AsyncSession
|
|
||||||
):
|
|
||||||
"""cursor_paginate response includes sort_columns."""
|
|
||||||
UserSortCursorCrud = CrudFactory(
|
|
||||||
User,
|
|
||||||
cursor_column=User.id,
|
|
||||||
order_fields=[User.username, User.email],
|
|
||||||
)
|
|
||||||
await UserCrud.create(
|
|
||||||
db_session, UserCreate(username="alice", email="a@test.com")
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await UserSortCursorCrud.cursor_paginate(db_session, schema=UserRead)
|
|
||||||
|
|
||||||
assert result.sort_columns is not None
|
|
||||||
assert "username" in result.sort_columns
|
|
||||||
assert "email" in result.sort_columns
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_sort_columns_none_when_no_order_fields(
|
|
||||||
self, db_session: AsyncSession
|
|
||||||
):
|
|
||||||
"""sort_columns is None when no order_fields are configured."""
|
|
||||||
result = await UserCrud.offset_paginate(db_session, schema=UserRead)
|
|
||||||
|
|
||||||
assert result.sort_columns is None
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_sort_columns_override_in_offset_paginate(
|
|
||||||
self, db_session: AsyncSession
|
|
||||||
):
|
|
||||||
"""order_fields override in offset_paginate is reflected in sort_columns."""
|
|
||||||
await UserCrud.create(
|
|
||||||
db_session, UserCreate(username="alice", email="a@test.com")
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await UserCrud.offset_paginate(
|
|
||||||
db_session, order_fields=[User.email], schema=UserRead
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.sort_columns == ["email"]
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_sort_columns_override_in_cursor_paginate(
|
|
||||||
self, db_session: AsyncSession
|
|
||||||
):
|
|
||||||
"""order_fields override in cursor_paginate is reflected in sort_columns."""
|
|
||||||
UserCursorCrud = CrudFactory(User, cursor_column=User.id)
|
|
||||||
await UserCrud.create(
|
|
||||||
db_session, UserCreate(username="alice", email="a@test.com")
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await UserCursorCrud.cursor_paginate(
|
|
||||||
db_session, order_fields=[User.username], schema=UserRead
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.sort_columns == ["username"]
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
|
||||||
async def test_sort_columns_are_sorted_alphabetically(
|
|
||||||
self, db_session: AsyncSession
|
|
||||||
):
|
|
||||||
"""sort_columns keys are returned in alphabetical order."""
|
|
||||||
UserSortCrud = CrudFactory(User, order_fields=[User.email, User.username])
|
|
||||||
await UserCrud.create(
|
|
||||||
db_session, UserCreate(username="alice", email="a@test.com")
|
|
||||||
)
|
|
||||||
|
|
||||||
result = await UserSortCrud.offset_paginate(db_session, schema=UserRead)
|
|
||||||
|
|
||||||
assert result.sort_columns is not None
|
|
||||||
assert result.sort_columns == sorted(result.sort_columns)
|
|
||||||
|
|
||||||
|
|
||||||
class TestOrderParamsViaConsolidated:
|
class TestOrderParamsViaConsolidated:
|
||||||
"""Tests for order params via consolidated offset_paginate_params()."""
|
"""Tests for order params via consolidated offset_paginate_params()."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user