Skip to content

Commit

Permalink
add keywords endpoint with facility and keywordgroup filter
Browse files Browse the repository at this point in the history
  • Loading branch information
N-Clerkx committed Aug 26, 2024
1 parent 24933e0 commit cbcc2aa
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 10 deletions.
9 changes: 8 additions & 1 deletion api/dmsapi/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@ def exception_handler_factory(status_code: int) -> Callable:
def handler(request: Request, exc: Exception):
"""I handle exceptions!!."""
# logger.error(exc, exc_info=True)
description = exc.__dict__ if hasattr(exc, "__dict__") else str(exc)
description: str | dict = ""
if exc.args:
description = exc.args[0]
elif hasattr(exc, "__dict__"):
description = exc.__dict__
else:
description = str(exc)

return JSONResponse(
content=ErrorResponse(code=exc.__class__.__name__, description=description),
status_code=status_code,
Expand Down
27 changes: 26 additions & 1 deletion api/dmsapi/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,28 @@ class Keyword_GroupCreate(Keyword_GroupBase):

class Keyword_GroupPublic(Keyword_GroupBase):
id: uuid.UUID
keywords: list["Keyword"] = Relationship(back_populates="group")


class Keyword_GroupPublicWithKeywords(Keyword_GroupPublic):
keywords: list["KeywordPublic"] = []
model_config = {
"json_schema_extra": {
"examples": [
{
"group_name_nl": "Sleutelwoord groep",
"group_name_en": "Keyword group",
"id": str(uuid.uuid4()),
"keywords": [
{
"nl_keyword": "Sleutelwoord",
"en_keyword": "Keyword",
"id": str(uuid.uuid4()),
}
],
}
]
}
}


class KeywordUpdate(SQLModel):
Expand All @@ -52,6 +73,10 @@ class Keyword(KeywordBase, table=True):
group: "Keyword_Group" = Relationship(back_populates="keywords")


class KeywordPublic(KeywordUpdate):
id: uuid.UUID


class KeywordURI:
keyword_id: uuid.UUID = Path(..., description="Keyword ID")

Expand Down
70 changes: 67 additions & 3 deletions api/dmsapi/extensions/keywords/keyword_client.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
from typing import Annotated, List
from typing import Annotated, List, Optional
from uuid import UUID
from fastapi import Path, Query
from stac_fastapi.types.errors import NotFoundError, InvalidQueryParameter
from sqlalchemy.engine import Engine
from sqlalchemy.orm import selectinload
from sqlalchemy.orm import selectinload, lazyload, joinedload
from sqlmodel import Session, select

from dmsapi.database.models import ( # type: ignore
Facility,
FacilityBase,
FacilityCreate,
FacilityKeywordGroupLink,
FacilityList,
Keyword,
Keyword_Group,
Keyword_GroupCreate,
Keyword_GroupPublicWithKeywords,
KeywordBase,
KeywordUpdate,
OKResponse,
Expand Down Expand Up @@ -356,3 +356,67 @@ def delete_keyword(
session.delete(result)
session.commit()
return OKResponse(message="Keyword deleted")

def get_keywords(
self,
facility_id: Annotated[
Optional[str],
Query(title="The ID of the facility to get keywords for"),
] = None,
keyword_group_id: Annotated[
Optional[str],
Query(title="The ID of the group to get keywords for"),
] = None,
) -> list[Keyword_GroupPublicWithKeywords]:
"""Retrieve all keywords from a keyword group or facility.
Args:
facility_id: ID of the facility to retrieve keywords for.
keyword_group_id: ID of the keyword group to retrieve the keywords for.
Returns:
filtered keyword groups with keywords.
"""
try:
facility_uuid = UUID(facility_id) if facility_id else None
except ValueError:
raise InvalidQueryParameter(f"Facility ID {facility_id} invalid UUID")
try:
keyword_group_uuid = UUID(keyword_group_id) if keyword_group_id else None
except ValueError:
raise InvalidQueryParameter(
f"Keyword group ID {keyword_group_id} invalid UUID"
)

if not facility_id and not keyword_group_id:
raise InvalidQueryParameter(
"Either facility_id or keyword_group_id must be provided"
)
if facility_id and keyword_group_id:
raise InvalidQueryParameter(
"Only one of facility_id or keyword_group_id can be provided"
)

with Session(self.db_engine) as session:
if facility_id:
facility = session.exec(
select(Facility)
.where(Facility.id == facility_uuid)
.options(
lazyload(Facility.keyword_groups).joinedload(
Keyword_Group.keywords
)
)
).first()
if facility is None:
raise NotFoundError(f"Facility with ID {facility_id} not found")
keyword_groups = facility.keyword_groups
else:
keyword_group = session.exec(
select(Keyword_Group)
.where(Keyword_Group.id == keyword_group_uuid)
.options(joinedload(Keyword_Group.keywords))
).first()
keyword_groups = [keyword_group]

return keyword_groups
31 changes: 31 additions & 0 deletions api/dmsapi/extensions/keywords/keyword_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Facility,
Keyword,
Keyword_GroupPublic,
Keyword_GroupPublicWithKeywords,
OKResponse,
)

Expand Down Expand Up @@ -80,6 +81,7 @@ def register(self, app: FastAPI) -> None:
self.add_get_keyword()
self.add_update_keyword()
self.add_delete_keyword()
self.add_get_keywords()
app.include_router(self.router, tags=["Keyword Extension"])

def add_create_facility(self):
Expand Down Expand Up @@ -347,3 +349,32 @@ def add_delete_keyword(self):
},
methods=["DELETE"],
)

