Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/pdct 654 return empty 200 on all search entities when no results foun #54

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions app/api/api_v1/query_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from typing import Union, cast

from fastapi import HTTPException, status


def get_query_params_as_dict(query_params) -> dict[str, Union[str, int]]:
print(query_params)
return {k: query_params[k] for k in query_params.keys()}


def set_default_query_params(
query_params,
default_search_fields,
default_query_term: str = "",
default_max_results: int = 500,
) -> dict[str, Union[str, int]]:
query_fields = query_params.keys()

if len(query_fields) < 1:
return {"q": default_query_term, "max_results": default_max_results}

if "max_results" not in query_fields:
query_params["max_results"] = default_max_results

query_params = remove_duplicate_query_params(query_params, default_search_fields)
return query_params


def validate_query_params(
query_params, valid_params: list[str] = ["q", "max_results"]
) -> bool:
query_fields = query_params.keys()
invalid_params = [x for x in query_fields if x not in valid_params]
if any(invalid_params):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Search parameters are invalid: {invalid_params}",
)

if not isinstance(query_params["max_results"], int):
try:
query_params.update({"max_results": cast(int, query_params["max_results"])})
except Exception:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Maximum results must be an integer value",
)
return True


def remove_duplicate_query_params(
query_params, params_to_remove: list[str]
) -> dict[str, Union[str, int]]:
for param in params_to_remove:
if all(k in query_params.keys() for k in ("q", param)):
query_params.pop(param)

return query_params
57 changes: 43 additions & 14 deletions app/api/api_v1/routers/collection.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
"""Endpoints for managing the Collection entity."""
import logging

from fastapi import APIRouter, HTTPException, Request, status
from app.errors import RepositoryError, ValidationError

import app.service.collection as collection_service
from app.api.api_v1.query_params import (
get_query_params_as_dict,
set_default_query_params,
validate_query_params,
)
from app.errors import RepositoryError, ValidationError
from app.model.collection import (
CollectionCreateDTO,
CollectionReadDTO,
CollectionWriteDTO,
CollectionCreateDTO,
)
import app.service.collection as collection_service

collections_router = r = APIRouter()

Expand Down Expand Up @@ -69,27 +75,50 @@ async def get_all_collections() -> list[CollectionReadDTO]:
"/collections/",
response_model=list[CollectionReadDTO],
)
async def search_collection(q: str = "") -> list[CollectionReadDTO]:
async def search_collection(request: Request) -> list[CollectionReadDTO]:
"""
Searches for collections matching the "q" URL parameter.

:param str q: The string to match, defaults to ""
:raises HTTPException: If nothing found a 404 is returned.
:return list[CollectionDTO]: A list of matching collections.
Searches for collections matching URL parameters ("q" by default).

:param Request request: The fields to match against and the values
to search for. Defaults to searching for "" in collection titles
and summaries.
:raises HTTPException: If invalid fields passed a 400 is returned.
:raises HTTPException: If a DB error occurs a 503 is returned.
:raises HTTPException: If the search request times out a 408 is
returned.
:return list[CollectionReadDTO]: A list of matching collections
(which can be empty).
"""

query_params = get_query_params_as_dict(request.query_params)

DEFAULT_SEARCH_FIELDS = ["title", "description"]
katybaulch marked this conversation as resolved.
Show resolved Hide resolved
query_params = set_default_query_params(query_params, DEFAULT_SEARCH_FIELDS)

VALID_PARAMS = ["q", "max_results"]
validate_query_params(query_params, VALID_PARAMS)

try:
collections = collection_service.search(q)
collections = collection_service.search(query_params)
except ValidationError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=e.message)
except RepositoryError as e:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=e.message
)

if len(collections) == 0:
except TimeoutError:
msg = (
"Request timed out fetching matching collections. Try adjusting your query."
)
_LOGGER.error(msg)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Collections not found for term: {q}",
status_code=status.HTTP_408_REQUEST_TIMEOUT,
detail=msg,
)

if len(collections) == 0:
_LOGGER.info(f"Collections not found for terms: {query_params}")

return collections


Expand Down
53 changes: 39 additions & 14 deletions app/api/api_v1/routers/document.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
"""Endpoints for managing the Document entity."""
import logging
from fastapi import APIRouter, HTTPException, status

from fastapi import APIRouter, HTTPException, Request, status

import app.service.document as document_service
from app.api.api_v1.query_params import (
get_query_params_as_dict,
set_default_query_params,
validate_query_params,
)
from app.errors import RepositoryError, ValidationError
from app.model.document import (
DocumentCreateDTO,
DocumentReadDTO,
DocumentWriteDTO,
)

import app.service.document as document_service

document_router = r = APIRouter()

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -69,27 +75,46 @@ async def get_all_documents() -> list[DocumentReadDTO]:
"/documents/",
response_model=list[DocumentReadDTO],
)
async def search_document(q: str = "") -> list[DocumentReadDTO]:
async def search_document(request: Request) -> list[DocumentReadDTO]:
"""
Searches for documents matching the "q" URL parameter.

:param str q: The string to match, defaults to ""
:raises HTTPException: If nothing found a 404 is returned.
:return list[DocumentDTO]: A list of matching documents.
Searches for documents matching URL parameters ("q" by default).

:param Request request: The fields to match against and the values
to search for. Defaults to searching for "" in document titles.
:raises HTTPException: If invalid fields passed a 400 is returned.
:raises HTTPException: If a DB error occurs a 503 is returned.
:raises HTTPException: If the search request times out a 408 is
returned.
:return list[DocumentReadDTO]: A list of matching documents (which
can be empty).
"""
query_params = get_query_params_as_dict(request.query_params)

