-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #421 from ral-facilities/add-spares-definition-put…
…-#413 Add endpoint to set spares definition #413
- Loading branch information
Showing
32 changed files
with
1,096 additions
and
115 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,65 @@ | ||
""" | ||
Module for defining the database models for representing settings. | ||
""" | ||
|
||
from abc import ABC, abstractmethod | ||
from typing import ClassVar | ||
|
||
from pydantic import BaseModel, Field | ||
|
||
from inventory_management_system_api.models.custom_object_id_data_types import CustomObjectIdField, StringObjectIdField | ||
from inventory_management_system_api.models.usage_status import UsageStatusOut | ||
|
||
|
||
class SettingInBase(BaseModel, ABC): | ||
""" | ||
Base input database model for a setting. | ||
""" | ||
|
||
@property | ||
@staticmethod | ||
@abstractmethod | ||
def SETTING_ID() -> str: # pylint: disable=invalid-name | ||
"""ID of the setting. Ensures this value can be obtained from the class type itself as a static variable.""" | ||
|
||
|
||
class SettingOutBase(SettingInBase): | ||
""" | ||
Base output database model for a setting. | ||
""" | ||
|
||
id: StringObjectIdField = Field(alias="_id") | ||
|
||
|
||
class SparesDefinitionUsageStatusIn(BaseModel): | ||
""" | ||
Input database model for a usage status in a spares definition. | ||
""" | ||
|
||
id: CustomObjectIdField | ||
|
||
|
||
class SparesDefinitionUsageStatusOut(BaseModel): | ||
""" | ||
Output database model for a usage status in a spares definition. | ||
""" | ||
|
||
id: StringObjectIdField | ||
|
||
|
||
class SparesDefinitionIn(SettingInBase): | ||
""" | ||
Input database model for a spares definition. | ||
""" | ||
|
||
SETTING_ID: ClassVar[str] = "spares_definition" | ||
|
||
usage_statuses: list[SparesDefinitionUsageStatusIn] | ||
|
||
|
||
class SparesDefinitionOut(SparesDefinitionIn, SettingOutBase): | ||
""" | ||
Output database model for a spares definition. | ||
""" | ||
|
||
usage_statuses: list[UsageStatusOut] |
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
106 changes: 106 additions & 0 deletions
106
inventory_management_system_api/repositories/setting.py
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,106 @@ | ||
""" | ||
Module for providing a repository for managing settings in a MongoDB database. | ||
""" | ||
|
||
import logging | ||
from typing import Optional, Type, TypeVar | ||
|
||
from pymongo.client_session import ClientSession | ||
from pymongo.collection import Collection | ||
|
||
from inventory_management_system_api.core.database import DatabaseDep | ||
from inventory_management_system_api.models.setting import SettingInBase, SettingOutBase, SparesDefinitionOut | ||
|
||
logger = logging.getLogger() | ||
|
||
# Template types for models inheriting from SettingIn/OutBase so this repo can be used generically for multiple settings | ||
SettingInBaseT = TypeVar("SettingInBaseT", bound=SettingInBase) | ||
SettingOutBaseT = TypeVar("SettingOutBaseT", bound=SettingOutBase) | ||
|
||
|
||
# Aggregation pipeline for getting the spares definition complete with usage status data | ||
SPARES_DEFINITION_GET_AGGREGATION_PIPELINE: list = [ | ||
# Only perform this on the relevant document | ||
{"$match": {"_id": SparesDefinitionOut.SETTING_ID}}, | ||
# Deconstruct the usage statuses so can go through them one by one | ||
{"$unwind": "$usage_statuses"}, | ||
# Find and store actual usage status data as 'statusDetails' | ||
{ | ||
"$lookup": { | ||
"from": "usage_statuses", | ||
"localField": "usage_statuses.id", | ||
"foreignField": "_id", | ||
"as": "statusDetails", | ||
} | ||
}, | ||
{"$unwind": "$statusDetails"}, | ||
# Merge the two sets of documents together | ||
{"$addFields": {"usage_statuses": {"$mergeObjects": ["$usage_statuses", "$statusDetails"]}}}, | ||
# Remove the temporary 'statusDetails' field as no longer needed | ||
{"$unset": "statusDetails"}, | ||
# Reconstruct the original document by merging with the original fields | ||
{ | ||
"$group": { | ||
"_id": "$_id", | ||
"usage_statuses": {"$push": "$usage_statuses"}, | ||
"otherFields": {"$first": "$$ROOT"}, | ||
} | ||
}, | ||
{"$replaceRoot": {"newRoot": {"$mergeObjects": ["$otherFields", {"usage_statuses": "$usage_statuses"}]}}}, | ||
] | ||
|
||
|
||
class SettingRepo: | ||
""" | ||
Repository for managing settings in a MongoDB database. | ||
""" | ||
|
||
def __init__(self, database: DatabaseDep) -> None: | ||
""" | ||
Initialize the `SettingRepo` with a MongoDB database instance. | ||
:param database: The database to use. | ||
""" | ||
self._database = database | ||
self._settings_collection: Collection = self._database.settings | ||
|
||
def upsert( | ||
self, setting: SettingInBaseT, out_model_type: Type[SettingOutBaseT], session: ClientSession = None | ||
) -> SettingOutBaseT: | ||
""" | ||
Update or insert a setting in a MongoDB database depending on whether it already exists. | ||
:param setting: Setting containing the fields to be updated. Also contains the ID for lookup. | ||
:param out_model_type: The output type of the setting's model. | ||
:param session: PyMongo ClientSession to use for database operations. | ||
:return: The updated setting. | ||
""" | ||
|
||
logger.info("Assigning setting with ID: %s in the database", setting.SETTING_ID) | ||
self._settings_collection.update_one( | ||
{"_id": setting.SETTING_ID}, {"$set": setting.model_dump(by_alias=True)}, upsert=True, session=session | ||
) | ||
|
||
return self.get(out_model_type=out_model_type, session=session) | ||
|
||
def get(self, out_model_type: Type[SettingOutBaseT], session: ClientSession = None) -> Optional[SettingOutBaseT]: | ||
""" | ||
Retrieve a setting from a MongoDB database. | ||
:param out_model_type: The output type of the setting's model. Also contains the ID for lookup. | ||
:param session: PyMongo ClientSession to use for database operations. | ||
:return: Retrieved setting or `None` if not found. | ||
""" | ||
|
||
if out_model_type is SparesDefinitionOut: | ||
# The spares definition contains a list of usage statuses - use an aggregate query here to obtain | ||
# the actual usage status entities instead of just their stored ID | ||
|
||
result = list(self._settings_collection.aggregate(SPARES_DEFINITION_GET_AGGREGATION_PIPELINE)) | ||
setting = result[0] if len(result) > 0 else None | ||
else: | ||
setting = self._settings_collection.find_one({"_id": out_model_type.SETTING_ID}, session=session) | ||
|
||
if setting is not None: | ||
return out_model_type(**setting) | ||
return None |
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,39 @@ | ||
""" | ||
Module for providing an API router which defines routes for managing settings using the `SettingService` service. | ||
""" | ||
|
||
import logging | ||
from typing import Annotated | ||
|
||
from fastapi import APIRouter, Depends, HTTPException, status | ||
|
||
from inventory_management_system_api.core.exceptions import InvalidObjectIdError, MissingRecordError | ||
from inventory_management_system_api.schemas.setting import SparesDefinitionPutSchema, SparesDefinitionSchema | ||
from inventory_management_system_api.services.setting import SettingService | ||
|
||
logger = logging.getLogger() | ||
|
||
router = APIRouter(prefix="/v1/settings", tags=["settings"]) | ||
|
||
SettingServiceDep = Annotated[SettingService, Depends(SettingService)] | ||
|
||
|
||
@router.put( | ||
path="/spares_definition", | ||
summary="Update the definition of a spare", | ||
response_description="Spares definition updated successfully", | ||
) | ||
def update_spares_definition( | ||
spares_definition: SparesDefinitionPutSchema, setting_service: SettingServiceDep | ||
) -> SparesDefinitionSchema: | ||
# pylint: disable=missing-function-docstring | ||
logger.info("Updating spares definition") | ||
logger.debug("Spares definition data: %s", spares_definition) | ||
|
||
try: | ||
updated_spares_definition = setting_service.update_spares_definition(spares_definition) | ||
return SparesDefinitionSchema(**updated_spares_definition.model_dump()) | ||
except (MissingRecordError, InvalidObjectIdError) as exc: | ||
message = "A specified usage status does not exist" | ||
logger.exception(message) | ||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=message) from exc |
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
Oops, something went wrong.