You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
be-optimumoh/src/database/service.py

145 lines
4.0 KiB
Python

import logging
from typing import Annotated, List, Type, TypeVar
from fastapi import Depends, Query
from pydantic.types import Json, constr
from sqlalchemy import Select, desc, func, or_
from src.database.schema import CommonParams
from .core import DbSession
log = logging.getLogger(__name__)
# allows only printable characters
QueryStr = constr(pattern=r"^[ -~]+$", min_length=1)
def common_parameters(
db_session: DbSession, # type: ignore
current_user: QueryStr = Query(None, alias="currentUser"), # type: ignore
page: int = Query(1, gt=0, le=1000000),
items_per_page: int = Query(5, alias="itemsPerPage", gt=-2, le=1000000),
query_str: QueryStr = Query(None, alias="q"), # type: ignore
filter_spec: QueryStr = Query(None, alias="filter"), # type: ignore
sort_by: List[str] = Query([], alias="sortBy[]"),
descending: List[bool] = Query([], alias="descending[]"),
exclude: List[str] = Query([], alias="exclude[]"),
all: int = Query(0, ge=0, le=1000000),
):
return {
"db_session": db_session,
"page": page,
"items_per_page": items_per_page,
"query_str": query_str,
"filter_spec": filter_spec,
"sort_by": sort_by,
"descending": descending,
"current_user": current_user,
"all": bool(all),
}
CommonParameters = Annotated[
dict[str, int | str | DbSession | QueryStr | Json | List[str] | List[bool]] | bool,
Depends(common_parameters),
]
T = TypeVar("T", bound=CommonParams)
def get_params_factory(model_type: Type[T]):
async def wrapper(
db_session: DbSession,
params: Annotated[model_type, Query()] # type: ignore
):
res = params.model_dump()
return {
"db_session": db_session,
"all": params.is_all,
**res
}
return wrapper
def search(*, query_str: str, query: Query, model, sort=False):
"""Perform a search based on the query."""
search_model = model
if not query_str.strip():
return query
search = []
if hasattr(search_model, "search_vector"):
vector = search_model.search_vector
search.append(vector.op("@@")(func.tsq_parse(query_str)))
if hasattr(search_model, "name"):
search.append(
search_model.name.ilike(f"%{query_str}%"),
)
search.append(search_model.name == query_str)
if not search:
raise Exception(f"Search not supported for model: {model}")
query = query.filter(or_(*search))
if sort:
query = query.order_by(desc(func.ts_rank_cd(vector, func.tsq_parse(query_str))))
return query.params(term=query_str)
async def search_filter_sort_paginate(
db_session: DbSession,
model,
query_str: str = None,
filter_spec: str | dict | None = None,
page: int = 1,
items_per_page: int = 5,
sort_by: List[str] = None,
descending: List[bool] = None,
current_user: str = None,
exclude: List[str] = None,
all: bool = False,
):
"""Common functionality for searching, filtering, sorting, and pagination."""
# Check if model is Select
if not isinstance(model, Select):
query = Select(model)
else:
query = model
if query_str:
sort = False if sort_by else True
query = search(query_str=query_str, query=query, model=model, sort=sort)
# Get total count
count_query = Select(func.count()).select_from(query.subquery())
total = await db_session.scalar(count_query)
if all:
result = await db_session.execute(query)
items = result.scalars().all()
return {
"items": items,
"itemsPerPage": total,
"page": 1,
"total": total,
"totalPages": 1,
}
query = query.offset((page - 1) * items_per_page).limit(items_per_page)
result = await db_session.execute(query)
items = result.scalars().all()
return {
"items": items,
"itemsPerPage": items_per_page,
"page": page,
"total": total,
"totalPages": (total + items_per_page - 1) // items_per_page,
}