-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Add Filter, Sort and Pagination Support (#20)
* chore: resolve fastapi exception handlers' signature errors * refactor: extend response data serialization function * fix: resolve user schema type issues * chore: remove leftover print statement * refactor: separate user 404 error condition from user fetch function * chore: move ItemIdType enum to schemas * feat: add pagination schema * feat: add utility function to create paginated responses * feat: add sort input dependency's schema * feat: enable sort on database queries * feat: enable query filtering * refactor: remove extra json body nesting for filter-sort input * feat: allow chaining filter-sort and pagination queries * fix: check for apt negative condition for filter-sort objects
- Loading branch information
1 parent
dd4850c
commit a42ee2c
Showing
10 changed files
with
305 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from fastapi import APIRouter, Depends, Query | ||
|
||
from src import dependencies as deps | ||
from src.schemas.web_responses import users as resp | ||
from src.schemas.responses import AppResponse, BaseResponse | ||
from src.services import poe as service | ||
|
||
|
||
dependencies = [Depends(deps.check_access_token)] | ||
router = APIRouter(prefix="/poe", tags=["Path of Exile"], dependencies=dependencies) | ||
|
||
|
||
# TODO: add responses | ||
@router.get("/categories", responses=resp.GET_USERS_RESPONSES) | ||
async def get_all_categories(): | ||
"""Gets a list of all item categories from the database, mapped by their group names.""" | ||
|
||
item_categories = await service.get_item_categories() | ||
item_category_mapping = service.group_item_categories(item_categories) | ||
|
||
return AppResponse(BaseResponse(data=item_category_mapping)) | ||
|
||
|
||
@router.get("/items", responses=resp.GET_USERS_RESPONSES) | ||
async def get_items_by_group(category_group: str = Query(..., min_length=3, max_length=50)): | ||
"""Gets a list of all items belonging to the given category group.""" | ||
|
||
items = await service.get_items_by_group(category_group) | ||
return AppResponse(BaseResponse(data=items)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import Annotated | ||
from fastapi import Body, Query | ||
from pydantic import BaseModel, BeforeValidator, Field, computed_field | ||
|
||
from src.config.constants.app import FILTER_OPERATIONS, ITEMS_PER_PAGE, MAXIMUM_ITEMS_PER_PAGE, SORT_OPERATIONS | ||
|
||
|
||
lowercase_validator = BeforeValidator(lambda v: v.lower()) | ||
|
||
|
||
class PaginationInput(BaseModel): | ||
"""PaginationInput encapsulates query parameters required for a paginated response.""" | ||
|
||
page: int = Query(1, gt=0) | ||
per_page: int = Query(ITEMS_PER_PAGE, gt=0, lte=MAXIMUM_ITEMS_PER_PAGE) | ||
|
||
@computed_field | ||
@property | ||
def offset(self) -> int: | ||
"""Calculates the offset value for use in database queries.""" | ||
|
||
return (self.page - 1) * self.per_page | ||
|
||
|
||
class SortSchema(BaseModel): | ||
"""SortInput encapsulates the sorting schema model, requiring the field to sort on, and the sort operation type.""" | ||
|
||
field: Annotated[str, lowercase_validator] | ||
operation: Annotated[SORT_OPERATIONS, lowercase_validator] | ||
|
||
|
||
class FilterSchema(BaseModel): | ||
"""FilterSchema encapsulates the filter schema model, requiring a field, a valid operation and the value to filter on the field by.""" | ||
|
||
field: Annotated[str, lowercase_validator] | ||
operation: Annotated[FILTER_OPERATIONS, lowercase_validator] | ||
value: str = Field(min_length=1, max_length=200) | ||
|
||
|
||
class FilterSortInput(BaseModel): | ||
"""FilterSortInput wraps filter and sort schema implementations, enabling them to be embedded as JSON body | ||
parameters for FastAPI request handler functions.""" | ||
|
||
filter_: list[FilterSchema] | None = Field(Body(None, embed=True), alias="filter") | ||
sort: list[SortSchema] | None = Field(Body(None, embed=True)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
from collections import defaultdict | ||
|
||
from fastapi import HTTPException | ||
from loguru import logger | ||
from starlette import status | ||
|
||
from src.models.poe import Item, ItemCategory | ||
from src.schemas.poe import ItemBase, ItemCategoryResponse | ||
|
||
|
||
async def get_item_categories() -> list[ItemCategoryResponse]: | ||
"""Gets all item category documents from the database, extracting only the required fields from the documents.""" | ||
|
||
try: | ||
item_categories = await ItemCategory.find_all().project(ItemCategoryResponse).to_list() | ||
except Exception as exc: | ||
logger.error(f"error getting item categories: {exc}") | ||
raise | ||
|
||
return item_categories | ||
|
||
|
||
def group_item_categories(item_categories: list[ItemCategoryResponse]) -> dict[str, list[ItemCategoryResponse]]: | ||
"""Groups item category documents by their category group.""" | ||
|
||
item_category_mapping = defaultdict(list) | ||
|
||
for category in item_categories: | ||
item_category_mapping[category.group].append(category) | ||
|
||
return item_category_mapping | ||
|
||
|
||
async def get_items_by_group(category_group: str) -> list[ItemBase]: | ||
""" | ||
Gets items by given category group. Raises a 400 error if category group is invalid. | ||
""" | ||
|
||
try: | ||
item_category = await ItemCategory.find_one(ItemCategory.group == category_group) | ||
except Exception as exc: | ||
logger.error(f"error getting item category by group '{category_group}': {exc} ") | ||
raise | ||
|
||
if item_category is None: | ||
raise HTTPException(status.HTTP_400_BAD_REQUEST, "Invalid category group.") | ||
|
||
try: | ||
items = ( | ||
await Item.find( | ||
Item.category.group == category_group, # type: ignore | ||
fetch_links=True, | ||
) | ||
.project(ItemBase) | ||
.to_list() | ||
) | ||
except Exception as exc: | ||
logger.error(f"error getting item by category group '{category_group}': {exc}") | ||
raise | ||
|
||
return items |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.