From 98354ad5d66ac624a1c0e97bfd946cb79fa880e2 Mon Sep 17 00:00:00 2001 From: k-allagbe Date: Thu, 10 Oct 2024 17:22:57 -0400 Subject: [PATCH 1/5] issue #106: exceptions & exception handling --- fertiscan/__init__.py | 219 ++-- fertiscan/db/metadata/errors.py | 20 + fertiscan/db/metadata/inspection/__init__.py | 57 +- fertiscan/db/queries/errors.py | 569 ++++++++++ fertiscan/db/queries/ingredient/__init__.py | 35 +- fertiscan/db/queries/inspection/__init__.py | 528 ++++----- fertiscan/db/queries/label/__init__.py | 209 ++-- fertiscan/db/queries/metric/__init__.py | 373 +++--- fertiscan/db/queries/nutrients/__init__.py | 572 +++++----- fertiscan/db/queries/organization/__init__.py | 1008 ++++++++--------- .../db/queries/specification/__init__.py | 202 ++-- fertiscan/db/queries/sub_label/__init__.py | 346 +++--- fertiscan_pyproject.toml | 2 +- .../fertiscan/db/test_guaranteed_analysis.py | 11 +- tests/fertiscan/db/test_organization.py | 4 +- tests/fertiscan/db/test_sub_label.py | 2 +- tests/fertiscan/test_datastore.py | 2 +- 17 files changed, 2260 insertions(+), 1899 deletions(-) create mode 100644 fertiscan/db/metadata/errors.py create mode 100644 fertiscan/db/queries/errors.py diff --git a/fertiscan/__init__.py b/fertiscan/__init__.py index 1d0600bc..12374c19 100644 --- a/fertiscan/__init__.py +++ b/fertiscan/__init__.py @@ -28,8 +28,8 @@ async def register_analysis( - cursor, - container_client, + cursor: Cursor, + container_client: ContainerClient, user_id, hashed_pictures, analysis_dict, @@ -46,44 +46,39 @@ async def register_analysis( Returns: - The analysis_dict with the analysis_id added. """ - try: - if not user.is_a_user_id(cursor=cursor, user_id=user_id): - raise user.UserNotFoundError( - f"User not found based on the given id: {user_id}" - ) - if not container_client.exists(): - raise datastore.ContainerCreationError( - f"Container not found based on the given user_id: {user_id}" - ) - - # Create picture set for this analysis - picture_set_metadata = data_picture_set.build_picture_set( - user_id, len(hashed_pictures) + if not user.is_a_user_id(cursor=cursor, user_id=user_id): + raise user.UserNotFoundError(f"User not found based on the given id: {user_id}") + if not container_client.exists(): + raise datastore.ContainerCreationError( + f"Container not found based on the given user_id: {user_id}" ) - picture_set_id = picture.new_picture_set(cursor, picture_set_metadata, user_id) - await datastore.create_picture_set( - cursor, container_client, len(hashed_pictures), user_id, str(picture_set_id) - ) + # Create picture set for this analysis + picture_set_metadata = data_picture_set.build_picture_set( + user_id, len(hashed_pictures) + ) + picture_set_id = picture.new_picture_set(cursor, picture_set_metadata, user_id) - # Upload pictures to storage - await datastore.upload_pictures( - cursor=cursor, - user_id=user_id, - container_client=container_client, - picture_set_id=picture_set_id, - hashed_pictures=hashed_pictures, - ) + await datastore.create_picture_set( + cursor, container_client, len(hashed_pictures), user_id, str(picture_set_id) + ) - # Register analysis in the database - formatted_analysis = data_inspection.build_inspection_import(analysis_dict) + # Upload pictures to storage + await datastore.upload_pictures( + cursor=cursor, + user_id=user_id, + container_client=container_client, + picture_set_id=picture_set_id, + hashed_pictures=hashed_pictures, + ) - analysis_db = inspection.new_inspection_with_label_info( - cursor, user_id, picture_set_id, formatted_analysis - ) - return analysis_db - except data_inspection.MissingKeyError: - raise + # Register analysis in the database + formatted_analysis = data_inspection.build_inspection_import(analysis_dict) + + analysis_db = inspection.new_inspection_with_label_info( + cursor, user_id, picture_set_id, formatted_analysis + ) + return analysis_db async def update_inspection( @@ -130,7 +125,7 @@ async def update_inspection( async def get_full_inspection_json( - cursor, + cursor: Cursor, inspection_id, user_id=None, picture_set_id=None, @@ -148,85 +143,80 @@ async def get_full_inspection_json( Returns: - The inspection json. """ - try: - if not inspection.is_a_inspection_id( - cursor=cursor, inspection_id=inspection_id + if not inspection.is_a_inspection_id(cursor=cursor, inspection_id=inspection_id): + raise inspection.InspectionNotFoundError( + f"Inspection not found based on the given id: {inspection_id}" + ) + + # Check Ids + if ( + ( + picture_set_id is None + or picture_set_id == "" + or not picture.is_a_picture_set_id( + cursor=cursor, picture_set_id=picture_set_id + ) + ) + or ( + user_id is None + or user_id == "" + or not user.is_a_user_id(cursor=cursor, user_id=user_id) + ) + or (label_info_id is None or label_info_id == "") + or (company_info_id is None or company_info_id == "") + or (manufacturer_info_id is None or manufacturer_info_id == "") + ): + ids = inspection.get_inspection_fk(cursor, inspection_id) + picture_set_id = ids[2] + label_info_id = ids[0] + company_info_id = ids[3] + manufacturer_info_id = ids[4] + user_id = ids[1] + else: + if not picture.is_a_picture_set_id( + cursor=cursor, picture_set_id=picture_set_id ): - raise inspection.InspectionNotFoundError( - f"Inspection not found based on the given id: {inspection_id}" + raise picture.PictureSetNotFoundError( + f"Picture set not found based on the given id: {picture_set_id}" ) - - # Check Ids - if ( - ( - picture_set_id is None - or picture_set_id == "" - or not picture.is_a_picture_set_id( - cursor=cursor, picture_set_id=picture_set_id - ) + if not user.is_a_user_id(cursor=cursor, user_id=user_id): + raise user.UserNotFoundError( + f"User not found based on the given id: {user_id}" ) - or ( - user_id is None - or user_id == "" - or not user.is_a_user_id(cursor=cursor, user_id=user_id) + ids = inspection.get_inspection_fk(cursor, inspection_id) + if not picture_set_id == ids[2]: + raise Warning( + "Picture set id does not match the picture_set_id in the inspection for the given inspection_id" ) - or (label_info_id is None or label_info_id == "") - or (company_info_id is None or company_info_id == "") - or (manufacturer_info_id is None or manufacturer_info_id == "") - ): - ids = inspection.get_inspection_fk(cursor, inspection_id) - picture_set_id = ids[2] - label_info_id = ids[0] - company_info_id = ids[3] - manufacturer_info_id = ids[4] - user_id = ids[1] - else: - if not picture.is_a_picture_set_id( - cursor=cursor, picture_set_id=picture_set_id - ): - raise picture.PictureSetNotFoundError( - f"Picture set not found based on the given id: {picture_set_id}" - ) - if not user.is_a_user_id(cursor=cursor, user_id=user_id): - raise user.UserNotFoundError( - f"User not found based on the given id: {user_id}" - ) - ids = inspection.get_inspection_fk(cursor, inspection_id) - if not picture_set_id == ids[2]: - raise Warning( - "Picture set id does not match the picture_set_id in the inspection for the given inspection_id" - ) - if not label_info_id == ids[0]: - raise Warning( - "Label info id does not match the label_info_id in the inspection for the given inspection_id" - ) - if not company_info_id == ids[3]: - raise Warning( - "Company info id does not match the company_info_id in the inspection for the given inspection_id" - ) - if not manufacturer_info_id == ids[4]: - raise Warning( - "Manufacturer info id does not match the manufacturer_info_id in the inspection for the given inspection_id" - ) - if not user_id == ids[1]: - raise Warning( - "User id does not match the user_id in the inspection for the given inspection_id" - ) - - # Retrieve pictures - # pictures_ids = picture.get_picture_in_picture_set(cursor, picture_set_id) - - # Retrieve label_info - inspection_metadata = data_inspection.build_inspection_export( - cursor, inspection_id, label_info_id - ) + if not label_info_id == ids[0]: + raise Warning( + "Label info id does not match the label_info_id in the inspection for the given inspection_id" + ) + if not company_info_id == ids[3]: + raise Warning( + "Company info id does not match the company_info_id in the inspection for the given inspection_id" + ) + if not manufacturer_info_id == ids[4]: + raise Warning( + "Manufacturer info id does not match the manufacturer_info_id in the inspection for the given inspection_id" + ) + if not user_id == ids[1]: + raise Warning( + "User id does not match the user_id in the inspection for the given inspection_id" + ) + + # Retrieve pictures + # pictures_ids = picture.get_picture_in_picture_set(cursor, picture_set_id) - return inspection_metadata - except inspection.InspectionNotFoundError: - raise + # Retrieve label_info + inspection_metadata = data_inspection.build_inspection_export( + cursor, inspection_id, label_info_id + ) + + return inspection_metadata -async def get_user_analysis_by_verified(cursor, user_id, verified: bool): +async def get_user_analysis_by_verified(cursor: Cursor, user_id, verified: bool): """ This function fetch all the inspection of a user @@ -251,16 +241,9 @@ async def get_user_analysis_by_verified(cursor, user_id, verified: bool): ] """ - try: - if not user.is_a_user_id(cursor=cursor, user_id=user_id): - raise user.UserNotFoundError( - f"User not found based on the given id: {user_id}" - ) - return inspection.get_all_user_inspection_filter_verified( - cursor, user_id, verified - ) - except user.UserNotFoundError: - raise + if not user.is_a_user_id(cursor=cursor, user_id=user_id): + raise user.UserNotFoundError(f"User not found based on the given id: {user_id}") + return inspection.get_all_user_inspection_filter_verified(cursor, user_id, verified) async def delete_inspection( diff --git a/fertiscan/db/metadata/errors.py b/fertiscan/db/metadata/errors.py new file mode 100644 index 00000000..859da058 --- /dev/null +++ b/fertiscan/db/metadata/errors.py @@ -0,0 +1,20 @@ +class MetadataError(Exception): + """Base exception for metadata errors""" + + pass + + +class BuildInspectionImportError(MetadataError): + """Error raised when building the inspection import fails""" + + pass + + +class BuildInspectionExportError(MetadataError): + """Error raised when building the inspection export fails""" + + pass + + +class NPKError(MetadataError): + pass diff --git a/fertiscan/db/metadata/inspection/__init__.py b/fertiscan/db/metadata/inspection/__init__.py index 56a7211c..421d370b 100644 --- a/fertiscan/db/metadata/inspection/__init__.py +++ b/fertiscan/db/metadata/inspection/__init__.py @@ -9,28 +9,21 @@ from pydantic import UUID4, BaseModel, ValidationError, model_validator +from fertiscan.db.metadata.errors import ( + BuildInspectionExportError, + BuildInspectionImportError, + MetadataError, + NPKError, +) from fertiscan.db.queries import ( - ingredient, inspection, label, metric, nutrients, organization, - specification, sub_label, ) - - -class MissingKeyError(Exception): - pass - - -class NPKError(Exception): - pass - - -class MetadataFormattingError(Exception): - pass +from fertiscan.db.queries.errors import QueryError class ValidatedModel(BaseModel): @@ -180,7 +173,9 @@ def build_inspection_import(analysis_form: dict) -> str: if key not in analysis_form: missing_keys.append(key) if len(missing_keys) > 0: - raise MissingKeyError(missing_keys) + raise BuildInspectionImportError( + f"The analysis form is missing keys: {missing_keys}" + ) npk = extract_npk(analysis_form.get("npk")) @@ -335,16 +330,13 @@ def build_inspection_import(analysis_form: dict) -> str: guaranteed_analysis=guaranteed, ) Inspection(**inspection_formatted.model_dump()) - except MissingKeyError as e: - raise MissingKeyError("Missing keys:" + str(e)) + return inspection_formatted.model_dump_json() + except MetadataError: + raise except ValidationError as e: - print(analysis_form.get("cautions_en", [])) - print(analysis_form.get("cautions_fr", [])) - raise MetadataFormattingError( - "Error InspectionCreationError not created: " + str(e) - ) from None - # print(inspection_formatted.model_dump_json()) - return inspection_formatted.model_dump_json() + raise BuildInspectionImportError(f"Validation error: {e}") from e + except Exception as e: + raise BuildInspectionImportError(f"Unexpected error: {e}") from e def build_inspection_export(cursor, inspection_id, label_info_id) -> str: @@ -396,21 +388,10 @@ def build_inspection_export(cursor, inspection_id, label_info_id) -> str: ) return inspection_formatted.model_dump_json() - except ( - label.LabelInformationNotFoundError - or metric.MetricNotFoundError - or organization.OrganizationNotFoundError - or sub_label.SubLabelNotFoundError - or ingredient.IngredientNotFoundError - or nutrients.MicronutrientNotFoundError - or nutrients.GuaranteedNotFoundError - or specification.SpecificationNotFoundError - ): - raise + except QueryError as e: + raise BuildInspectionExportError(f"Error fetching data: {e}") from e except Exception as e: - raise MetadataFormattingError( - "Error Inspection Form not created: " + str(e) - ) from e + raise BuildInspectionImportError(f"Unexpected error: {e}") from e def split_value_unit(value_unit: str) -> dict: diff --git a/fertiscan/db/queries/errors.py b/fertiscan/db/queries/errors.py new file mode 100644 index 00000000..7d7a43c9 --- /dev/null +++ b/fertiscan/db/queries/errors.py @@ -0,0 +1,569 @@ +from functools import wraps + +from psycopg import Error + + +class QueryError(Exception): + """Base exception for all query errors.""" + + pass + + +class InspectionQueryError(QueryError): + """Base exception for all inspection-related query errors.""" + + pass + + +class InspectionNotFoundError(InspectionQueryError): + """Raised when an inspection is not found.""" + + pass + + +class InspectionCreationError(InspectionQueryError): + """Raised when an error occurs during the creation of an inspection.""" + + pass + + +class InspectionUpdateError(InspectionQueryError): + """Raised when an error occurs during the updating of an inspection.""" + + pass + + +class InspectionRetrievalError(InspectionQueryError): + """Raised when an error occurs during the retrieval of an inspection.""" + + pass + + +class InspectionDeleteError(InspectionQueryError): + """Raised when an error occurs during the deletion of an inspection.""" + + pass + + +class LabelInformationQueryError(QueryError): + """Base exception for all label information-related query errors.""" + + pass + + +class LabelInformationNotFoundError(LabelInformationQueryError): + """Raised when a label information is not found.""" + + pass + + +class LabelInformationCreationError(LabelInformationQueryError): + """Raised when an error occurs during the creation of a label information.""" + + pass + + +class LabelInformationUpdateError(LabelInformationQueryError): + """Raised when an error occurs during the updating of a label information.""" + + pass + + +class LabelInformationRetrievalError(LabelInformationQueryError): + """Raised when an error occurs during the retrieval of a label information.""" + + pass + + +class LabelInformationDeleteError(LabelInformationQueryError): + """Raised when an error occurs during the deletion of a label information.""" + + pass + + +class MetricQueryError(QueryError): + """Base exception for all metric-related query errors.""" + + pass + + +class MetricNotFoundError(MetricQueryError): + """Raised when a metric is not found.""" + + pass + + +class MetricCreationError(MetricQueryError): + """Raised when an error occurs during the creation of a metric.""" + + pass + + +class MetricUpdateError(MetricQueryError): + """Raised when an error occurs during the updating of a metric.""" + + pass + + +class MetricRetrievalError(MetricQueryError): + """Raised when an error occurs during the retrieval of a metric.""" + + pass + + +class MetricDeleteError(MetricQueryError): + """Raised when an error occurs during the deletion of a metric.""" + + pass + + +class UnitQueryError(QueryError): + """Base exception for all unit-related query errors.""" + + pass + + +class UnitNotFoundError(UnitQueryError): + """Raised when a unit is not found.""" + + pass + + +class UnitCreationError(UnitQueryError): + """Raised when an error occurs during the creation of a unit.""" + + pass + + +class UnitUpdateError(UnitQueryError): + """Raised when an error occurs during the updating of a unit.""" + + pass + + +class UnitRetrievalError(UnitQueryError): + """Raised when an error occurs during the retrieval of a unit.""" + + pass + + +class UnitDeleteError(UnitQueryError): + """Raised when an error occurs during the deletion of a unit.""" + + pass + + +class ElementCompoundQueryError(QueryError): + """Base exception for all element compound-related query errors.""" + + pass + + +class ElementCompoundNotFoundError(ElementCompoundQueryError): + """Raised when an element compound is not found.""" + + pass + + +class ElementCompoundCreationError(ElementCompoundQueryError): + """Raised when an error occurs during the creation of an element compound.""" + + pass + + +class ElementCompoundUpdateError(ElementCompoundQueryError): + """Raised when an error occurs during the updating of an element compound.""" + + pass + + +class ElementCompoundRetrievalError(ElementCompoundQueryError): + """Raised when an error occurs during the retrieval of an element compound.""" + + pass + + +class ElementCompoundDeleteError(ElementCompoundQueryError): + """Raised when an error occurs during the deletion of an element compound.""" + + pass + + +class MicronutrientQueryError(QueryError): + """Base exception for all micronutrient-related query errors.""" + + pass + + +class MicronutrientNotFoundError(MicronutrientQueryError): + """Raised when a micronutrient is not found.""" + + pass + + +class MicronutrientCreationError(MicronutrientQueryError): + """Raised when an error occurs during the creation of a micronutrient.""" + + pass + + +class MicronutrientUpdateError(MicronutrientQueryError): + """Raised when an error occurs during the updating of a micronutrient.""" + + pass + + +class MicronutrientRetrievalError(MicronutrientQueryError): + """Raised when an error occurs during the retrieval of a micronutrient.""" + + pass + + +class MicronutrientDeleteError(MicronutrientQueryError): + """Raised when an error occurs during the deletion of a micronutrient.""" + + pass + + +class GuaranteedAnalysisQueryError(QueryError): + """Base exception for all guaranteed analysis-related query errors.""" + + pass + + +class GuaranteedAnalysisNotFoundError(GuaranteedAnalysisQueryError): + """Raised when a guaranteed analysis is not found.""" + + pass + + +class GuaranteedAnalysisCreationError(GuaranteedAnalysisQueryError): + """Raised when an error occurs during the creation of a guaranteed analysis.""" + + pass + + +class GuaranteedAnalysisUpdateError(GuaranteedAnalysisQueryError): + """Raised when an error occurs during the updating of a guaranteed analysis.""" + + pass + + +class GuaranteedAnalysisRetrievalError(GuaranteedAnalysisQueryError): + """Raised when an error occurs during the retrieval of a guaranteed analysis.""" + + pass + + +class GuaranteedAnalysisDeleteError(GuaranteedAnalysisQueryError): + """Raised when an error occurs during the deletion of a guaranteed analysis.""" + + pass + + +class OrganizationQueryError(QueryError): + """Base exception for all organization-related query errors.""" + + pass + + +class OrganizationNotFoundError(OrganizationQueryError): + """Raised when an organization is not found.""" + + pass + + +class OrganizationCreationError(OrganizationQueryError): + """Raised when an error occurs during the creation of an organization.""" + + pass + + +class OrganizationUpdateError(OrganizationQueryError): + """Raised when an error occurs during the updating of an organization.""" + + pass + + +class OrganizationRetrievalError(OrganizationQueryError): + """Raised when an error occurs during the retrieval of an organization.""" + + pass + + +class OrganizationDeleteError(OrganizationQueryError): + """Raised when an error occurs during the deletion of an organization.""" + + pass + + +class OrganizationInformationQueryError(QueryError): + """Base exception for all organization information-related query errors.""" + + pass + + +class OrganizationInformationNotFoundError(OrganizationInformationQueryError): + """Raised when organization information is not found.""" + + pass + + +class OrganizationInformationCreationError(OrganizationInformationQueryError): + """Raised when an error occurs during the creation of organization information.""" + + pass + + +class OrganizationInformationUpdateError(OrganizationInformationQueryError): + """Raised when an error occurs during the updating of organization information.""" + + pass + + +class OrganizationInformationRetrievalError(OrganizationInformationQueryError): + """Raised when an error occurs during the retrieval of organization information.""" + + pass + + +class OrganizationInformationDeleteError(OrganizationInformationQueryError): + """Raised when an error occurs during the deletion of organization information.""" + + pass + + +class LocationQueryError(QueryError): + """Base exception for all location-related query errors.""" + + pass + + +class LocationNotFoundError(LocationQueryError): + """Raised when a location is not found.""" + + pass + + +class LocationCreationError(LocationQueryError): + """Raised when an error occurs during the creation of a location.""" + + pass + + +class LocationUpdateError(LocationQueryError): + """Raised when an error occurs during the updating of a location.""" + + pass + + +class LocationRetrievalError(LocationQueryError): + """Raised when an error occurs during the retrieval of a location.""" + + pass + + +class LocationDeleteError(LocationQueryError): + """Raised when an error occurs during the deletion of a location.""" + + pass + + +class RegionQueryError(QueryError): + """Base exception for all region-related query errors.""" + + pass + + +class RegionNotFoundError(RegionQueryError): + """Raised when a region is not found.""" + + pass + + +class RegionCreationError(RegionQueryError): + """Raised when an error occurs during the creation of a region.""" + + pass + + +class RegionUpdateError(RegionQueryError): + """Raised when an error occurs during the updating of a region.""" + + pass + + +class RegionRetrievalError(RegionQueryError): + """Raised when an error occurs during the retrieval of a region.""" + + pass + + +class RegionDeleteError(RegionQueryError): + """Raised when an error occurs during the deletion of a region.""" + + pass + + +class ProvinceQueryError(QueryError): + """Base exception for all province-related query errors.""" + + pass + + +class ProvinceNotFoundError(ProvinceQueryError): + """Raised when a province is not found.""" + + pass + + +class ProvinceCreationError(ProvinceQueryError): + """Raised when an error occurs during the creation of a province.""" + + pass + + +class ProvinceUpdateError(ProvinceQueryError): + """Raised when an error occurs during the updating of a province.""" + + pass + + +class ProvinceRetrievalError(ProvinceQueryError): + """Raised when an error occurs during the retrieval of a province.""" + + pass + + +class ProvinceDeleteError(ProvinceQueryError): + """Raised when an error occurs during the deletion of a province.""" + + pass + + +class SpecificationQueryError(QueryError): + """Base exception for all specification-related query errors.""" + + pass + + +class SpecificationNotFoundError(SpecificationQueryError): + """Raised when a specification is not found.""" + + pass + + +class SpecificationCreationError(SpecificationQueryError): + """Raised when an error occurs during the creation of a specification.""" + + pass + + +class SpecificationUpdateError(SpecificationQueryError): + """Raised when an error occurs during the updating of a specification.""" + + pass + + +class SpecificationRetrievalError(SpecificationQueryError): + """Raised when an error occurs during the retrieval of a specification.""" + + pass + + +class SpecificationDeleteError(SpecificationQueryError): + """Raised when an error occurs during the deletion of a specification.""" + + pass + + +class SubLabelQueryError(QueryError): + """Base exception for all sublabel-related query errors.""" + + pass + + +class SubLabelNotFoundError(SubLabelQueryError): + """Raised when a sublabel is not found.""" + + pass + + +class SubLabelCreationError(SubLabelQueryError): + """Raised when an error occurs during the creation of a sublabel.""" + + pass + + +class SubLabelUpdateError(SubLabelQueryError): + """Raised when an error occurs during the updating of a sublabel.""" + + pass + + +class SubLabelRetrievalError(SubLabelQueryError): + """Raised when an error occurs during the retrieval of a sublabel.""" + + pass + + +class SubLabelDeleteError(SubLabelQueryError): + """Raised when an error occurs during the deletion of a sublabel.""" + + pass + + +class SubTypeQueryError(QueryError): + """Base exception for all subtype-related query errors.""" + + pass + + +class SubTypeNotFoundError(SubTypeQueryError): + """Raised when a subtype is not found.""" + + pass + + +class SubTypeCreationError(SubTypeQueryError): + """Raised when an error occurs during the creation of a subtype.""" + + pass + + +class SubTypeUpdateError(SubTypeQueryError): + """Raised when an error occurs during the updating of a subtype.""" + + pass + + +class SubTypeRetrievalError(SubTypeQueryError): + """Raised when an error occurs during the retrieval of a subtype.""" + + pass + + +class SubTypeDeleteError(SubTypeQueryError): + """Raised when an error occurs during the deletion of a subtype.""" + + pass + + +def handle_query_errors(error_cls=QueryError): + """Decorator for handling query errors.""" + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except QueryError: + raise + except Error as db_error: + raise error_cls(f"Database error: {db_error}") from db_error + except Exception as e: + raise error_cls(f"Unexpected error: {e}") from e + + return wrapper + + return decorator diff --git a/fertiscan/db/queries/ingredient/__init__.py b/fertiscan/db/queries/ingredient/__init__.py index 1dd86ee7..964cab4b 100644 --- a/fertiscan/db/queries/ingredient/__init__.py +++ b/fertiscan/db/queries/ingredient/__init__.py @@ -1,18 +1,24 @@ +from psycopg import Cursor, Error + """ This module represent the function for the Ingredient table """ -class IngredientCreationError(Exception): +class IngredientQueryError(Exception): + pass + + +class IngredientCreationError(IngredientQueryError): pass -class IngredientNotFoundError(Exception): +class IngredientRetrievalError(IngredientQueryError): pass def new_ingredient( - cursor, + cursor: Cursor, name: str, value: float, read_unit: str, @@ -37,13 +43,15 @@ def new_ingredient( return cursor.fetchone()[0] except IngredientCreationError: raise - except Exception: - raise IngredientCreationError("Error: could not create the ingredient") + except Error as db_error: + raise IngredientCreationError(f"Database error: {db_error}") from db_error + except Exception as e: + raise IngredientCreationError(f"Unexpected error: {e}") from e -def get_ingredient_json(cursor, label_id) -> dict: +def get_ingredient_json(cursor: Cursor, label_id) -> dict: """ - This function gets the ingredient json from the database. + This function gets the ingredient JSON from the database. """ try: query = """ @@ -52,11 +60,12 @@ def get_ingredient_json(cursor, label_id) -> dict: cursor.execute(query, (label_id,)) result = cursor.fetchone() if result is None: - raise IngredientNotFoundError("Error: ingredient not found") + raise IngredientRetrievalError("Error: ingredient not found") return result[0] - except IngredientNotFoundError: + + except IngredientRetrievalError: raise - except Exception: - raise IngredientNotFoundError( - "Datastore.db.ingredient unhandled error: could not get the ingredient" - ) + except Error as db_error: + raise IngredientCreationError(f"Database error: {db_error}") from db_error + except Exception as e: + raise IngredientCreationError(f"Unexpected error: {e}") from e diff --git a/fertiscan/db/queries/inspection/__init__.py b/fertiscan/db/queries/inspection/__init__.py index bafae5dd..975dd41a 100644 --- a/fertiscan/db/queries/inspection/__init__.py +++ b/fertiscan/db/queries/inspection/__init__.py @@ -6,32 +6,23 @@ import json from uuid import UUID -from psycopg import Cursor, DatabaseError, Error, OperationalError +from psycopg import Cursor from psycopg.rows import dict_row from psycopg.sql import SQL +from fertiscan.db.queries.errors import ( + InspectionCreationError, + InspectionDeleteError, + InspectionNotFoundError, + InspectionQueryError, + InspectionRetrievalError, + InspectionUpdateError, + handle_query_errors, +) -class InspectionCreationError(Exception): - pass - -class InspectionUpdateError(Exception): - pass - - -class InspectionRetrievalError(Exception): - pass - - -class InspectionDeleteError(Exception): - pass - - -class InspectionNotFoundError(Exception): - pass - - -def new_inspection(cursor, user_id, picture_set_id, verified=False): +@handle_query_errors(InspectionCreationError) +def new_inspection(cursor: Cursor, user_id, picture_set_id, verified=False): """ This function uploads a new inspection to the database. @@ -45,25 +36,25 @@ def new_inspection(cursor, user_id, picture_set_id, verified=False): - The UUID of the inspection. """ - try: - query = """ - INSERT INTO inspection ( - inspector_id, - picture_set_id, - verified - ) - VALUES - (%s, %s, %s) - RETURNING - id - """ - cursor.execute(query, (user_id, picture_set_id, verified)) - return cursor.fetchone()[0] - except Exception: - raise InspectionCreationError("Datastore inspection unhandeled error") - - -def new_inspection_with_label_info(cursor, user_id, picture_set_id, label_json): + query = """ + INSERT INTO inspection ( + inspector_id, + picture_set_id, + verified + ) + VALUES + (%s, %s, %s) + RETURNING + id + """ + cursor.execute(query, (user_id, picture_set_id, verified)) + if result := cursor.fetchone(): + return result[0] + raise InspectionCreationError("Failed to create inspection. No data returned.") + + +@handle_query_errors(InspectionCreationError) +def new_inspection_with_label_info(cursor: Cursor, user_id, picture_set_id, label_json): """ This function calls the new_inspection function within the database and adds the label information to the inspection. @@ -77,17 +68,15 @@ def new_inspection_with_label_info(cursor, user_id, picture_set_id, label_json): Returns: - The json with ids of the inspection and the label information. """ - try: - query = """ - SELECT new_inspection(%s, %s, %s) - """ - cursor.execute(query, (user_id, picture_set_id, label_json)) - return cursor.fetchone()[0] - except Exception as e: - raise InspectionCreationError(e.__str__()) + query = """ + SELECT new_inspection(%s, %s, %s) + """ + cursor.execute(query, (user_id, picture_set_id, label_json)) + return cursor.fetchone()[0] -def is_a_inspection_id(cursor, inspection_id): +@handle_query_errors(InspectionQueryError) +def is_a_inspection_id(cursor: Cursor, inspection_id) -> bool: """ This function checks if the inspection exists in the database. @@ -99,25 +88,23 @@ def is_a_inspection_id(cursor, inspection_id): - The value if the inspection exists. """ - try: - query = """ - SELECT - EXISTS( - SELECT - 1 - FROM - inspection - WHERE - id = %s - ) - """ - cursor.execute(query, (inspection_id,)) - return cursor.fetchone()[0] - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - -def is_inspection_verified(cursor, inspection_id): + query = """ + SELECT + EXISTS( + SELECT + 1 + FROM + inspection + WHERE + id = %s + ) + """ + cursor.execute(query, (inspection_id,)) + return cursor.fetchone()[0] + + +@handle_query_errors(InspectionQueryError) +def is_inspection_verified(cursor: Cursor, inspection_id): """ This function checks if the inspection has been verified. @@ -129,22 +116,24 @@ def is_inspection_verified(cursor, inspection_id): - The value if the inspection has been verified. """ - try: - query = """ - SELECT - verified - FROM - inspection - WHERE - id = %s - """ - cursor.execute(query, (inspection_id,)) - return cursor.fetchone()[0] - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - -def get_inspection(cursor, inspection_id): + query = """ + SELECT + verified + FROM + inspection + WHERE + id = %s + """ + cursor.execute(query, (inspection_id,)) + if result := cursor.fetchone(): + return result[0] + raise InspectionNotFoundError( + "Failed to check inspection verification status. No data returned." + ) + + +@handle_query_errors(InspectionRetrievalError) +def get_inspection(cursor: Cursor, inspection_id): """ This function gets the inspection from the database. @@ -156,30 +145,28 @@ def get_inspection(cursor, inspection_id): - The inspection. """ - try: - query = """ - SELECT - verified, - upload_date, - updated_at, - inspector_id, - label_info_id, - sample_id, - picture_set_id, - fertilizer_id, - inspection_comment - FROM - inspection - WHERE - id = %s - """ - cursor.execute(query, (inspection_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - -def get_inspection_dict(cursor: Cursor, inspection_id: str): + query = """ + SELECT + verified, + upload_date, + updated_at, + inspector_id, + label_info_id, + sample_id, + picture_set_id, + fertilizer_id, + inspection_comment + FROM + inspection + WHERE + id = %s + """ + cursor.execute(query, (inspection_id,)) + return cursor.fetchone() + + +@handle_query_errors(InspectionRetrievalError) +def get_inspection_dict(cursor: Cursor, inspection_id: str | UUID): """ This function fetches the inspection by its ID from the database. @@ -190,14 +177,14 @@ def get_inspection_dict(cursor: Cursor, inspection_id: str): Returns: - The inspection as a dictionary, or None if no record is found. """ - with cursor.connection.cursor(row_factory=dict_row) as dict_cursor: query = SQL("SELECT * FROM inspection WHERE id = %s") dict_cursor.execute(query, (inspection_id,)) return dict_cursor.fetchone() -def get_inspection_original_dataset(cursor, inspection_id): +@handle_query_errors(InspectionQueryError) +def get_inspection_original_dataset(cursor: Cursor, inspection_id): """ This function gets the inspection from the database. @@ -209,22 +196,20 @@ def get_inspection_original_dataset(cursor, inspection_id): - The inspection. """ - try: - query = """ - SELECT - original_dataset - FROM - inspection_factial - WHERE - inspection_id = %s - """ - cursor.execute(query, (inspection_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - -def get_inspection_fk(cursor, inspection_id): + query = """ + SELECT + original_dataset + FROM + inspection_factial + WHERE + inspection_id = %s + """ + cursor.execute(query, (inspection_id,)) + return cursor.fetchone() + + +@handle_query_errors(InspectionQueryError) +def get_inspection_fk(cursor: Cursor, inspection_id): """ This function gets the foreign keys of the inspection from the database. @@ -245,32 +230,30 @@ def get_inspection_fk(cursor, inspection_id): ] """ - try: - query = """ - SELECT - inspection.label_info_id, - inspection.inspector_id, - inspection.picture_set_id, - label_info.company_info_id, - label_info.manufacturer_info_id, - inspection.fertilizer_id, - inspection.sample_id - FROM - inspection - LEFT JOIN - label_information as label_info - ON - inspection.label_info_id = label_info.id - WHERE - inspection.id = %s - """ - cursor.execute(query, (inspection_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - -def get_all_user_inspection_filter_verified(cursor, user_id, verified: bool): + query = """ + SELECT + inspection.label_info_id, + inspection.inspector_id, + inspection.picture_set_id, + label_info.company_info_id, + label_info.manufacturer_info_id, + inspection.fertilizer_id, + inspection.sample_id + FROM + inspection + LEFT JOIN + label_information as label_info + ON + inspection.label_info_id = label_info.id + WHERE + inspection.id = %s + """ + cursor.execute(query, (inspection_id,)) + return cursor.fetchone() + + +@handle_query_errors(InspectionRetrievalError) +def get_all_user_inspection_filter_verified(cursor: Cursor, user_id, verified: bool): """ This function gets all the unverified inspection of a user from the database. @@ -282,45 +265,43 @@ def get_all_user_inspection_filter_verified(cursor, user_id, verified: bool): - The inspection. """ - try: - query = """ - SELECT - inspection.id as inspection_id, - inspection.upload_date as upload_date, - inspection.updated_at as updated_at, - inspection.sample_id as sample_id, - inspection.picture_set_id as picture_set_id, - label_info.id as label_info_id, - label_info.product_name as product_name, - label_info.manufacturer_info_id as manufacturer_info_id, - company_info.id as company_info_id, - company_info.name as company_name - FROM - inspection - LEFT JOIN - label_information as label_info - ON - inspection.label_info_id = label_info.id - LEFT JOIN - organization_information as company_info - ON - label_info.company_info_id = company_info.id - WHERE - inspection.inspector_id = %s AND inspection.verified = %s - """ - cursor.execute( - query, - ( - user_id, - verified, - ), - ) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - -def get_all_user_inspection(cursor, user_id): + query = """ + SELECT + inspection.id as inspection_id, + inspection.upload_date as upload_date, + inspection.updated_at as updated_at, + inspection.sample_id as sample_id, + inspection.picture_set_id as picture_set_id, + label_info.id as label_info_id, + label_info.product_name as product_name, + label_info.manufacturer_info_id as manufacturer_info_id, + company_info.id as company_info_id, + company_info.name as company_name + FROM + inspection + LEFT JOIN + label_information as label_info + ON + inspection.label_info_id = label_info.id + LEFT JOIN + organization_information as company_info + ON + label_info.company_info_id = company_info.id + WHERE + inspection.inspector_id = %s AND inspection.verified = %s + """ + cursor.execute( + query, + ( + user_id, + verified, + ), + ) + return cursor.fetchall() + + +@handle_query_errors(InspectionRetrievalError) +def get_all_user_inspection(cursor: Cursor, user_id): """ This function gets all the inspection of a user from the database. @@ -332,30 +313,28 @@ def get_all_user_inspection(cursor, user_id): - The inspection. """ - try: - query = """ - SELECT - id, - verified, - upload_date, - updated_at, - label_info_id, - sample_id, - picture_set_id, - fertilizer_id - FROM - inspection - WHERE - inspector_id = %s - """ - cursor.execute(query, (user_id,)) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) + query = """ + SELECT + id, + verified, + upload_date, + updated_at, + label_info_id, + sample_id, + picture_set_id, + fertilizer_id + FROM + inspection + WHERE + inspector_id = %s + """ + cursor.execute(query, (user_id,)) + return cursor.fetchall() # Deprecated -def get_all_organization_inspection(cursor, org_id): +@handle_query_errors(InspectionRetrievalError) +def get_all_organization_inspection(cursor: Cursor, org_id): """ This function gets all the inspection of an organization from the database. @@ -367,29 +346,27 @@ def get_all_organization_inspection(cursor, org_id): - The inspection. """ - try: - query = """ - SELECT - id, - verified, - upload_date, - updated_at, - inspector_id, - label_info_id, - sample_id, - picture_set_id, - fertilizer_id - FROM - inspection - WHERE - company_id = %s OR manufacturer_id = %s - """ - cursor.execute(query, (org_id, org_id)) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore inspection unhandeled error" + e.__str__()) - - + query = """ + SELECT + id, + verified, + upload_date, + updated_at, + inspector_id, + label_info_id, + sample_id, + picture_set_id, + fertilizer_id + FROM + inspection + WHERE + company_id = %s OR manufacturer_id = %s + """ + cursor.execute(query, (org_id, org_id)) + return cursor.fetchall() + + +@handle_query_errors(InspectionUpdateError) def update_inspection( cursor: Cursor, inspection_id: str | UUID, @@ -411,27 +388,16 @@ def update_inspection( Raises: - InspectionUpdateError: Custom error for handling specific update issues. """ - try: - # Prepare and execute the SQL function call - query = SQL("SELECT update_inspection(%s, %s, %s)") - cursor.execute(query, (inspection_id, user_id, json.dumps(updated_data_dict))) - result = cursor.fetchone() - - if result is None: - raise InspectionUpdateError( - "Failed to update inspection. No data returned." - ) + # Prepare and execute the SQL function call + query = SQL("SELECT update_inspection(%s, %s, %s)") + cursor.execute(query, (inspection_id, user_id, json.dumps(updated_data_dict))) + if result := cursor.fetchone(): return result[0] - - except (Error, DatabaseError, OperationalError) as e: - raise InspectionUpdateError(f"Database error occurred: {str(e)}") from e - except (ValueError, TypeError) as e: - raise InspectionUpdateError(f"Invalid input: {str(e)}") from e - except Exception as e: - raise InspectionUpdateError(f"Unexpected error: {str(e)}") from e + raise InspectionUpdateError("Failed to update inspection. No data returned.") +@handle_query_errors(InspectionDeleteError) def delete_inspection( cursor: Cursor, inspection_id: str | UUID, @@ -451,25 +417,16 @@ def delete_inspection( Raises: - InspectionDeleteError: Custom error for handling specific delete issues. """ - try: - query = SQL("SELECT delete_inspection(%s, %s);") - cursor.execute(query, (inspection_id, user_id)) - result = cursor.fetchone() - - if result is None: - raise InspectionDeleteError( - "Failed to delete inspection. No data returned." - ) + query = SQL("SELECT delete_inspection(%s, %s);") + cursor.execute(query, (inspection_id, user_id)) + if result := cursor.fetchone(): return result[0] - - except (Error, DatabaseError, OperationalError) as e: - raise InspectionDeleteError(f"Database error occurred: {str(e)}") from e - except Exception as e: - raise InspectionDeleteError(f"Unexpected error: {str(e)}") from e + raise InspectionDeleteError("Failed to delete inspection. No data returned.") -def get_inspection_factual(cursor, inspection_id): +@handle_query_errors(InspectionQueryError) +def get_inspection_factual(cursor: Cursor, inspection_id): """ This function gets the inspection from the database. @@ -482,23 +439,20 @@ def get_inspection_factual(cursor, inspection_id): """ if not is_a_inspection_id(cursor, inspection_id): raise InspectionNotFoundError(f"Inspection with id {inspection_id} not found") - try: - query = """ - SELECT - inspection_id, - inspector_id, - label_info_id, - time_id, - sample_id, - company_id, - manufacturer_id, - picture_set_id - FROM - inspection_factual - WHERE - inspection_id = %s - """ - cursor.execute(query, (inspection_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore.db.inspection unhandeled error" + e.__str__()) + query = """ + SELECT + inspection_id, + inspector_id, + label_info_id, + time_id, + sample_id, + company_id, + manufacturer_id, + picture_set_id + FROM + inspection_factual + WHERE + inspection_id = %s + """ + cursor.execute(query, (inspection_id,)) + return cursor.fetchone() diff --git a/fertiscan/db/queries/label/__init__.py b/fertiscan/db/queries/label/__init__.py index 7b531218..90f1eaaf 100644 --- a/fertiscan/db/queries/label/__init__.py +++ b/fertiscan/db/queries/label/__init__.py @@ -2,11 +2,18 @@ This module represent the function for the table label_information """ +from psycopg import Cursor -class LabelInformationNotFoundError(Exception): - pass +from fertiscan.db.queries.errors import ( + LabelInformationCreationError, + LabelInformationNotFoundError, + LabelInformationQueryError, + LabelInformationRetrievalError, + handle_query_errors, +) +@handle_query_errors(LabelInformationCreationError) def new_label_information( cursor, name: str, @@ -42,30 +49,31 @@ def new_label_information( Returns: - str: The UUID of the label_information """ - try: - query = """ - SELECT new_label_information(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s); - """ - cursor.execute( - query, - ( - name, - lot_number, - npk, - registration_number, - n, - p, - k, - title_en, - title_fr, - is_minimal, - company_info_id, - manufacturer_info_id, - ), - ) - return cursor.fetchone()[0] - except Exception as e: - raise e + query = """ + SELECT new_label_information(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s); + """ + cursor.execute( + query, + ( + name, + lot_number, + npk, + registration_number, + n, + p, + k, + title_en, + title_fr, + is_minimal, + company_info_id, + manufacturer_info_id, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise LabelInformationCreationError( + "Failed to create label information. No data returned." + ) def new_label_information_complete( @@ -75,7 +83,8 @@ def new_label_information_complete( return None -def get_label_information(cursor, label_information_id): +@handle_query_errors(LabelInformationRetrievalError) +def get_label_information(cursor: Cursor, label_information_id: str) -> dict: """ This function get a label_information from the database. @@ -84,35 +93,36 @@ def get_label_information(cursor, label_information_id): - label_information_id (str): The UUID of the label_information. Returns: - - dict: The label_information - """ - try: - query = """ - SELECT - id, - product_name, - lot_number, - npk, - registration_number, - n, - p, - k, - guaranteed_title_en, - guaranteed_title_fr, - title_is_minimal, - company_info_id, - manufacturer_info_id - FROM - label_information - WHERE - id = %s - """ - cursor.execute(query, (label_information_id,)) - return cursor.fetchone() - except Exception as e: - raise e - + - dict: A dictionary containing the label information. + Raises: + - InspectionRetrievalError: Custom error for handling retrieval issues. + """ + query = """ + SELECT + id, + product_name, + lot_number, + npk, + registration_number, + n, + p, + k, + guaranteed_title_en, + guaranteed_title_fr, + title_is_minimal, + company_info_id, + manufacturer_info_id + FROM + label_information + WHERE + id = %s + """ + cursor.execute(query, (label_information_id,)) + return cursor.fetchone() + + +@handle_query_errors(LabelInformationRetrievalError) def get_label_information_json(cursor, label_info_id) -> dict: """ This function retrieves the label information from the database in json format. @@ -124,23 +134,19 @@ def get_label_information_json(cursor, label_info_id) -> dict: Returns: - dict: The label information in json format. """ - try: - query = """ - SELECT get_label_info_json(%s); - """ - cursor.execute(query, (str(label_info_id),)) - label_info = cursor.fetchone() - if label_info is None or label_info[0] is None: - raise LabelInformationNotFoundError( - "Error: could not get the label information: " + str(label_info_id) - ) - return label_info[0] - except LabelInformationNotFoundError as e: - raise e - except Exception as e: - raise e + query = """ + SELECT get_label_info_json(%s); + """ + cursor.execute(query, (str(label_info_id),)) + label_info = cursor.fetchone() + if label_info is None or label_info[0] is None: + raise LabelInformationNotFoundError( + "Error: could not get the label information: " + str(label_info_id) + ) + return label_info[0] +@handle_query_errors(LabelInformationQueryError) def get_label_dimension(cursor, label_id): """ This function get the label_dimension from the database. @@ -152,36 +158,33 @@ def get_label_dimension(cursor, label_id): Returns: - dict: The label_dimension """ - try: - query = """ - SELECT - "label_id", - "company_info_id", - "company_location_id", - "manufacturer_info_id", - "manufacturer_location_id", - "instructions_ids", - "cautions_ids", - "first_aid_ids", - "warranties_ids", - "specification_ids", - "ingredient_ids", - "micronutrient_ids", - "guaranteed_ids", - "weight_ids", - "volume_ids", - "density_ids" - FROM - label_dimension - WHERE - label_id = %s; - """ - cursor.execute(query, (label_id,)) - data = cursor.fetchone() - if data is None or data[0] is None: - raise LabelInformationNotFoundError( - "Error: could not get the label dimension for label: " + str(label_id) - ) - return data - except Exception as e: - raise e + query = """ + SELECT + "label_id", + "company_info_id", + "company_location_id", + "manufacturer_info_id", + "manufacturer_location_id", + "instructions_ids", + "cautions_ids", + "first_aid_ids", + "warranties_ids", + "specification_ids", + "ingredient_ids", + "micronutrient_ids", + "guaranteed_ids", + "weight_ids", + "volume_ids", + "density_ids" + FROM + label_dimension + WHERE + label_id = %s; + """ + cursor.execute(query, (label_id,)) + data = cursor.fetchone() + if data is None or data[0] is None: + raise LabelInformationNotFoundError( + "Error: could not get the label dimension for label: " + str(label_id) + ) + return data diff --git a/fertiscan/db/queries/metric/__init__.py b/fertiscan/db/queries/metric/__init__.py index ea734cd5..e951e0a6 100644 --- a/fertiscan/db/queries/metric/__init__.py +++ b/fertiscan/db/queries/metric/__init__.py @@ -3,23 +3,19 @@ """ +from psycopg import Cursor -class MetricCreationError(Exception): - pass - - -class MetricNotFoundError(Exception): - pass - - -class UnitCreationError(Exception): - pass - - -class UnitNotFoundError(Exception): - pass +from fertiscan.db.queries.errors import ( + MetricCreationError, + MetricQueryError, + MetricRetrievalError, + UnitCreationError, + UnitQueryError, + handle_query_errors, +) +@handle_query_errors(MetricQueryError) def is_a_metric(cursor, metric_id): """ This function checks if the metric is in the database. @@ -32,24 +28,24 @@ def is_a_metric(cursor, metric_id): - boolean: if the metric exists. """ - try: - query = """ - SELECT EXISTS( - SELECT - 1 - FROM - metric - WHERE - id = %s - ) - """ - cursor.execute(query, (metric_id,)) - return cursor.fetchone()[0] - except Exception: - return False - - -def new_metric(cursor, value, read_unit, label_id, metric_type: str, edited=False): + query = """ + SELECT EXISTS( + SELECT + 1 + FROM + metric + WHERE + id = %s + ) + """ + cursor.execute(query, (metric_id,)) + return cursor.fetchone()[0] + + +@handle_query_errors(MetricCreationError) +def new_metric( + cursor: Cursor, value, read_unit, label_id, metric_type: str, edited=False +): """ This function uploads a new metric to the database. @@ -62,33 +58,30 @@ def new_metric(cursor, value, read_unit, label_id, metric_type: str, edited=Fals Returns: - The UUID of the metric. """ - - try: - if metric_type.lower() not in ["density", "weight", "volume"]: - raise MetricCreationError( - f"Error: metric type:{metric_type} not valid. Metric type must be one of the following: 'density','weight','volume'" - ) - query = """ - SELECT new_metric_unit(%s, %s, %s, %s, %s); - """ - cursor.execute( - query, - ( - value, - read_unit, - label_id, - metric_type.lower(), - edited, - ), + if metric_type.lower() not in ["density", "weight", "volume"]: + raise MetricCreationError( + f"Error: metric type:{metric_type} not valid. Metric type must be one of the following: 'density','weight','volume'" ) - return cursor.fetchone()[0] - except MetricCreationError as e: - raise e - except Exception: - raise MetricCreationError("Error: metric not uploaded") - - -def get_metric(cursor, metric_id): + query = """ + SELECT new_metric_unit(%s, %s, %s, %s, %s); + """ + cursor.execute( + query, + ( + value, + read_unit, + label_id, + metric_type.lower(), + edited, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise MetricCreationError("Failed to create metric. No data returned.") + + +@handle_query_errors(MetricRetrievalError) +def get_metric(cursor: Cursor, metric_id): """ This function gets the metric from the database. @@ -100,25 +93,23 @@ def get_metric(cursor, metric_id): - The metric. """ - try: - query = """ - SELECT - value, - unit_id, - edited, - metric_type - FROM - metric - WHERE - id = %s - """ - cursor.execute(query, (metric_id,)) - return cursor.fetchone() - except Exception: - raise MetricNotFoundError("Error: metric not found") - - -def get_metric_by_label(cursor, label_id): + query = """ + SELECT + value, + unit_id, + edited, + metric_type + FROM + metric + WHERE + id = %s + """ + cursor.execute(query, (metric_id,)) + return cursor.fetchone() + + +@handle_query_errors(MetricRetrievalError) +def get_metric_by_label(cursor: Cursor, label_id): """ This function gets the metric from the database. @@ -130,28 +121,26 @@ def get_metric_by_label(cursor, label_id): - The metric. """ - try: - query = """ - SELECT - id, - value, - unit_id, - edited, - metric_type - FROM - metric - WHERE - label_id = %s - ORDER BY - metric_type - """ - cursor.execute(query, (label_id,)) - return cursor.fetchall() - except Exception: - raise MetricNotFoundError("Error: metric not found") - - -def get_metrics_json(cursor, label_id) -> dict: + query = """ + SELECT + id, + value, + unit_id, + edited, + metric_type + FROM + metric + WHERE + label_id = %s + ORDER BY + metric_type + """ + cursor.execute(query, (label_id,)) + return cursor.fetchall() + + +@handle_query_errors(MetricRetrievalError) +def get_metrics_json(cursor: Cursor, label_id) -> dict: """ This function gets the metric from the database and returns it in json format. @@ -162,24 +151,19 @@ def get_metrics_json(cursor, label_id) -> dict: Returns: - The metric in dict format. """ - try: - query = """ - SELECT get_metrics_json(%s); - """ - cursor.execute(query, (str(label_id),)) - metric = cursor.fetchone() - if metric is None: - raise MetricNotFoundError( - "Error: could not get the metric for label: " + str(label_id) - ) - return metric[0] - except MetricNotFoundError as e: - raise e - except Exception: - raise Exception("Error: could not get the metric for label: " + str(label_id)) - - -def get_full_metric(cursor, metric_id): + query = """ + SELECT get_metrics_json(%s); + """ + cursor.execute(query, (str(label_id),)) + if result := cursor.fetchone(): + return result[0] + raise MetricRetrievalError( + "Failed to retrieve metrics json. No data returned for: " + str(label_id) + ) + + +@handle_query_errors(MetricRetrievalError) +def get_full_metric(cursor: Cursor, metric_id): """ This function gets the metric from the database with the unit. @@ -191,32 +175,30 @@ def get_full_metric(cursor, metric_id): - The metric with the unit. """ - try: - query = """ - SELECT - metric.id, - metric.value, - unit.unit, - unit.to_si_unit, - metric.edited, - metric.metric_type, - CONCAT(CAST(metric.value AS CHAR), ' ', unit.unit) AS full_metric - FROM - metric - JOIN - unit - ON - metric.unit_id = unit.id - WHERE - metric.id = %s - """ - cursor.execute(query, (metric_id,)) - return cursor.fetchone() - except Exception: - raise MetricNotFoundError("Error: metric not found") - - -def new_unit(cursor, unit, to_si_unit): + query = """ + SELECT + metric.id, + metric.value, + unit.unit, + unit.to_si_unit, + metric.edited, + metric.metric_type, + CONCAT(CAST(metric.value AS CHAR), ' ', unit.unit) AS full_metric + FROM + metric + JOIN + unit + ON + metric.unit_id = unit.id + WHERE + metric.id = %s + """ + cursor.execute(query, (metric_id,)) + return cursor.fetchone() + + +@handle_query_errors(UnitCreationError) +def new_unit(cursor: Cursor, unit, to_si_unit): """ This function uploads a new unit to the database. @@ -229,30 +211,30 @@ def new_unit(cursor, unit, to_si_unit): - The UUID of the unit. """ - try: - query = """ - INSERT INTO - unit( - unit, - to_si_unit - ) - VALUES - (%s, %s) - RETURNING id - """ - cursor.execute( - query, - ( + query = """ + INSERT INTO + unit( unit, - to_si_unit, - ), - ) - return cursor.fetchone()[0] - except Exception: - raise UnitCreationError("Error: unit not uploaded") - - -def is_a_unit(cursor, unit): + to_si_unit + ) + VALUES + (%s, %s) + RETURNING id + """ + cursor.execute( + query, + ( + unit, + to_si_unit, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise UnitCreationError("Failed to create unit. No data returned.") + + +@handle_query_errors(UnitQueryError) +def is_a_unit(cursor: Cursor, unit): """ This function checks if the unit is in the database. @@ -264,24 +246,22 @@ def is_a_unit(cursor, unit): - boolean: if the unit exists. """ - try: - query = """ - SELECT EXISTS( - SELECT - 1 - FROM - unit - WHERE - unit = %s - ) - """ - cursor.execute(query, (unit,)) - return cursor.fetchone()[0] - except Exception: - return False - - -def get_unit_id(cursor, unit): + query = """ + SELECT EXISTS( + SELECT + 1 + FROM + unit + WHERE + unit = %s + ) + """ + cursor.execute(query, (unit,)) + return cursor.fetchone()[0] + + +@handle_query_errors(UnitQueryError) +def get_unit_id(cursor: Cursor, unit): """ This function gets the unit from the database. @@ -293,16 +273,15 @@ def get_unit_id(cursor, unit): - The UUID of the unit. """ - try: - query = """ - SELECT - id - FROM - unit - WHERE - unit = %s - """ - cursor.execute(query, (unit,)) - return cursor.fetchone()[0] - except Exception: - raise UnitNotFoundError("Error: unit not found") + query = """ + SELECT + id + FROM + unit + WHERE + unit = %s + """ + cursor.execute(query, (unit,)) + if result := cursor.fetchone(): + return result[0] + raise UnitQueryError("Failed to retrieve unit id. No data returned.") diff --git a/fertiscan/db/queries/nutrients/__init__.py b/fertiscan/db/queries/nutrients/__init__.py index e3a16188..723d6b84 100644 --- a/fertiscan/db/queries/nutrients/__init__.py +++ b/fertiscan/db/queries/nutrients/__init__.py @@ -3,32 +3,22 @@ """ - -class ElementCreationError(Exception): - pass - - -class ElementNotFoundError(Exception): - pass - - -class MicronutrientCreationError(Exception): - pass - - -class MicronutrientNotFoundError(Exception): - pass - - -class GuaranteedCreationError(Exception): - pass - - -class GuaranteedNotFoundError(Exception): - pass - - -def new_element(cursor, number, name_fr, name_en, symbol): +from psycopg import Cursor + +from fertiscan.db.queries.errors import ( + ElementCompoundCreationError, + ElementCompoundNotFoundError, + ElementCompoundQueryError, + GuaranteedAnalysisCreationError, + GuaranteedAnalysisRetrievalError, + MicronutrientCreationError, + MicronutrientRetrievalError, + handle_query_errors, +) + + +@handle_query_errors(ElementCompoundCreationError) +def new_element(cursor: Cursor, number, name_fr, name_en, symbol): """ This function add a new element in the database. @@ -43,19 +33,19 @@ def new_element(cursor, number, name_fr, name_en, symbol): - str: The UUID of the element. """ - try: - query = """ - INSERT INTO element_compound (number,name_fr,name_en,symbol) - VALUES (%s,%s,%s,%s) - RETURNING id - """ - cursor.execute(query, (number, name_fr, name_en, symbol)) - return cursor.fetchone()[0] - except Exception: - raise ElementCreationError + query = """ + INSERT INTO element_compound (number,name_fr,name_en,symbol) + VALUES (%s,%s,%s,%s) + RETURNING id + """ + cursor.execute(query, (number, name_fr, name_en, symbol)) + if result := cursor.fetchone(): + return result[0] + raise ElementCompoundCreationError("Failed to create element. No data returned.") -def get_element_id_full_search(cursor, name): +@handle_query_errors(ElementCompoundQueryError) +def get_element_id_full_search(cursor: Cursor, name): """ This function get the element in the database. @@ -67,19 +57,21 @@ def get_element_id_full_search(cursor, name): - str: The UUID of the element. """ - try: - query = """ - SELECT id - FROM element_compound - WHERE name_fr ILIKE %s OR name_en ILIKE %s OR symbol = %s - """ - cursor.execute(query, (name, name, name)) - return cursor.fetchone()[0] - except Exception: - raise ElementNotFoundError - - -def get_element_id_name(cursor, name): + query = """ + SELECT id + FROM element_compound + WHERE name_fr ILIKE %s OR name_en ILIKE %s OR symbol = %s + """ + cursor.execute(query, (name, name, name)) + if result := cursor.fetchone(): + return result[0] + raise ElementCompoundNotFoundError( + "Failed to retrieve element id. No data returned." + ) + + +@handle_query_errors(ElementCompoundQueryError) +def get_element_id_name(cursor: Cursor, name): """ This function get the element in the database. @@ -91,23 +83,25 @@ def get_element_id_name(cursor, name): - str: The UUID of the element. """ - try: - query = """ - SELECT - id - FROM - element_compound - WHERE - name_fr = %s OR - name_en = %s - """ - cursor.execute(query, (name, name)) - return cursor.fetchone()[0] - except Exception: - raise ElementNotFoundError - - -def get_element_id_symbol(cursor, symbol): + query = """ + SELECT + id + FROM + element_compound + WHERE + name_fr = %s OR + name_en = %s + """ + cursor.execute(query, (name, name)) + if result := cursor.fetchone(): + return result[0] + raise ElementCompoundNotFoundError( + "Failed to retrieve element id. No data returned." + ) + + +@handle_query_errors(ElementCompoundQueryError) +def get_element_id_symbol(cursor: Cursor, symbol): """ This function get the element in the database. @@ -119,23 +113,25 @@ def get_element_id_symbol(cursor, symbol): - str: The UUID of the element. """ - try: - query = """ - SELECT - id - FROM - element_compound - WHERE - symbol ILIKE %s - """ - cursor.execute(query, (symbol,)) - return cursor.fetchone()[0] - except Exception: - raise ElementNotFoundError - - + query = """ + SELECT + id + FROM + element_compound + WHERE + symbol ILIKE %s + """ + cursor.execute(query, (symbol,)) + if result := cursor.fetchone(): + return result[0] + raise ElementCompoundNotFoundError( + "Failed to retrieve element id. No data returned." + ) + + +@handle_query_errors(MicronutrientCreationError) def new_micronutrient( - cursor, + cursor: Cursor, read_name: str, value: float, unit: str, @@ -159,21 +155,19 @@ def new_micronutrient( - str: The UUID of the micronutrient. """ - try: - if language.lower() not in ["fr", "en"]: - raise MicronutrientCreationError("Language not supported") - query = """ - SELECT new_micronutrient(%s, %s, %s, %s, %s,%s,%s); - """ - cursor.execute( - query, (read_name, value, unit, label_id, language, edited, element_id) - ) - return cursor.fetchone()[0] - except Exception: - raise MicronutrientCreationError - - -def get_micronutrient(cursor, micronutrient_id): + if language.lower() not in ["fr", "en"]: + raise MicronutrientCreationError("Language not supported") + query = """ + SELECT new_micronutrient(%s, %s, %s, %s, %s,%s,%s); + """ + cursor.execute( + query, (read_name, value, unit, label_id, language, edited, element_id) + ) + return cursor.fetchone()[0] + + +@handle_query_errors(MicronutrientRetrievalError) +def get_micronutrient(cursor: Cursor, micronutrient_id): """ This function get the micronutrient in the database. @@ -185,28 +179,26 @@ def get_micronutrient(cursor, micronutrient_id): - str: The UUID of the micronutrient. """ - try: - query = """ - SELECT - read_name, - value, - unit, - element_id, - edited, - language, - CONCAT(CAST(read_name AS TEXT),' ',value,' ', unit) AS reading - FROM - micronutrient - WHERE - id = %s - """ - cursor.execute(query, (micronutrient_id,)) - return cursor.fetchone() - except Exception: - raise MicronutrientNotFoundError - - -def get_micronutrient_json(cursor, label_id) -> dict: + query = """ + SELECT + read_name, + value, + unit, + element_id, + edited, + language, + CONCAT(CAST(read_name AS TEXT),' ',value,' ', unit) AS reading + FROM + micronutrient + WHERE + id = %s + """ + cursor.execute(query, (micronutrient_id,)) + return cursor.fetchone() + + +@handle_query_errors(MicronutrientRetrievalError) +def get_micronutrient_json(cursor: Cursor, label_id) -> dict: """ This function get the micronutrient in the database for a specific label. @@ -217,24 +209,19 @@ def get_micronutrient_json(cursor, label_id) -> dict: Returns: - micronutrient (dict): The micronutrient. """ - try: - query = """ - SELECT get_micronutrient_json(%s); - """ - cursor.execute(query, (label_id,)) - data = cursor.fetchone() - if data is None: - raise MicronutrientNotFoundError( - "Error: could not get the micronutrient for label: " + str(label_id) - ) - return data[0] - except MicronutrientNotFoundError as e: - raise e - except Exception: - raise MicronutrientNotFoundError - - -def get_full_micronutrient(cursor, micronutrient_id): + query = """ + SELECT get_micronutrient_json(%s); + """ + cursor.execute(query, (label_id,)) + if result := cursor.fetchone(): + return result[0] + raise MicronutrientRetrievalError( + "Failed to retrieve micronutrient json. No data returned for: " + str(label_id) + ) + + +@handle_query_errors(MicronutrientRetrievalError) +def get_full_micronutrient(cursor: Cursor, micronutrient_id): """ This function get the micronutrient in the database with the element. @@ -244,32 +231,30 @@ def get_full_micronutrient(cursor, micronutrient_id): """ - try: - query = """ - SELECT - m.read_name, - m.value, - m.unit, - ec.name_fr, - ec.name_en, - ec.symbol, - m.edited, - m.language, - CONCAT(CAST(m.read_name AS TEXT),' ',m.value,' ', m.unit) AS reading - FROM - micronutrient m - LEFT JOIN - element_compound ec ON m.element_id = ec.id - WHERE - m.id = %s - """ - cursor.execute(query, (micronutrient_id,)) - return cursor.fetchone() - except Exception: - raise MicronutrientNotFoundError - - -def get_all_micronutrients(cursor, label_id): + query = """ + SELECT + m.read_name, + m.value, + m.unit, + ec.name_fr, + ec.name_en, + ec.symbol, + m.edited, + m.language, + CONCAT(CAST(m.read_name AS TEXT),' ',m.value,' ', m.unit) AS reading + FROM + micronutrient m + LEFT JOIN + element_compound ec ON m.element_id = ec.id + WHERE + m.id = %s + """ + cursor.execute(query, (micronutrient_id,)) + return cursor.fetchone() + + +@handle_query_errors(MicronutrientRetrievalError) +def get_all_micronutrients(cursor: Cursor, label_id): """ This function get all the micronutrients with the right label_id in the database. @@ -281,34 +266,32 @@ def get_all_micronutrients(cursor, label_id): - str: The UUID of the micronutrient. """ - try: - query = """ - SELECT - m.id, - m.read_name, - m.value, - m.unit, - ec.name_fr, - ec.name_en, - ec.symbol, - m.edited, - m.language, - CONCAT(CAST(m.read_name AS TEXT),' ',m.value,' ', m.unit) AS reading - FROM - micronutrient m - LEFT JOIN - element_compound ec ON m.element_id = ec.id - WHERE - m.label_id = %s - """ - cursor.execute(query, (label_id,)) - return cursor.fetchall() - except Exception: - raise MicronutrientNotFoundError - - + query = """ + SELECT + m.id, + m.read_name, + m.value, + m.unit, + ec.name_fr, + ec.name_en, + ec.symbol, + m.edited, + m.language, + CONCAT(CAST(m.read_name AS TEXT),' ',m.value,' ', m.unit) AS reading + FROM + micronutrient m + LEFT JOIN + element_compound ec ON m.element_id = ec.id + WHERE + m.label_id = %s + """ + cursor.execute(query, (label_id,)) + return cursor.fetchall() + + +@handle_query_errors(GuaranteedAnalysisCreationError) def new_guaranteed_analysis( - cursor, + cursor: Cursor, read_name, value, unit, @@ -332,27 +315,29 @@ def new_guaranteed_analysis( - str: The UUID of the guaranteed. """ - try: - if language.lower() not in ["fr", "en"]: - raise GuaranteedCreationError("Language not supported") - if ( - (read_name is None or read_name == "") - and (value is None or value == "") - and (unit is None or unit == "") - ): - raise GuaranteedCreationError("Read name and value cannot be empty") - query = """ - SELECT new_guaranteed_analysis(%s, %s, %s, %s, %s, %s, %s); - """ - cursor.execute( - query, (read_name, value, unit, label_id, language, edited, element_id) - ) - return cursor.fetchone()[0] - except Exception: - raise GuaranteedCreationError - - -def get_guaranteed(cursor, guaranteed_id): + if language.lower() not in ["fr", "en"]: + raise GuaranteedAnalysisCreationError("Language not supported") + if ( + (read_name is None or read_name == "") + and (value is None or value == "") + and (unit is None or unit == "") + ): + raise GuaranteedAnalysisCreationError("Read name and value cannot be empty") + query = """ + SELECT new_guaranteed_analysis(%s, %s, %s, %s, %s, %s, %s); + """ + cursor.execute( + query, (read_name, value, unit, label_id, language, edited, element_id) + ) + if result := cursor.fetchone(): + return result[0] + raise GuaranteedAnalysisCreationError( + "Failed to create guaranteed analysis. No data returned." + ) + + +@handle_query_errors(GuaranteedAnalysisRetrievalError) +def get_guaranteed(cursor: Cursor, guaranteed_id): """ This function get the guaranteed in the database. @@ -364,29 +349,27 @@ def get_guaranteed(cursor, guaranteed_id): - str: The UUID of the guaranteed. """ - try: - query = """ - SELECT - read_name, - value, - unit, - element_id, - label_id, - edited, - language, - CONCAT(CAST(read_name AS TEXT),' ',value,' ', unit) AS reading - FROM - guaranteed - WHERE - id = %s - """ - cursor.execute(query, (guaranteed_id,)) - return cursor.fetchone() - except Exception: - raise GuaranteedNotFoundError - - -def get_guaranteed_analysis_json(cursor, label_id) -> dict: + query = """ + SELECT + read_name, + value, + unit, + element_id, + label_id, + edited, + language, + CONCAT(CAST(read_name AS TEXT),' ',value,' ', unit) AS reading + FROM + guaranteed + WHERE + id = %s + """ + cursor.execute(query, (guaranteed_id,)) + return cursor.fetchone() + + +@handle_query_errors(GuaranteedAnalysisRetrievalError) +def get_guaranteed_analysis_json(cursor: Cursor, label_id) -> dict: """ This function get the guaranteed in the database for a specific label. @@ -397,24 +380,22 @@ def get_guaranteed_analysis_json(cursor, label_id) -> dict: Returns: - guaranteed (dict): The guaranteed. """ - try: - query = """ - SELECT get_guaranteed_analysis_json(%s); - """ - cursor.execute(query, (label_id,)) - data = cursor.fetchone()[0] - if data is None: - data = {"title": None, "is_minimal": False, "en": [], "fr": []} + query = """ + SELECT get_guaranteed_analysis_json(%s); + """ + cursor.execute(query, (label_id,)) + if result := cursor.fetchone(): + if (data := result[0]) is None: + return {"title": None, "is_minimal": False, "en": [], "fr": []} return data - except GuaranteedNotFoundError as e: - raise e - except Exception: - raise GuaranteedNotFoundError( - "Error: could not get the guaranteed for label: " + str(label_id) - ) + raise GuaranteedAnalysisRetrievalError( + "Failed to retrieve guaranteed analysis json. No data returned for: " + + str(label_id) + ) -def get_full_guaranteed(cursor, guaranteed): +@handle_query_errors(GuaranteedAnalysisRetrievalError) +def get_full_guaranteed(cursor: Cursor, guaranteed): """ This function get the guaranteed in the database with the element. @@ -426,31 +407,29 @@ def get_full_guaranteed(cursor, guaranteed): - str: The UUID of the guaranteed. """ - try: - query = """ - SELECT - g.read_name, - g.value, - g.unit, - ec.name_fr, - ec.name_en, - ec.symbol, - g.edited, - CONCAT(CAST(g.read_name AS TEXT),' ',g.value,' ', g.unit) AS reading - FROM - guaranteed g - JOIN - element_compound ec ON g.element_id = ec.id - WHERE - g.id = %s - """ - cursor.execute(query, (guaranteed,)) - return cursor.fetchone() - except Exception: - raise GuaranteedNotFoundError - - -def get_all_guaranteeds(cursor, label_id): + query = """ + SELECT + g.read_name, + g.value, + g.unit, + ec.name_fr, + ec.name_en, + ec.symbol, + g.edited, + CONCAT(CAST(g.read_name AS TEXT),' ',g.value,' ', g.unit) AS reading + FROM + guaranteed g + JOIN + element_compound ec ON g.element_id = ec.id + WHERE + g.id = %s + """ + cursor.execute(query, (guaranteed,)) + return cursor.fetchone() + + +@handle_query_errors(GuaranteedAnalysisRetrievalError) +def get_all_guaranteeds(cursor: Cursor, label_id): """ This function get all the guaranteed in the database. @@ -462,26 +441,23 @@ def get_all_guaranteeds(cursor, label_id): - str: The UUID of the guaranteed. """ - try: - query = """ - SELECT - g.id, - g.read_name, - g.value, - g.unit, - ec.name_fr, - ec.name_en, - ec.symbol, - g.edited, - CONCAT(CAST(g.read_name AS TEXT),' ',g.value,' ', g.unit) AS reading - FROM - guaranteed g - JOIN - element_compound ec ON g.element_id = ec.id - WHERE - g.label_id = %s - """ - cursor.execute(query, (label_id,)) - return cursor.fetchall() - except Exception: - raise GuaranteedNotFoundError + query = """ + SELECT + g.id, + g.read_name, + g.value, + g.unit, + ec.name_fr, + ec.name_en, + ec.symbol, + g.edited, + CONCAT(CAST(g.read_name AS TEXT),' ',g.value,' ', g.unit) AS reading + FROM + guaranteed g + JOIN + element_compound ec ON g.element_id = ec.id + WHERE + g.label_id = %s + """ + cursor.execute(query, (label_id,)) + return cursor.fetchall() diff --git a/fertiscan/db/queries/organization/__init__.py b/fertiscan/db/queries/organization/__init__.py index 7bf5e27b..0746a541 100644 --- a/fertiscan/db/queries/organization/__init__.py +++ b/fertiscan/db/queries/organization/__init__.py @@ -1,47 +1,33 @@ """ This module represent the function for the table organization and its children tables: [location, region, province] - - """ - -class OrganizationCreationError(Exception): - pass - - -class OrganizationNotFoundError(Exception): - pass - - -class OrganizationUpdateError(Exception): - pass - - -class LocationCreationError(Exception): - pass - - -class LocationNotFoundError(Exception): - pass - - -class RegionCreationError(Exception): - pass - - -class RegionNotFoundError(Exception): - pass - - -class ProvinceCreationError(Exception): - pass - - -class ProvinceNotFoundError(Exception): - pass - - -def new_organization(cursor, information_id, location_id=None): +from psycopg import Cursor + +from fertiscan.db.queries.errors import ( + LocationCreationError, + LocationNotFoundError, + LocationRetrievalError, + OrganizationCreationError, + OrganizationInformationCreationError, + OrganizationInformationNotFoundError, + OrganizationInformationRetrievalError, + OrganizationInformationUpdateError, + OrganizationNotFoundError, + OrganizationRetrievalError, + OrganizationUpdateError, + ProvinceCreationError, + ProvinceNotFoundError, + ProvinceRetrievalError, + RegionCreationError, + RegionNotFoundError, + RegionRetrievalError, + handle_query_errors, +) + + +@handle_query_errors(OrganizationInformationCreationError) +def new_organization(cursor: Cursor, information_id, location_id=None): """ This function create a new organization in the database. @@ -54,45 +40,37 @@ def new_organization(cursor, information_id, location_id=None): Returns: - str: The UUID of the organization """ - try: - if location_id is None: - query = """ - SELECT - location_id - FROM - organization_information - WHERE - id = %s - """ - cursor.execute(query, (information_id,)) - location_id = cursor.fetchone()[0] + if location_id is None: query = """ - INSERT INTO - organization ( - information_id, - main_location_id - ) - VALUES - (%s, %s) - RETURNING - id + SELECT + location_id + FROM + organization_information + WHERE + id = %s """ - cursor.execute( - query, - ( + cursor.execute(query, (information_id,)) + location_id = cursor.fetchone()[0] + query = """ + INSERT INTO + organization ( information_id, - location_id, - ), - ) - return cursor.fetchone()[0] - except Exception as e: - raise OrganizationCreationError( - "Datastore organization unhandeled error" + e.__str__() - ) - - + main_location_id + ) + VALUES + (%s, %s) + RETURNING + id + """ + cursor.execute(query, (information_id, location_id)) + if result := cursor.fetchone(): + return result[0] + raise OrganizationCreationError("Failed to create Organization. No data returned.") + + +@handle_query_errors(OrganizationInformationCreationError) def new_organization_info_located( - cursor, address: str, name: str, website: str, phone_number: str + cursor: Cursor, address: str, name: str, website: str, phone_number: str ): """ This function create a new organization information in the database. @@ -106,27 +84,27 @@ def new_organization_info_located( Returns: - str: The UUID of the organization information """ - try: - query = """ - SELECT new_organization_info_located(%s, %s, %s, %s); - """ - cursor.execute( - query, - ( - name, - address, - website, - phone_number, - ), - ) - return cursor.fetchone()[0] - except Exception as e: - raise OrganizationCreationError( - "Datastore organization unhandeled error: " + e.__str__() - ) - - -def new_organization_info(cursor, name, website, phone_number, location_id=None): + query = """ + SELECT new_organization_info_located(%s, %s, %s, %s); + """ + cursor.execute( + query, + ( + name, + address, + website, + phone_number, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise OrganizationCreationError("Failed to create Organization. No data returned.") + + +@handle_query_errors(OrganizationInformationCreationError) +def new_organization_info( + cursor: Cursor, name, website, phone_number, location_id=None +): """ This function create a new organization information in the database. @@ -139,32 +117,30 @@ def new_organization_info(cursor, name, website, phone_number, location_id=None) Returns: - str: The UUID of the organization information """ - try: - query = """ - INSERT INTO - organization_information ( - name, - website, - phone_number, - location_id - ) - VALUES - (%s, %s, %s, %s) - RETURNING - id - """ - cursor.execute( - query, - (name, website, phone_number, location_id), - ) - return cursor.fetchone()[0] - except Exception as e: - raise OrganizationCreationError( - "Datastore organization unhandeled error" + e.__str__() - ) - - -def get_organization_info(cursor, information_id): + query = """ + INSERT INTO + organization_information ( + name, + website, + phone_number, + location_id + ) + VALUES + (%s, %s, %s, %s) + RETURNING + id + """ + cursor.execute( + query, + (name, website, phone_number, location_id), + ) + if result := cursor.fetchone(): + return result[0] + raise OrganizationCreationError("Failed to create Organization. No data returned.") + + +@handle_query_errors(OrganizationInformationRetrievalError) +def get_organization_info(cursor: Cursor, information_id): """ This function get a organization information from the database. @@ -175,32 +151,27 @@ def get_organization_info(cursor, information_id): Returns: - dict: The organization information """ - try: - query = """ - SELECT - name, - website, - phone_number, - location_id - FROM - organization_information - WHERE - id = %s - """ - cursor.execute(query, (information_id,)) - res = cursor.fetchone() - if res is None: - raise OrganizationNotFoundError - return res - except OrganizationNotFoundError: - raise OrganizationNotFoundError( - "organization information not found with information_id: " + information_id - ) - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def get_organizations_info_json(cursor, label_id) -> dict: + query = """ + SELECT + name, + website, + phone_number, + location_id + FROM + organization_information + WHERE + id = %s + """ + cursor.execute(query, (information_id,)) + if result := cursor.fetchone(): + return result + raise OrganizationInformationNotFoundError( + "Organization information not found with information_id: " + information_id + ) + + +@handle_query_errors(OrganizationInformationRetrievalError) +def get_organizations_info_json(cursor: Cursor, label_id) -> dict: """ This function get a organization information from the database. @@ -211,33 +182,26 @@ def get_organizations_info_json(cursor, label_id) -> dict: Returns: - dict: The organization information """ - try: + query = """ + SELECT get_organizations_information_json(%s); + """ + cursor.execute(query, (str(label_id),)) - query = """ - SELECT get_organizations_information_json(%s); - """ - cursor.execute(query, (str(label_id),)) - - res = cursor.fetchone() - if res is None or res[0] is None: - # raise OrganizationNotFoundError - # There might not be any organization information - return {} - if len(res[0]) == 2: - return {**res[0][0], **res[0][1]} - elif len(res[0]) == 1: - return res[0][0] - else: - return {} - except OrganizationNotFoundError: - raise OrganizationNotFoundError( - "organization information not found for the label_info_id " + str(label_id) - ) - except Exception as e: - raise Exception("Datastore organization unhandeled error: " + e.__str__()) - - -def update_organization(cursor, organization_id, information_id, location_id): + res = cursor.fetchone() + if res is None or res[0] is None: + # raise OrganizationNotFoundError + # There might not be any organization information + return {} + if len(res[0]) == 2: + return {**res[0][0], **res[0][1]} + elif len(res[0]) == 1: + return res[0][0] + else: + return {} + + +@handle_query_errors(OrganizationUpdateError) +def update_organization(cursor: Cursor, organization_id, information_id, location_id): """ This function update a organization in the database. @@ -252,32 +216,30 @@ def update_organization(cursor, organization_id, information_id, location_id): Returns: - str: The UUID of the organization """ - try: - query = """ - UPDATE - organization - SET - information_id = COALESCE(%s,information_id), - main_location_id = COALESCE(%s,main_location_id) - WHERE - id = %s - """ - cursor.execute( - query, - ( - information_id, - location_id, - organization_id, - ), - ) - return organization_id - except Exception as e: - raise OrganizationUpdateError( - "Datastore organization unhandeled error" + e.__str__() - ) - - -def update_organization_info(cursor, information_id, name, website, phone_number): + query = """ + UPDATE + organization + SET + information_id = COALESCE(%s,information_id), + main_location_id = COALESCE(%s,main_location_id) + WHERE + id = %s + """ + cursor.execute( + query, + ( + information_id, + location_id, + organization_id, + ), + ) + return organization_id + + +@handle_query_errors(OrganizationInformationUpdateError) +def update_organization_info( + cursor: Cursor, information_id, name, website, phone_number +): """ This function update a organization information in the database. @@ -291,34 +253,30 @@ def update_organization_info(cursor, information_id, name, website, phone_number Returns: - str: The UUID of the organization information """ - try: - query = """ - UPDATE - organization_information - SET - name = COALESCE(%s,name), - website = COALESCE(%s,website), - phone_number = COALESCE(%s,phone_number) - WHERE - id = %s - """ - cursor.execute( - query, - ( - name, - website, - phone_number, - information_id, - ), - ) - return information_id - except Exception as e: - raise OrganizationUpdateError( - "Datastore organization unhandeled error" + e.__str__() - ) - - -def get_organization(cursor, organization_id): + query = """ + UPDATE + organization_information + SET + name = COALESCE(%s,name), + website = COALESCE(%s,website), + phone_number = COALESCE(%s,phone_number) + WHERE + id = %s + """ + cursor.execute( + query, + ( + name, + website, + phone_number, + information_id, + ), + ) + return information_id + + +@handle_query_errors(OrganizationRetrievalError) +def get_organization(cursor: Cursor, organization_id): """ This function get a organization from the database. @@ -329,30 +287,25 @@ def get_organization(cursor, organization_id): Returns: - dict: The organization """ - try: - query = """ - SELECT - information_id, - main_location_id - FROM - organization - WHERE - id = %s - """ - cursor.execute(query, (organization_id,)) - res = cursor.fetchone() - if res is None: - raise OrganizationNotFoundError - return res - except OrganizationNotFoundError: - raise OrganizationNotFoundError( - "organization not found with organization_id: " + organization_id - ) - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def get_full_organization(cursor, org_id): + query = """ + SELECT + information_id, + main_location_id + FROM + organization + WHERE + id = %s + """ + cursor.execute(query, (organization_id,)) + if result := cursor.fetchone(): + return result + raise OrganizationNotFoundError( + "Organization not found with organization_id: " + organization_id + ) + + +@handle_query_errors(OrganizationRetrievalError) +def get_full_organization(cursor: Cursor, org_id): """ This function get the full organization details from the database. This includes the location, region and province info of the organization. @@ -364,48 +317,46 @@ def get_full_organization(cursor, org_id): Returns: - dict: The organization """ - try: - query = """ - SELECT - organization.id, - information.name, - information.website, - information.phone_number, - location.id, - location.name, - location.address, - region.id, - region.name, - province.id, - province.name - FROM - organization - LEFT JOIN - organization_information as information - ON - organization.information_id = information.id - LEFT JOIN - location - ON - organization.main_location_id = location.id - LEFT JOIN - region - ON - location.region_id = region.id - LEFT JOIN - province - ON - region.province_id = province.id - WHERE - organization.id = %s - """ - cursor.execute(query, (org_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def new_location(cursor, name, address, region_id, org_id=None): + query = """ + SELECT + organization.id, + information.name, + information.website, + information.phone_number, + location.id, + location.name, + location.address, + region.id, + region.name, + province.id, + province.name + FROM + organization + LEFT JOIN + organization_information as information + ON + organization.information_id = information.id + LEFT JOIN + location + ON + organization.main_location_id = location.id + LEFT JOIN + region + ON + location.region_id = region.id + LEFT JOIN + province + ON + region.province_id = province.id + WHERE + organization.id = %s + """ + cursor.execute(query, (org_id,)) + return cursor.fetchone() + + +@handle_query_errors(LocationCreationError) +def new_location(cursor: Cursor, name, address, region_id, org_id=None): """ This function create a new location in the database. @@ -419,35 +370,35 @@ def new_location(cursor, name, address, region_id, org_id=None): Returns: - str: The UUID of the location """ - try: - query = """ - INSERT INTO - location ( - name, - address, - region_id, - owner_id - ) - VALUES - (%s, %s, %s, %s) - RETURNING - id - """ - cursor.execute( - query, - ( - name, - address, + query = """ + INSERT INTO + location ( + name, + address, region_id, - org_id, - ), - ) - return cursor.fetchone()[0] - except Exception as e: - raise LocationCreationError("Datastore location unhandeled error" + e.__str__()) - - -def get_location(cursor, location_id): + owner_id + ) + VALUES + (%s, %s, %s, %s) + RETURNING + id + """ + cursor.execute( + query, + ( + name, + address, + region_id, + org_id, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise LocationCreationError("Failed to create Location. No data returned.") + + +@handle_query_errors(LocationRetrievalError) +def get_location(cursor: Cursor, location_id): """ This function get a location from the database. @@ -458,32 +409,25 @@ def get_location(cursor, location_id): Returns: - dict: The location """ - try: - query = """ - SELECT - name, - address, - region_id, - owner_id - FROM - location - WHERE - id = %s - """ - cursor.execute(query, (location_id,)) - res = cursor.fetchone() - if res is None: - raise LocationNotFoundError - return res - except LocationNotFoundError: - raise LocationNotFoundError( - "location not found with location_id: " + location_id - ) - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def get_full_location(cursor, location_id): + query = """ + SELECT + name, + address, + region_id, + owner_id + FROM + location + WHERE + id = %s + """ + cursor.execute(query, (location_id,)) + if result := cursor.fetchone(): + return result + raise LocationNotFoundError("Location not found with location_id: " + location_id) + + +@handle_query_errors(LocationRetrievalError) +def get_full_location(cursor: Cursor, location_id): """ This function get the full location details from the database. This includes the region and province info of the location. @@ -495,34 +439,32 @@ def get_full_location(cursor, location_id): Returns: - dict: The location """ - try: - query = """ - SELECT - location.id, - location.name, - location.address, - region.name, - province.name - FROM - location - LEFT JOIN - region - ON - location.region_id = region.id - LEFT JOIN - province - ON - region.province_id = province.id - WHERE - location.id = %s - """ - cursor.execute(query, (location_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def get_location_by_region(cursor, region_id): + query = """ + SELECT + location.id, + location.name, + location.address, + region.name, + province.name + FROM + location + LEFT JOIN + region + ON + location.region_id = region.id + LEFT JOIN + province + ON + region.province_id = province.id + WHERE + location.id = %s + """ + cursor.execute(query, (location_id,)) + return cursor.fetchone() + + +@handle_query_errors(LocationRetrievalError) +def get_location_by_region(cursor: Cursor, region_id): """ This function get a location from the database. @@ -533,24 +475,22 @@ def get_location_by_region(cursor, region_id): Returns: - dict: The location """ - try: - query = """ - SELECT - id, - name, - address - FROM - location - WHERE - region_id = %s - """ - cursor.execute(query, (region_id,)) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) + query = """ + SELECT + id, + name, + address + FROM + location + WHERE + region_id = %s + """ + cursor.execute(query, (region_id,)) + return cursor.fetchall() -def get_location_by_organization(cursor, org_id): +@handle_query_errors(LocationRetrievalError) +def get_location_by_organization(cursor: Cursor, org_id): """ This function get a location from the database. @@ -561,25 +501,23 @@ def get_location_by_organization(cursor, org_id): Returns: - dict: The location """ - try: - query = """ - SELECT - id, - name, - address, - region_id - FROM - location - WHERE - owner_id = %s - """ - cursor.execute(query, (org_id,)) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) + query = """ + SELECT + id, + name, + address, + region_id + FROM + location + WHERE + owner_id = %s + """ + cursor.execute(query, (org_id,)) + return cursor.fetchall() -def new_region(cursor, name, province_id): +@handle_query_errors(RegionCreationError) +def new_region(cursor: Cursor, name, province_id): """ This function create a new region in the database. @@ -591,31 +529,31 @@ def new_region(cursor, name, province_id): Returns: - str: The UUID of the region """ - try: - query = """ - INSERT INTO - region ( - province_id, - name - ) - VALUES - (%s, %s) - RETURNING - id - """ - cursor.execute( - query, - ( - province_id, - name, - ), - ) - return cursor.fetchone()[0] - except Exception as e: - raise RegionCreationError("Datastore region unhandeled error" + e.__str__()) - - -def get_region(cursor, region_id): + query = """ + INSERT INTO + region ( + province_id, + name + ) + VALUES + (%s, %s) + RETURNING + id + """ + cursor.execute( + query, + ( + province_id, + name, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise RegionCreationError("Failed to create Region. No data returned.") + + +@handle_query_errors(RegionRetrievalError) +def get_region(cursor: Cursor, region_id): """ This function get a region from the database. @@ -626,29 +564,23 @@ def get_region(cursor, region_id): Returns: - dict: The region """ - try: - query = """ - SELECT - name, - province_id - FROM - region - WHERE - id = %s - """ - cursor.execute(query, (region_id,)) - res = cursor.fetchone() - if res is None: - raise RegionNotFoundError - return res - except RegionNotFoundError: - raise RegionNotFoundError("region not found with region_id: " + str(region_id)) - except Exception as e: - print(e) - raise Exception("Datastore organization unhandeled error " + e.__str__()) + query = """ + SELECT + name, + province_id + FROM + region + WHERE + id = %s + """ + cursor.execute(query, (region_id,)) + if result := cursor.fetchone(): + return result + raise RegionNotFoundError("region not found with region_id: " + str(region_id)) -def get_full_region(cursor, region_id): +@handle_query_errors(RegionRetrievalError) +def get_full_region(cursor: Cursor, region_id): """ This function get the full region details from the database. This includes the province info of the region. @@ -660,28 +592,26 @@ def get_full_region(cursor, region_id): Returns: - dict: The region """ - try: - query = """ - SELECT - region.id, - region.name, - province.name - FROM - region - LEFT JOIN - province - ON - region.province_id = province.id - WHERE - region.id = %s - """ - cursor.execute(query, (region_id,)) - return cursor.fetchone() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def get_region_by_province(cursor, province_id): + query = """ + SELECT + region.id, + region.name, + province.name + FROM + region + LEFT JOIN + province + ON + region.province_id = province.id + WHERE + region.id = %s + """ + cursor.execute(query, (region_id,)) + return cursor.fetchone() + + +@handle_query_errors(RegionRetrievalError) +def get_region_by_province(cursor: Cursor, province_id): """ This function get a region from the database. @@ -692,24 +622,22 @@ def get_region_by_province(cursor, province_id): Returns: - dict: The region """ - try: - query = """ - SELECT - id, - province_id, - name - FROM - region - WHERE - province_id = %s - """ - cursor.execute(query, (province_id,)) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) + query = """ + SELECT + id, + province_id, + name + FROM + region + WHERE + province_id = %s + """ + cursor.execute(query, (province_id,)) + return cursor.fetchall() -def new_province(cursor, name): +@handle_query_errors(ProvinceCreationError) +def new_province(cursor: Cursor, name): """ This function create a new province in the database. @@ -720,24 +648,24 @@ def new_province(cursor, name): Returns: - str: The UUID of the province """ - try: - query = """ - INSERT INTO - province ( - name - ) - VALUES - (%s) - RETURNING - id - """ - cursor.execute(query, (name,)) - return cursor.fetchone()[0] - except Exception as e: - raise ProvinceCreationError("Datastore province unhandeled error" + e.__str__()) + query = """ + INSERT INTO + province ( + name + ) + VALUES + (%s) + RETURNING + id + """ + cursor.execute(query, (name,)) + if result := cursor.fetchone(): + return result[0] + raise ProvinceCreationError("Failed to create Province. No data returned.") -def get_province(cursor, province_id): +@handle_query_errors(ProvinceRetrievalError) +def get_province(cursor: Cursor, province_id): """ This function get a province from the database. @@ -748,29 +676,24 @@ def get_province(cursor, province_id): Returns: - dict: The province """ - try: - query = """ - SELECT - name - FROM - province - WHERE - id = %s - """ - cursor.execute(query, (province_id,)) - res = cursor.fetchone() - if res is None: - raise ProvinceNotFoundError - return res - except ProvinceNotFoundError: - raise ProvinceNotFoundError( - "province not found with province_id: " + str(province_id) - ) - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) - - -def get_all_province(cursor): + query = """ + SELECT + name + FROM + province + WHERE + id = %s + """ + cursor.execute(query, (province_id,)) + if result := cursor.fetchone(): + return result + raise ProvinceNotFoundError( + "province not found with province_id: " + str(province_id) + ) + + +@handle_query_errors(ProvinceRetrievalError) +def get_all_province(cursor: Cursor): """ This function get all province from the database. @@ -780,15 +703,12 @@ def get_all_province(cursor): Returns: - dict: The province """ - try: - query = """ - SELECT - id, - name - FROM - province - """ - cursor.execute(query) - return cursor.fetchall() - except Exception as e: - raise Exception("Datastore organization unhandeled error" + e.__str__()) + query = """ + SELECT + id, + name + FROM + province + """ + cursor.execute(query) + return cursor.fetchall() diff --git a/fertiscan/db/queries/specification/__init__.py b/fertiscan/db/queries/specification/__init__.py index 8f51f302..4f592ff3 100644 --- a/fertiscan/db/queries/specification/__init__.py +++ b/fertiscan/db/queries/specification/__init__.py @@ -2,26 +2,27 @@ This module represent the function for the Specification table """ -from psycopg import sql +from psycopg import Cursor, sql -class SpecificationCreationError(Exception): - pass - - -class SpecificationNotFoundError(Exception): - pass +from fertiscan.db.queries.errors import ( + SpecificationCreationError, + SpecificationNotFoundError, + SpecificationQueryError, + SpecificationRetrievalError, + handle_query_errors, +) +@handle_query_errors(SpecificationCreationError) def new_specification( - cursor, + cursor: Cursor, humidity, ph, solubility, label_id, language, edited=False, - schema="public", ): """ This function creates a new specification in the database. @@ -35,26 +36,24 @@ def new_specification( Returns: - The UUID of the new specification. """ - try: - if language not in ["en", "fr"]: - raise SpecificationCreationError( - "Error: language must be either 'en' or 'fr'" - ) - #query = sql.SQL(""" - # SELECT {}.new_specification(%s, %s, %s, %s, %s, %s); - #""").format(sql.Identifier(schema)) - query=sql.SQL(""" - SELECT new_specification(%s, %s, %s, %s, %s, %s); - """) - cursor.execute(query, (humidity, ph, solubility, language, label_id, edited)) - return cursor.fetchone()[0] - except SpecificationCreationError: - raise - except Exception: - raise SpecificationCreationError("Error: could not create the specification") - - -def get_specification(cursor, specification_id): + if language not in ["en", "fr"]: + raise SpecificationCreationError("Error: language must be either 'en' or 'fr'") + # query = sql.SQL(""" + # SELECT {}.new_specification(%s, %s, %s, %s, %s, %s); + # """).format(sql.Identifier(schema)) + query = sql.SQL(""" + SELECT new_specification(%s, %s, %s, %s, %s, %s); + """) + cursor.execute(query, (humidity, ph, solubility, language, label_id, edited)) + if result := cursor.fetchone(): + return result[0] + raise SpecificationCreationError( + "Failed to create Specification. No data returned." + ) + + +@handle_query_errors(SpecificationRetrievalError) +def get_specification(cursor: Cursor, specification_id): """ This function gets the specification from the database. Parameters: @@ -63,31 +62,25 @@ def get_specification(cursor, specification_id): Returns: - The specification. """ - try: - query = """ - SELECT - humidity, - ph, - solubility, - edited - FROM - specification - WHERE - id = %s - """ - cursor.execute(query, (specification_id,)) - result = cursor.fetchone() - - if result is None: - raise SpecificationNotFoundError( - "No record found for the given specification_id" - ) + query = """ + SELECT + humidity, + ph, + solubility, + edited + FROM + specification + WHERE + id = %s + """ + cursor.execute(query, (specification_id,)) + if result := cursor.fetchone(): return result - except Exception: - raise SpecificationNotFoundError("Error: could not get the specification") + raise SpecificationNotFoundError("No record found for the given specification_id") -def has_specification(cursor, label_id): +@handle_query_errors(SpecificationQueryError) +def has_specification(cursor: Cursor, label_id): """ This function checks if a label has specification. Parameters: @@ -96,26 +89,26 @@ def has_specification(cursor, label_id): Returns: - True if the label has specification, False otherwise. """ - try: - query = """ - SELECT - EXISTS( - SELECT 1 - FROM - specification - WHERE - label_id = %s - ); - """ - cursor.execute(query, (label_id,)) - return cursor.fetchone()[0] - except Exception: - raise SpecificationNotFoundError( - "Error: could not check if the label has specification" - ) + query = """ + SELECT + EXISTS( + SELECT 1 + FROM + specification + WHERE + label_id = %s + ); + """ + cursor.execute(query, (label_id,)) + if result := cursor.fetchone(): + return result[0] + raise SpecificationQueryError( + "Failed to check if label has specification. No data returned." + ) -def get_specification_json(cursor, label_id) -> dict: +@handle_query_errors(SpecificationRetrievalError) +def get_specification_json(cursor: Cursor, label_id) -> dict: """ This function gets the specification from the database. Parameters: @@ -124,28 +117,19 @@ def get_specification_json(cursor, label_id) -> dict: Returns: - The specification. """ - try: - if not has_specification(cursor, label_id): - return {"specifications": {"en": [], "fr": []}} - query = """ - SELECT get_specification_json(%s); - """ - cursor.execute(query, (label_id,)) - result = cursor.fetchone() - - if result is None: - raise SpecificationNotFoundError( - "No record found for the given specification_id" - ) - specification = result[0] - return specification - except SpecificationNotFoundError: - raise - except Exception: - raise + if not has_specification(cursor, label_id): + return {"specifications": {"en": [], "fr": []}} + query = """ + SELECT get_specification_json(%s); + """ + cursor.execute(query, (label_id,)) + if result := cursor.fetchone(): + return result[0] + raise SpecificationNotFoundError("No record found for the given specification_id") -def get_all_specifications(cursor, label_id): +@handle_query_errors(SpecificationRetrievalError) +def get_all_specifications(cursor: Cursor, label_id): """ This function gets all the specifications from the database. Parameters: @@ -154,28 +138,20 @@ def get_all_specifications(cursor, label_id): Returns: - The specifications. """ - try: - query = """ - SELECT - id, - humidity, - ph, - solubility, - edited, - language - FROM - specification - WHERE - label_id = %s - """ - cursor.execute(query, (label_id,)) - result = cursor.fetchall() - if result is None or len(result) == 0: - raise SpecificationNotFoundError( - "No record found for the given specification_id" - ) + query = """ + SELECT + id, + humidity, + ph, + solubility, + edited, + language + FROM + specification + WHERE + label_id = %s + """ + cursor.execute(query, (label_id,)) + if result := cursor.fetchall(): return result - except Exception: - raise SpecificationNotFoundError( - "Error: could not get the specifications with the label_id= " + label_id - ) + raise SpecificationNotFoundError("No record found for the given label_id") diff --git a/fertiscan/db/queries/sub_label/__init__.py b/fertiscan/db/queries/sub_label/__init__.py index 37588b23..d5e4a572 100644 --- a/fertiscan/db/queries/sub_label/__init__.py +++ b/fertiscan/db/queries/sub_label/__init__.py @@ -3,24 +3,24 @@ """ - -class SubLabelCreationError(Exception): - pass - - -class SubLabelNotFoundError(Exception): - pass - - -class SubTypeCreationError(Exception): - pass - - -class SubTypeNotFoundError(Exception): - pass - - -def new_sub_label(cursor, text_fr, text_en, label_id, sub_type_id, edited=False): +from psycopg import Cursor + +from fertiscan.db.queries.errors import ( + SubLabelCreationError, + SubLabelNotFoundError, + SubLabelQueryError, + SubLabelRetrievalError, + SubLabelUpdateError, + SubTypeCreationError, + SubTypeQueryError, + handle_query_errors, +) + + +@handle_query_errors(SubLabelCreationError) +def new_sub_label( + cursor: Cursor, text_fr, text_en, label_id, sub_type_id, edited=False +): """ This function creates a new sub label in the database. @@ -35,17 +35,17 @@ def new_sub_label(cursor, text_fr, text_en, label_id, sub_type_id, edited=False) Returns: - The UUID of the new sub label. """ - try: - query = """ - SELECT new_sub_label(%s, %s, %s, %s, %s); - """ - cursor.execute(query, (text_fr, text_en, label_id, sub_type_id, edited)) - return cursor.fetchone()[0] - except Exception: - raise SubLabelCreationError("Error: could not create the sub label") + query = """ + SELECT new_sub_label(%s, %s, %s, %s, %s); + """ + cursor.execute(query, (text_fr, text_en, label_id, sub_type_id, edited)) + if result := cursor.fetchone(): + return result[0] + raise SubLabelCreationError("Failed to create SubLabel. No data returned.") -def get_sub_label(cursor, sub_label_id): +@handle_query_errors(SubLabelRetrievalError) +def get_sub_label(cursor: Cursor, sub_label_id): """ This function gets the sub label from the database. @@ -56,26 +56,24 @@ def get_sub_label(cursor, sub_label_id): Returns: - The sub label entity. """ - try: - query = """ - SELECT - text_content_fr, - text_content_en, - edited, - label_id, - sub_type_id - FROM - sub_label - WHERE - id = %s - """ - cursor.execute(query, (sub_label_id,)) - return cursor.fetchone() - except Exception: - raise SubLabelNotFoundError("Error: could not get the sub label") + query = """ + SELECT + text_content_fr, + text_content_en, + edited, + label_id, + sub_type_id + FROM + sub_label + WHERE + id = %s + """ + cursor.execute(query, (sub_label_id,)) + return cursor.fetchone() -def has_sub_label(cursor, label_id): +@handle_query_errors(SubLabelQueryError) +def has_sub_label(cursor: Cursor, label_id): """ This function checks if a label has sub label. @@ -86,25 +84,27 @@ def has_sub_label(cursor, label_id): Returns: - True if the label has sub label, False otherwise. """ - try: - query = """ - SELECT - Exists( - SELECT 1 - FROM - sub_label - WHERE - label_id = %s - Limit 1 - ); - """ - cursor.execute(query, (label_id,)) - return cursor.fetchone()[0] - except Exception: - raise SubLabelNotFoundError("Error: could not check if the label has sub label") + query = """ + SELECT + Exists( + SELECT 1 + FROM + sub_label + WHERE + label_id = %s + Limit 1 + ); + """ + cursor.execute(query, (label_id,)) + if result := cursor.fetchone(): + return result[0] + raise SubLabelQueryError( + "Failed to check if label has sub label. No data returned." + ) -def get_sub_label_json(cursor, label_id) -> dict: +@handle_query_errors(SubLabelRetrievalError) +def get_sub_label_json(cursor: Cursor, label_id) -> dict: """ This function gets all the sub label for a label in a json format. @@ -115,26 +115,23 @@ def get_sub_label_json(cursor, label_id) -> dict: Returns: - The sub label in dict format. """ - try: - if not has_sub_label(cursor, label_id): - return { - "cautions": {"en": [], "fr": []}, - "instructions": {"en": [], "fr": []}, - "first_aid": {"en": [], "fr": []}, - } - query = """ - SELECT get_sub_label_json(%s); - """ - cursor.execute(query, (str(label_id),)) - sub_label = cursor.fetchone() - return sub_label[0] - except SubLabelNotFoundError as e: - raise e - except Exception: - raise - - -def get_full_sub_label(cursor, sub_label_id): + if not has_sub_label(cursor, label_id): + return { + "cautions": {"en": [], "fr": []}, + "instructions": {"en": [], "fr": []}, + "first_aid": {"en": [], "fr": []}, + } + query = """ + SELECT get_sub_label_json(%s); + """ + cursor.execute(query, (str(label_id),)) + if result := cursor.fetchone(): + return result[0] + raise SubLabelNotFoundError("No sub label found for label_id: " + str(label_id)) + + +@handle_query_errors(SubLabelRetrievalError) +def get_full_sub_label(cursor: Cursor, sub_label_id): """ This function gets the full sub label from the database. @@ -145,31 +142,29 @@ def get_full_sub_label(cursor, sub_label_id): Returns: - The full sub label entity. """ - try: - query = """ - SELECT - sub_label.id, - sub_label.text_content_fr, - sub_label.text_content_en, - sub_label.edited, - sub_type.type_fr, - sub_type.type_en - FROM - sub_label - JOIN - sub_type - ON - sub_label.sub_type_id = sub_type.id - WHERE - sub_label.id = %s - """ - cursor.execute(query, (sub_label_id,)) - return cursor.fetchone() - except Exception: - raise SubLabelNotFoundError("Error: could not get the full sub label") + query = """ + SELECT + sub_label.id, + sub_label.text_content_fr, + sub_label.text_content_en, + sub_label.edited, + sub_type.type_fr, + sub_type.type_en + FROM + sub_label + JOIN + sub_type + ON + sub_label.sub_type_id = sub_type.id + WHERE + sub_label.id = %s + """ + cursor.execute(query, (sub_label_id,)) + return cursor.fetchone() -def get_all_sub_label(cursor, label_id): +@handle_query_errors(SubLabelRetrievalError) +def get_all_sub_label(cursor: Cursor, label_id): """ This function gets all the sub label from the database. @@ -180,33 +175,31 @@ def get_all_sub_label(cursor, label_id): Returns: - The list of sub label entity. """ - try: - query = """ - SELECT - sub_label.id, - sub_label.text_content_fr, - sub_label.text_content_en, - sub_label.edited, - sub_type.type_fr, - sub_type.type_en - FROM - sub_label - JOIN - sub_type - ON - sub_label.sub_type_id = sub_type.id - WHERE - label_id = %s - ORDER BY - sub_type.type_en - """ - cursor.execute(query, (label_id,)) - return cursor.fetchall() - except Exception: - raise SubLabelNotFoundError("Error: could not get the sub label") + query = """ + SELECT + sub_label.id, + sub_label.text_content_fr, + sub_label.text_content_en, + sub_label.edited, + sub_type.type_fr, + sub_type.type_en + FROM + sub_label + JOIN + sub_type + ON + sub_label.sub_type_id = sub_type.id + WHERE + label_id = %s + ORDER BY + sub_type.type_en + """ + cursor.execute(query, (label_id,)) + return cursor.fetchall() -def update_sub_label(cursor, sub_label_id, text_fr, text_en, edited=True): +@handle_query_errors(SubLabelUpdateError) +def update_sub_label(cursor: Cursor, sub_label_id, text_fr, text_en, edited=True): """ This function updates the sub label in the database. @@ -220,23 +213,21 @@ def update_sub_label(cursor, sub_label_id, text_fr, text_en, edited=True): Returns: - None """ - try: - query = """ - UPDATE - sub_label - SET - text_content_fr = %s, - text_content_en = %s, - edited = %s - WHERE - id = %s - """ - cursor.execute(query, (text_fr, text_en, edited, sub_label_id)) - except Exception: - raise SubLabelNotFoundError("Error: could not update the sub label") + query = """ + UPDATE + sub_label + SET + text_content_fr = %s, + text_content_en = %s, + edited = %s + WHERE + id = %s + """ + cursor.execute(query, (text_fr, text_en, edited, sub_label_id)) -def new_sub_type(cursor, type_fr, type_en): +@handle_query_errors(SubTypeCreationError) +def new_sub_type(cursor: Cursor, type_fr, type_en): """ This function creates a new sub type in the database. @@ -248,22 +239,22 @@ def new_sub_type(cursor, type_fr, type_en): Returns: - The UUID of the new sub type. """ - try: - query = """ - INSERT INTO - sub_type (type_fr, type_en) - VALUES - (%s, %s) - RETURNING - id - """ - cursor.execute(query, (type_fr, type_en)) - return cursor.fetchone()[0] - except Exception: - raise SubTypeCreationError("Error: could not create the sub type") + query = """ + INSERT INTO + sub_type (type_fr, type_en) + VALUES + (%s, %s) + RETURNING + id + """ + cursor.execute(query, (type_fr, type_en)) + if result := cursor.fetchone(): + return result[0] + raise SubTypeCreationError("Failed to create SubType. No data returned.") -def get_sub_type_id(cursor, type_name): +@handle_query_errors(SubTypeQueryError) +def get_sub_type_id(cursor: Cursor, type_name): """ This function gets the sub type from the database. @@ -274,22 +265,21 @@ def get_sub_type_id(cursor, type_name): Returns: - The UUID of the sub type. """ - try: - query = """ - SELECT - id - FROM - sub_type - WHERE - type_fr ILIKE %s OR type_en ILIKE %s - """ - cursor.execute( - query, - ( - type_name, - type_name, - ), - ) - return cursor.fetchone()[0] - except Exception: - raise SubTypeNotFoundError("Error: could not get the sub type") + query = """ + SELECT + id + FROM + sub_type + WHERE + type_fr ILIKE %s OR type_en ILIKE %s + """ + cursor.execute( + query, + ( + type_name, + type_name, + ), + ) + if result := cursor.fetchone(): + return result[0] + raise SubTypeQueryError("Failed to get the sub type id. No data returned.") diff --git a/fertiscan_pyproject.toml b/fertiscan_pyproject.toml index 6c58430a..7a8622b3 100644 --- a/fertiscan_pyproject.toml +++ b/fertiscan_pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "fertiscan_datastore" -version = "1.0.2" +version = "1.0.3" authors = [ { name="Francois Werbrouck", email="francois.werbrouck@inspection.gc.ca" } ] diff --git a/tests/fertiscan/db/test_guaranteed_analysis.py b/tests/fertiscan/db/test_guaranteed_analysis.py index 249ef192..5bf83be3 100644 --- a/tests/fertiscan/db/test_guaranteed_analysis.py +++ b/tests/fertiscan/db/test_guaranteed_analysis.py @@ -7,8 +7,9 @@ import unittest import datastore.db as db -from fertiscan.db.metadata import inspection as metadata from datastore.db.metadata import validator +from fertiscan.db.metadata import inspection as metadata +from fertiscan.db.queries import errors as e from fertiscan.db.queries import label, nutrients DB_CONNECTION_STRING = os.environ.get("FERTISCAN_DB_URL") @@ -78,19 +79,19 @@ def test_get_element_id(self): def test_get_element_error(self): self.assertRaises( - nutrients.ElementNotFoundError, + e.ElementCompoundNotFoundError, nutrients.get_element_id_full_search, self.cursor, "not-an-element", ) self.assertRaises( - nutrients.ElementNotFoundError, + e.ElementCompoundNotFoundError, nutrients.get_element_id_name, self.cursor, "not-an-element", ) self.assertRaises( - nutrients.ElementNotFoundError, + e.ElementCompoundNotFoundError, nutrients.get_element_id_symbol, self.cursor, "not-an-element", @@ -170,7 +171,7 @@ def test_new_guaranteed_analysis(self): self.assertTrue(validator.is_valid_uuid(guaranteed_analysis_id)) def test_new_guaranteed_analysis_empty(self): - with self.assertRaises(nutrients.GuaranteedCreationError): + with self.assertRaises(e.GuaranteedAnalysisCreationError): nutrients.new_guaranteed_analysis( self.cursor, None, diff --git a/tests/fertiscan/db/test_organization.py b/tests/fertiscan/db/test_organization.py index 764b7958..59bfed34 100644 --- a/tests/fertiscan/db/test_organization.py +++ b/tests/fertiscan/db/test_organization.py @@ -208,7 +208,7 @@ def test_new_organization_info_no_location(self): self.assertTrue(validator.is_valid_uuid(id)) def test_new_organization_located_empty(self): - with self.assertRaises(organization.OrganizationCreationError): + with self.assertRaises(organization.OrganizationInformationCreationError): organization.new_organization_info_located( self.cursor, None, None, None, None ) @@ -234,7 +234,7 @@ def test_get_organization_info(self): self.assertEqual(data[3], self.location_id) def test_get_organization_info_not_found(self): - with self.assertRaises(organization.OrganizationNotFoundError): + with self.assertRaises(organization.OrganizationInformationNotFoundError): organization.get_organization_info(self.cursor, str(uuid.uuid4())) def test_update_organization_info(self): diff --git a/tests/fertiscan/db/test_sub_label.py b/tests/fertiscan/db/test_sub_label.py index 56c3549e..67a4a282 100644 --- a/tests/fertiscan/db/test_sub_label.py +++ b/tests/fertiscan/db/test_sub_label.py @@ -50,7 +50,7 @@ def test_get_sub_type_id(self): self.assertEqual(str(data_fr), str(sub_type_id)) def test_get_sub_type_id_not_found(self): - with self.assertRaises(sub_label.SubTypeNotFoundError): + with self.assertRaises(sub_label.SubTypeQueryError): sub_label.get_sub_type_id(self.cursor, "not-a-type") diff --git a/tests/fertiscan/test_datastore.py b/tests/fertiscan/test_datastore.py index d3c764db..e18c48a1 100644 --- a/tests/fertiscan/test_datastore.py +++ b/tests/fertiscan/test_datastore.py @@ -304,7 +304,7 @@ def test_register_analysis_invalid_user(self): def test_register_analysy_missing_key(self): self.analysis_json.pop("specification_en", None) - with self.assertRaises(fertiscan.data_inspection.MissingKeyError): + with self.assertRaises(fertiscan.data_inspection.BuildInspectionImportError): asyncio.run( fertiscan.register_analysis( self.cursor, From 0903c450e8f981033825aa6bd582b4420ecc40f3 Mon Sep 17 00:00:00 2001 From: "K. Allagbe" Date: Fri, 11 Oct 2024 18:21:17 -0400 Subject: [PATCH 2/5] issue #106: tests --- fertiscan/db/metadata/inspection/__init__.py | 43 ++++++----- tests/fertiscan/metadata/test_inspection.py | 73 ++++++++++++++++++- tests/fertiscan/queries/__init__.py | 0 .../queries/test_query_error_handling.py | 67 +++++++++++++++++ 4 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 tests/fertiscan/queries/__init__.py create mode 100644 tests/fertiscan/queries/test_query_error_handling.py diff --git a/fertiscan/db/metadata/inspection/__init__.py b/fertiscan/db/metadata/inspection/__init__.py index 421d370b..4165fcf7 100644 --- a/fertiscan/db/metadata/inspection/__init__.py +++ b/fertiscan/db/metadata/inspection/__init__.py @@ -433,22 +433,27 @@ def extract_npk(npk: str): Returns: - A list containing the npk values. """ - if npk is None or npk == "" or len(npk) < 5: - return [None, None, None] - npk_formated = npk.replace("N", "-") - npk_formated = npk_formated.replace("P", "-") - npk_formated = npk_formated.replace("K", "-") - npk_reformated = npk.split("-") - for i in range(len(npk_reformated)): - if not npk_reformated[i].isnumeric(): - NPKError( - "NPK values must be numeric. Issue with: " - + npk_reformated[i] - + "in the NPK string: " - + npk - ) - return [ - float(npk_reformated[0]), - float(npk_reformated[1]), - float(npk_reformated[2]), - ] + try: + if npk is None or npk == "" or len(npk) < 5: + return [None, None, None] + npk_formated = npk.replace("N", "-") + npk_formated = npk_formated.replace("P", "-") + npk_formated = npk_formated.replace("K", "-") + npk_reformated = npk.split("-") + for i in range(len(npk_reformated)): + if not npk_reformated[i].isnumeric(): + NPKError( + "NPK values must be numeric. Issue with: " + + npk_reformated[i] + + "in the NPK string: " + + npk + ) + return [ + float(npk_reformated[0]), + float(npk_reformated[1]), + float(npk_reformated[2]), + ] + except NPKError: + raise + except Exception as e: + raise NPKError(f"Unexpected error: {e}") from e diff --git a/tests/fertiscan/metadata/test_inspection.py b/tests/fertiscan/metadata/test_inspection.py index 23eaf23c..2b4ff0f6 100644 --- a/tests/fertiscan/metadata/test_inspection.py +++ b/tests/fertiscan/metadata/test_inspection.py @@ -1,11 +1,18 @@ import json import os import unittest +from unittest.mock import Mock, patch import datastore.db as db import fertiscan.db.metadata.inspection as metadata from datastore.db.queries import picture, user +from fertiscan.db.metadata.errors import ( + BuildInspectionExportError, + BuildInspectionImportError, + NPKError, +) from fertiscan.db.queries import inspection +from fertiscan.db.queries.errors import QueryError DB_CONNECTION_STRING = os.environ.get("FERTISCAN_DB_URL") if DB_CONNECTION_STRING is None or DB_CONNECTION_STRING == "": @@ -16,7 +23,7 @@ raise ValueError("FERTISCAN_SCHEMA_TESTING is not set") -class test_inspection_export(unittest.TestCase): +class TestInspectionExport(unittest.TestCase): def setUp(self): self.con = db.connect_db(DB_CONNECTION_STRING, DB_SCHEMA) self.cursor = self.con.cursor() @@ -389,7 +396,6 @@ def test_missing_guaranteed_analysis(self): # "Empty specification found in 'fr' list" # ) - def test_null_in_middle_of_sub_label_en(self): formatted_analysis = metadata.build_inspection_import(self.analyse) @@ -553,8 +559,37 @@ def test_organization_not_located(self): self.assertEqual(inspection_data["manufacturer"]["name"], test_str) self.assertEqual(inspection_data["company"]["website"], test_str) + @patch("fertiscan.db.queries.label.get_label_information_json") + def test_query_error(self, mock_get_label_info): + # Simulate QueryError being raised + mock_get_label_info.side_effect = QueryError("Simulated query error") -class test_inspection_import(unittest.TestCase): + cursor = Mock() + inspection_id = 1 + label_info_id = 2 + + with self.assertRaises(BuildInspectionExportError) as context: + metadata.build_inspection_export(cursor, inspection_id, label_info_id) + + self.assertIn("Error fetching data", str(context.exception)) + self.assertIn("Simulated query error", str(context.exception)) + + @patch("fertiscan.db.queries.label.get_label_information_json") + def test_unexpected_error(self, mock_get_label_info): + mock_get_label_info.side_effect = TypeError("Simulated unexpected error") + + cursor = Mock() + inspection_id = 1 + label_info_id = 2 + + with self.assertRaises(BuildInspectionImportError) as context: + metadata.build_inspection_export(cursor, inspection_id, label_info_id) + + self.assertIn("Unexpected error", str(context.exception)) + self.assertIn("Simulated unexpected error", str(context.exception)) + + +class TestInspectionImport(unittest.TestCase): def setUp(self): self.con = db.connect_db(DB_CONNECTION_STRING, DB_SCHEMA) self.cursor = self.con.cursor() @@ -608,3 +643,35 @@ def test_empty_sub_label(self): self.assertIsNotNone(inspection_data["cautions"]) self.assertEqual(inspection_data["cautions"]["en"], []) self.assertEqual(inspection_data["cautions"]["fr"], []) + + def test_missing_keys(self): + analysis_form = { + "company_name": "Test Company", + "fertiliser_name": "Test Fertilizer", + # intentionally leaving out other required keys + } + with self.assertRaises(BuildInspectionImportError) as context: + metadata.build_inspection_import(analysis_form) + self.assertIn("The analysis form is missing keys", str(context.exception)) + + def test_validation_error(self): + self.analyse["weight"] = [{"unit": "kg", "value": "invalid_value"}] + with self.assertRaises(BuildInspectionImportError) as context: + metadata.build_inspection_import(self.analyse) + self.assertIn("Validation error", str(context.exception)) + + def test_npk_error(self): + self.analyse["npk"] = "invalid_npk" + with self.assertRaises(NPKError): + metadata.build_inspection_import(self.analyse) + + @patch("fertiscan.db.metadata.inspection.extract_npk") + def test_unexpected_error(self, mock_extract_npk): + # Mock extract_npk to raise an exception to simulate an unexpected error + mock_extract_npk.side_effect = Exception("Simulated unexpected error") + + with self.assertRaises(BuildInspectionImportError) as context: + metadata.build_inspection_import(self.analyse) + + self.assertIn("Unexpected error", str(context.exception)) + self.assertIn("Simulated unexpected error", str(context.exception)) diff --git a/tests/fertiscan/queries/__init__.py b/tests/fertiscan/queries/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/fertiscan/queries/test_query_error_handling.py b/tests/fertiscan/queries/test_query_error_handling.py new file mode 100644 index 00000000..ff353eef --- /dev/null +++ b/tests/fertiscan/queries/test_query_error_handling.py @@ -0,0 +1,67 @@ +import unittest + +from psycopg import Error as PsycopgError + +from fertiscan.db.queries.errors import QueryError, handle_query_errors + + +class TestHandleQueryErrors(unittest.TestCase): + # Test to ensure the decorated function runs successfully without errors + def test_successful_function_execution(self): + @handle_query_errors() + def successful_function(): + return "Success" + + self.assertEqual(successful_function(), "Success") + + # Test to verify that QueryError and its subclasses are raised as-is + def test_query_error_propagation(self): + class CustomQueryError(QueryError): + pass + + @handle_query_errors() + def function_raises_query_error(): + raise CustomQueryError("Test query error") + + with self.assertRaises(CustomQueryError): + function_raises_query_error() + + # Test to ensure that psycopg DB errors are caught and raised as QueryError + def test_db_error_handling(self): + @handle_query_errors() + def function_raises_db_error(): + raise PsycopgError("DB error") + + with self.assertRaises(QueryError) as cm: + function_raises_db_error() + + self.assertIn("Database error: DB error", str(cm.exception)) + + # Test to ensure any unexpected exceptions are caught and raised as QueryError + def test_unexpected_error_handling(self): + @handle_query_errors() + def function_raises_unexpected_error(): + raise ValueError("Some value error") + + with self.assertRaises(QueryError) as cm: + function_raises_unexpected_error() + + self.assertIn("Unexpected error: Some value error", str(cm.exception)) + + # Test to check custom error class handling in the decorator + def test_custom_error_cls(self): + class CustomError(Exception): + pass + + @handle_query_errors(error_cls=CustomError) + def function_raises_db_error(): + raise PsycopgError("DB error") + + with self.assertRaises(CustomError) as cm: + function_raises_db_error() + + self.assertIn("Database error: DB error", str(cm.exception)) + + +if __name__ == "__main__": + unittest.main() From bdb058f94e057caccfe945ea294431cd81520f10 Mon Sep 17 00:00:00 2001 From: "K. Allagbe" Date: Wed, 16 Oct 2024 23:05:18 -0400 Subject: [PATCH 3/5] issue #106: raise LabelInformationQueryError for label dimension --- fertiscan/db/queries/label/__init__.py | 2 +- fertiscan_pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fertiscan/db/queries/label/__init__.py b/fertiscan/db/queries/label/__init__.py index 90f1eaaf..f3efe3bc 100644 --- a/fertiscan/db/queries/label/__init__.py +++ b/fertiscan/db/queries/label/__init__.py @@ -184,7 +184,7 @@ def get_label_dimension(cursor, label_id): cursor.execute(query, (label_id,)) data = cursor.fetchone() if data is None or data[0] is None: - raise LabelInformationNotFoundError( + raise LabelInformationQueryError( "Error: could not get the label dimension for label: " + str(label_id) ) return data diff --git a/fertiscan_pyproject.toml b/fertiscan_pyproject.toml index 0b01d1f8..f507a2a3 100644 --- a/fertiscan_pyproject.toml +++ b/fertiscan_pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "fertiscan_datastore" version = "1.0.4" authors = [ - { name="Francois Werbrouck", email="francois.werbrouck@inspection.gc.ca" } + { name="Francois Werbrouck", email="francois.werbrouck@inspection.gc.ca" }, { name="Kotchikpa Guy-Landry Allagbe" , email = "kotchikpaguy-landry.allagbe@inspection.gc.ca"} ] description = "Data management python layer" From 5e04932a481ec08b3b294374870e15364f0e09e4 Mon Sep 17 00:00:00 2001 From: "K. Allagbe" Date: Sat, 19 Oct 2024 20:24:54 -0400 Subject: [PATCH 4/5] issue #106: label dimension errors and handling --- fertiscan/db/queries/errors.py | 13 +++++++++++++ fertiscan/db/queries/label/__init__.py | 7 ++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/fertiscan/db/queries/errors.py b/fertiscan/db/queries/errors.py index 3d788d00..6ca37ec5 100644 --- a/fertiscan/db/queries/errors.py +++ b/fertiscan/db/queries/errors.py @@ -5,6 +5,7 @@ |__QueryError |__InspectionQueryError |__LabelInformationQueryError + |__LabelDimensionQueryError |__MetricQueryError |__UnitQueryError |__ElementCompoundQueryError @@ -105,6 +106,18 @@ class LabelInformationDeleteError(LabelInformationQueryError): pass +class LabelDimensionQueryError(QueryError): + """Base exception for all label dimension-related query errors.""" + + pass + + +class LabelDimensionNotFoundError(LabelDimensionQueryError): + """Raised when a label dimension is not found.""" + + pass + + class MetricQueryError(QueryError): """Base exception for all metric-related query errors.""" diff --git a/fertiscan/db/queries/label/__init__.py b/fertiscan/db/queries/label/__init__.py index f3efe3bc..26fc5c7b 100644 --- a/fertiscan/db/queries/label/__init__.py +++ b/fertiscan/db/queries/label/__init__.py @@ -5,9 +5,10 @@ from psycopg import Cursor from fertiscan.db.queries.errors import ( + LabelDimensionNotFoundError, + LabelDimensionQueryError, LabelInformationCreationError, LabelInformationNotFoundError, - LabelInformationQueryError, LabelInformationRetrievalError, handle_query_errors, ) @@ -146,7 +147,7 @@ def get_label_information_json(cursor, label_info_id) -> dict: return label_info[0] -@handle_query_errors(LabelInformationQueryError) +@handle_query_errors(LabelDimensionQueryError) def get_label_dimension(cursor, label_id): """ This function get the label_dimension from the database. @@ -184,7 +185,7 @@ def get_label_dimension(cursor, label_id): cursor.execute(query, (label_id,)) data = cursor.fetchone() if data is None or data[0] is None: - raise LabelInformationQueryError( + raise LabelDimensionNotFoundError( "Error: could not get the label dimension for label: " + str(label_id) ) return data From ca2353440210b9e750aa21e55463ab428e82e3c5 Mon Sep 17 00:00:00 2001 From: "K. Allagbe" Date: Mon, 28 Oct 2024 10:50:12 -0400 Subject: [PATCH 5/5] issue #106: clarification of the query errors hierarchy --- fertiscan/db/queries/errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fertiscan/db/queries/errors.py b/fertiscan/db/queries/errors.py index 6ca37ec5..7d53b120 100644 --- a/fertiscan/db/queries/errors.py +++ b/fertiscan/db/queries/errors.py @@ -20,7 +20,7 @@ |__SubLabelQueryError |__SubTypeQueryError -Each QueryError sub type exception has specific errors related to creation, retrieval, updating, and deletion, as well as a 'not found' error. +Each QueryError sub type exception also has specific errors sub types related to creation, retrieval, updating, and deletion, as well as a 'not found' error. """ from functools import wraps