DEFAULT_SEARCH_FIELDS = ["title"]
query_params = set_default_query_params(query_params, DEFAULT_SEARCH_FIELDS)

VALID_PARAMS = ["q", "max_results"]
validate_query_params(query_params, VALID_PARAMS)

try:
documents = document_service.search(q)
documents = document_service.search(query_params)
except ValidationError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=e.message)
except RepositoryError as e:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=e.message
)

if len(documents) == 0:
except TimeoutError:
msg = "Request timed out fetching matching documents. Try adjusting your query."
_LOGGER.error(msg)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Documents not found for term: {q}",
status_code=status.HTTP_408_REQUEST_TIMEOUT,
detail=msg,
)

if len(documents) == 0:
_LOGGER.info(f"Documents not found for terms: {query_params}")

return documents


Expand Down
49 changes: 37 additions & 12 deletions app/api/api_v1/routers/event.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
"""Endpoints for managing Family Event entities."""
import logging

from fastapi import APIRouter, HTTPException, status
from fastapi import APIRouter, HTTPException, Request, status

import app.service.event as event_service
from app.api.api_v1.query_params import (
get_query_params_as_dict,
set_default_query_params,
validate_query_params,
)
from app.errors import RepositoryError, ValidationError
from app.model.event import EventCreateDTO, EventReadDTO, EventWriteDTO

Expand Down Expand Up @@ -37,27 +42,47 @@ async def get_all_events() -> list[EventReadDTO]:
"/events/",
response_model=list[EventReadDTO],
)
async def search_event(q: str = "") -> list[EventReadDTO]:
async def search_event(request: Request) -> list[EventReadDTO]:
"""
Searches for family events matching the "q" URL parameter.

:param str q: The string to match, defaults to ""
:raises HTTPException: If nothing found a 404 is returned.
:return list[EventDTO]: A list of matching events.
Searches for family events matching URL parameters ("q" by default).

:param Request request: The fields to match against and the values
to search for. Defaults to searching for "" in event titles and
type names.
:raises HTTPException: If invalid fields passed a 400 is returned.
:raises HTTPException: If a DB error occurs a 503 is returned.
:raises HTTPException: If the search request times out a 408 is
returned.
:return list[EventReadDTO]: A list of matching events (which can be
empty).
"""
query_params = get_query_params_as_dict(request.query_params)

DEFAULT_SEARCH_FIELDS = ["title", "event_type_value"]
query_params = set_default_query_params(query_params, DEFAULT_SEARCH_FIELDS)

VALID_PARAMS = ["q", "max_results"]
validate_query_params(query_params, VALID_PARAMS)

try:
events_found = event_service.search(q)
events_found = event_service.search(query_params)
except ValidationError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=e.message)
except RepositoryError as e:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=e.message
)

if not events_found:
except TimeoutError:
msg = "Request timed out fetching matching events. Try adjusting your query."
_LOGGER.error(msg)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Events not found for term: {q}",
status_code=status.HTTP_408_REQUEST_TIMEOUT,
detail=msg,
)

if len(events_found) == 0:
_LOGGER.info(f"Events not found for terms: {query_params}")

return events_found


Expand Down
51 changes: 14 additions & 37 deletions app/api/api_v1/routers/family.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
implemented directly accesses the "repository" layer.
"""
import logging
from typing import Union, cast

from fastapi import APIRouter, HTTPException, Request, status

import app.service.family as family_service
from app.api.api_v1.query_params import (
get_query_params_as_dict,
set_default_query_params,
validate_query_params,
)
from app.errors import RepositoryError, ValidationError
from app.model.family import FamilyCreateDTO, FamilyReadDTO, FamilyWriteDTO

Expand Down Expand Up @@ -77,46 +81,19 @@ async def search_family(request: Request) -> list[FamilyReadDTO]:
to search for. Defaults to searching for "" in family titles and
summaries.
:raises HTTPException: If invalid fields passed a 400 is returned.
:raises HTTPException: If nothing found a 404 is returned.
:return list[FamilyDTO]: A list of matching families.
:raises HTTPException: If a DB error occurs a 503 is returned.
:raises HTTPException: If the search request times out a 408 is
returned.
:return list[FamilyDTO]: A list of matching families (which can be
empty).
"""
query_params: dict[str, Union[str, int]] = {
k: request.query_params[k] for k in request.query_params.keys()
}
query_params = get_query_params_as_dict(request.query_params)

query_fields = query_params.keys()
if len(query_fields) < 1:
query_params = {"q": ""}
DEFAULT_SEARCH_FIELDS = ["title", "description"]
query_params = set_default_query_params(query_params, DEFAULT_SEARCH_FIELDS)

VALID_PARAMS = ["q", "title", "description", "geography", "status", "max_results"]
invalid_params = [x for x in query_fields if x not in VALID_PARAMS]
if any(invalid_params):
msg = f"Search parameters are invalid: {invalid_params}"
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=msg,
)

if "q" in query_fields:
if "title" in query_fields:
query_params.pop("title")
if "description" in query_fields:
query_params.pop("description")

DEFAULT_MAX_RESULTS = 500
if "max_results" not in query_fields:
query_params["max_results"] = DEFAULT_MAX_RESULTS
else:
if not isinstance(query_params["max_results"], int):
try:
query_params.update(
{"max_results": cast(int, query_params["max_results"])}
)
except Exception:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Maximum results must be an integer value",
)
validate_query_params(query_params, VALID_PARAMS)

try:
families = family_service.search(query_params)
Expand Down
Loading
Loading