def add_get_keywords(self):
self.router.add_api_route(
name="Get Keywords",
path="/keywords",
response_model=list[Keyword_GroupPublicWithKeywords],
responses={
200: {
"content": {
MimeTypes.json.value: {},
},
"model": list[Keyword_GroupPublicWithKeywords],
},
400: {
"content": {
MimeTypes.json.value: {},
},
"model": ErrorResponse,
},
404: {
"content": {
MimeTypes.json.value: {},
},
"model": ErrorResponse,
},
},
endpoint=self.client.get_keywords,
methods=["GET"],
)
63 changes: 63 additions & 0 deletions api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,66 @@ async def keyword(keyword_client: KeywordClient, keyword_group: Keyword_Group):
"en_keyword": "english_testword",
}
)


@pytest_asyncio.fixture(scope="function")
async def filled_db(keyword_client: KeywordClient):
# create facilities
facility1 = keyword_client.create_facility({"name": "test_facility"})
facility2 = keyword_client.create_facility({"name": "test_facility2"})

# create keyword group
keyword_group1 = keyword_client.create_keywordgroup(
{"group_name_nl": "testgroup1", "group_name_en": "engelse_testgroup1"}
)
keyword_group2 = keyword_client.create_keywordgroup(
{"group_name_nl": "testgroup2", "group_name_en": "engelse_testgroup2"}
)

# link facility1 to both keyword groups, facility2 to the second keyword group
keyword_client.link_keywordgroup_to_facility(
FacilityKeywordGroupLink(
facility_id=str(facility1.id), keyword_group_id=str(keyword_group1.id)
)
)
keyword_client.link_keywordgroup_to_facility(
FacilityKeywordGroupLink(
facility_id=str(facility1.id), keyword_group_id=str(keyword_group2.id)
)
)
keyword_client.link_keywordgroup_to_facility(
FacilityKeywordGroupLink(
facility_id=str(facility2.id), keyword_group_id=str(keyword_group2.id)
)
)

# fill keywordgroups with keywords
keyword_client.create_keyword(
{
"group_id": keyword_group1.id,
"nl_keyword": "testwoord1group1",
"en_keyword": "english_testword",
}
)
keyword_client.create_keyword(
{
"group_id": keyword_group1.id,
"nl_keyword": "testwoord2group1",
"en_keyword": "english_testword",
}
)
keyword_client.create_keyword(
{
"group_id": keyword_group2.id,
"nl_keyword": "testwoord1group2",
"en_keyword": "english_testword",
}
)
keyword_client.create_keyword(
{
"group_id": keyword_group2.id,
"nl_keyword": "testwoord2group2",
"en_keyword": "english_testword",
}
)
return [facility1, facility2], [keyword_group1, keyword_group2]
5 changes: 0 additions & 5 deletions api/tests/keyword_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,3 @@ async def test_delete_keyword(
assert response.json() == {"message": "Keyword deleted"}
keyword_json = await app_client.get(f"/keyword/{keyword.id}")
assert keyword_json.status_code == 404


# test get keywords by facility

# test get keywords by keyword group
76 changes: 76 additions & 0 deletions api/tests/keywords_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import pytest
from dmsapi.extensions.keywords.keyword_client import KeywordClient
from dmsapi.database.models import Keyword, Keyword_Group # type: ignore
from httpx import AsyncClient


# test get keywords by facility
@pytest.mark.asyncio
async def test_get_keywords_by_facility(
app_client: AsyncClient,
filled_db,
):
facilities, keywordgroups = filled_db
facility1, facility2 = facilities
keywordgroup1, keywordgroup2 = keywordgroups

response = await app_client.get(f"/keywords?facility_id={facility1.id}")
assert response.status_code == 200
response_data = response.json()
assert len(response_data) == 2
keywordgroup_ids = [group["id"] for group in response_data]
assert str(keywordgroup1.id) in keywordgroup_ids
assert str(keywordgroup2.id) in keywordgroup_ids


# test get keywords by keyword group
@pytest.mark.asyncio
async def test_get_keywords_by_keywordgroup(
app_client: AsyncClient,
filled_db,
):
facilities, keywordgroups = filled_db
keywordgroup1, keywordgroup2 = keywordgroups

response = await app_client.get(f"/keywords?keyword_group_id={keywordgroup1.id}")
assert response.status_code == 200
response_data = response.json()
assert len(response_data) == 1
assert response_data[0]["id"] == str(keywordgroup1.id)


# test get keywords by facility and keyword group invalid
@pytest.mark.asyncio
async def test_get_keywords_by_facility_and_keywordgroup_invalid(
app_client: AsyncClient,
filled_db,
):
facilities, keywordgroups = filled_db
facility1, facility2 = facilities
keywordgroup1, keywordgroup2 = keywordgroups

response = await app_client.get(
f"/keywords?facility_id={facility1.id}&keyword_group_id={keywordgroup2.id}"
)
assert response.status_code == 400
response_data = response.json()
assert response_data["code"] == "InvalidQueryParameter"
assert (
response_data["description"]
== "Only one of facility_id or keyword_group_id can be provided"
)


# test get keywords invalid no parameters
@pytest.mark.asyncio
async def test_get_keywords_no_parameters_invalid(
app_client: AsyncClient,
):
response = await app_client.get(f"/keywords")
assert response.status_code == 400
response_data = response.json()
assert response_data["code"] == "InvalidQueryParameter"
assert (
response_data["description"]
== "Either facility_id or keyword_group_id must be provided"
)

0 comments on commit cbcc2aa

Please sign in to comment.