Skip to content

Commit

Permalink
Feature/pdct 654 return empty 200 on all search entities when no resu…
Browse files Browse the repository at this point in the history
…lts foun (#54)

* PDCT-654 Replaced 404 when no matching docs found with log statement and 200 response.

* PDCT-654 Replaced 404 when no matching colls found with log and 200 response.

* PDCT-654 Moved query param logic into separate file.

* PDCT-654 Updated family search to use query_param.py functions.

* PDCT-654 Set default search params & remove duplicate params in default setter.

* PDCT-654 Renamed variable to 'default_search_fields',

* PDCT-654 Return empty 200 when no collections found during search.

* PDCT-654 Return empty 200 when no documents found during search.

* PDCT-654 Return empty 200 when no events found during search.

* PDCT-654 Updated docstrings with correct DTOs and param descriptions.

* PDCT-654 Refactored logic to remove unnecessary code.

* PDCT-654 Changed 'description' family search param to 'summary' to match DTO.
  • Loading branch information
katybaulch authored Dec 6, 2023
1 parent 6c9562a commit 30bf41e
Show file tree
Hide file tree
Showing 33 changed files with 768 additions and 252 deletions.
46 changes: 46 additions & 0 deletions app/api/api_v1/query_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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_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

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
56 changes: 42 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,49 @@ 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)

query_params = set_default_query_params(query_params)

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
52 changes: 38 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,45 @@ 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)

query_params = set_default_query_params(query_params)

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
48 changes: 36 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,46 @@ 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)

query_params = set_default_query_params(query_params)

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
54 changes: 15 additions & 39 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,18 @@ 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_fields = query_params.keys()
if len(query_fields) < 1:
query_params = {"q": ""}

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,
)
query_params = get_query_params_as_dict(request.query_params)

query_params = set_default_query_params(query_params)

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",
)
VALID_PARAMS = ["q", "title", "summary", "geography", "status", "max_results"]
validate_query_params(query_params, VALID_PARAMS)

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

0 comments on commit 30bf41e

Please sign in to comment.