diff --git a/fertiscan/__init__.py b/fertiscan/__init__.py index 16c2706f..10ea155c 100644 --- a/fertiscan/__init__.py +++ b/fertiscan/__init__.py @@ -30,88 +30,152 @@ if FERTISCAN_STORAGE_URL is None or FERTISCAN_STORAGE_URL == "": print("Warning: FERTISCAN_STORAGE_URL not set") -class Inspection(BaseModel): - id: UUID - user_id: UUID - folder_id : UUID - label_id: Optional[UUID] = None - verified: Optional[bool] = None - class InspectionController: - def __init__(self,inspection_model: Inspection): - self.model:Inspection = inspection_model - - - def register_analysis( + def __init__(self,inspection_model:data_inspection.Inspection): + self.model:data_inspection.Inspection = inspection_model + self.id:UUID = inspection_model.inspection_id + return + + def get_inspection_image_location_data( self, - cursor: Cursor, - analysis_dict, - ): + cursor: Cursor + )->tuple[UUID,UUID]: """ - Register an analysis in the database + Retrieve the relevant information regarding where the Inspection Images are stored + This is usefull to perform Container_Controller.get_folder_pictures() Parameters: - - cursor: The cursor object to interact with the database. - - container_client: The container client of the user. - - analysis_dict (dict): The analysis to register in a dict string - + - cursor (Cursor): Database cursor for executing queries. + Returns: - - The analysis_dict with the analysis_id added. + - tuple (container_id: UUID, folder: UUID): The id necessary to locate the images """ - if not user.is_a_user_id(cursor=cursor, user_id=self.model.user_id): - raise user.UserNotFoundError(f"User not found based on the given id: {self.model.user_id}") - # Register analysis in the database - formatted_analysis = data_inspection.build_inspection_import(analysis_dict,user_id) + if not inspection.is_a_inspection_id(cursor=cursor, inspection_id=self.id): + raise inspection.InspectionNotFoundError( + f"Inspection not found based on the given id: {self.id}" + ) + picture_set_id = inspection.get_inspection_fk(cursor,self.id)[2] - analysis_db = inspection.new_inspection_with_label_info( - cursor, self.model.user_id, self.model.folder_id, formatted_analysis - ) - self.model.id = analysis_db["inspection_id"] - return analysis_db + container_id = picture.get_picture_set_container_id( + cursor=cursor, + picture_set_id=picture_set_id + ) + return (container_id,picture_set_id) def update_inspection( self, cursor: Cursor, + user_id: UUID, updated_data: dict | data_inspection.Inspection, - ): + )->data_inspection.Inspection: """ - Update an existing inspection record in the database. + Update an existing inspection record in the database. - Parameters: - - cursor (Cursor): Database cursor for executing queries. - - inspection_id (str | UUID): UUID of the inspection to update. - - user_id (str | UUID): UUID of the user performing the update. - - updated_data (dict | data_inspection.Inspection): Dictionary or Inspection model containing updated inspection data. + Parameters: + - cursor (Cursor): Database cursor for executing queries. + - inspection_id (str | UUID): UUID of the inspection to update. + - user_id (str | UUID): UUID of the user performing the update. + - updated_data (dict | data_inspection.Inspection): Dictionary or Inspection model containing updated inspection data. - Returns: - - data_inspection.Inspection: Updated inspection data from the database. + Returns: + - data_inspection.Inspection: Updated inspection data from the database. - Raises: - - InspectionUpdateError: If an error occurs during the update. - """ + Raises: + - InspectionUpdateError: If an error occurs during the update. + """ if not user.is_a_user_id(cursor=cursor, user_id=self.model.user_id): - raise user.UserNotFoundError(f"User not found based on the given id: {self.model.user_id}") - + raise user.UserNotFoundError(f"User not found based on the given id: {self.model.user_id}") if not isinstance(updated_data, data_inspection.Inspection): - updated_data = data_inspection.Inspection.model_validate(updated_data) - + updated_data = data_inspection.Inspection.model_validate(updated_data) # The inspection record must exist before updating it if not inspection.is_a_inspection_id(cursor, str(self.model.id)): - raise inspection.InspectionNotFoundError( - f"Inspection not found based on the given id: {self.model.id}" - ) - + raise inspection.InspectionNotFoundError( + f"Inspection not found based on the given id: {self.model.id}" + ) + if self.model.container_id != updated_data.container_id or self.model.folder_id != updated_data.folder_id: + raise Warning("You should not update an Inspection picture location. This does not cause issues in the DB but could result in errors when attempting to fetch the pictures") + if not datastore.container_db.is_a_container(cursor=cursor,container_id=updated_data.container_id): + raise datastore.ContainerCreationError(f"Container not found based on the given id: {updated_data.container_id}") + # Make sure the user can verify this inspection + if not datastore.verify_user_can_write(cursor=cursor,container_id=updated_data.container_id,user_id=user_id): + raise datastore.PermissionNotHighEnough(f"The user {user_id} does not have the write permission or higher which mean he cannot upload an inspection in the container {container_id}") + updated_result = inspection.update_inspection( cursor, self.model.id, self.model.user_id, updated_data.model_dump() ) return data_inspection.Inspection.model_validate(updated_result) - - def get_full_inspection_analysis( + + def delete_inspection( self, cursor:Cursor, - ): + user_id: UUID, + )-> data_inspection.DBInspection: """ - Get the full inspection json from the database + Delete an existing inspection record and its associated picture set from the database. + + Parameters: + - cursor (Cursor): Database cursor for executing queries. + - inspection_id (str | UUID): UUID of the inspection to delete. + - user_id (str | UUID): UUID of the user performing the deletion. + + Returns: + - data_inspection.Inspection: The deleted inspection data from the database. + + """ + 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 datastore.container_db.is_a_container(cursor=cursor,container_id=self.id): + raise datastore.ContainerCreationError(f"Container not found based on the given id: {self.id}") + if not datastore.verify_user_can_write(cursor=cursor,container_id=self.id,user_id=user_id): + raise datastore.PermissionNotHighEnough(f"The user {user_id} does not have the write permission or higher which mean he cannot upload an inspection in the container {container_id}") + + # Delete the inspection and get the returned data + deleted_inspection = inspection.delete_inspection(cursor, self.id, user_id) + deleted_inspection = data_inspection.DBInspection.model_validate(deleted_inspection) + container_controller:ContainerController = datastore.get_container_controller(cursor,self.id) + + container_controller.delete_folder_permanently(cursor,user_id,self.model.folder_id) + return deleted_inspection + +def new_inspection(cursor:Cursor, user_id:UUID, analysis_dict, container_id:UUID, folder_id:UUID)->InspectionController: + """ + Register an analysis in the database + + Parameters: + - cursor: The cursor object to interact with the database. + - container_client: The container client of the user. + - analysis_dict (dict): The analysis to register in a dict string + + Returns: + - The analysis_dict with the analysis_id added. + """ + 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 datastore.container_db.is_a_container(cursor=cursor,container_id=container_id): + raise datastore.ContainerCreationError(f"Container not found based on the given id: {container_id}") + if not datastore.verify_user_can_write(cursor=cursor,container_id=container_id,user_id=user_id): + raise datastore.PermissionNotHighEnough(f"The user {user_id} does not have the write permission or higher which mean he cannot upload an inspection in the container {container_id}") + + formatted_analysis = data_inspection.build_inspection_import( + analysis_form=analysis_dict, + user_id=user_id, + folder_id=folder_id, + container_id=container_id) + + analysis_db = inspection.new_inspection_with_label_info( + cursor, user_id, folder_id, formatted_analysis + ) + analysis_db = data_inspection.Inspection.model_validate(analysis_db) + inspection_controller = InspectionController(analysis_db) + + return inspection_controller + +def get_inspection( + cursor:Cursor, + inspection_id:UUID, +)->InspectionController: + """ + Get the full inspection json from the database and return a controller for it Parameters: - cursor: The cursor object to interact with the database. @@ -119,23 +183,24 @@ def get_full_inspection_analysis( Returns: - The inspection json. """ - if not inspection.is_a_inspection_id(cursor=cursor, inspection_id=self.model.id): - raise inspection.InspectionNotFoundError( + 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: {self.model.id}" ) - - # Retrieve label_info - inspection_metadata = data_inspection.build_inspection_export( - cursor, self.model.id - ) - return inspection_metadata - - def get_user_analysis_by_verified( - self, - cursor:Cursor, - verified : bool - ): - """ + + # Retrieve label_info + inspection_metadata = data_inspection.build_inspection_export( + cursor, inspection_id + ) + inspection_controller = InspectionController(inspection_model=inspection_metadata) + return inspection_controller + +def get_user_analysis_by_verified( + cursor:Cursor, + user_id:UUID, + verified : bool +): + """ This function fetch all the inspection of a user Parameters: - cursor: The cursor object to interact with the database. @@ -156,59 +221,7 @@ def get_user_analysis_by_verified( company_info.company_name ] """ - if not user.is_a_user_id(cursor=cursor, user_id=self.model.user_id): - raise user.UserNotFoundError(f"User not found based on the given id: {self.model.user_id}") - return inspection.get_all_user_inspection_filter_verified(cursor, self.model.user_id, verified) - - def delete_inspection( - self, - cursor:Cursor, - container_id: UUID, - )-> data_inspection.DBInspection: - """ - Delete an existing inspection record and its associated picture set from the database. - - Parameters: - - cursor (Cursor): Database cursor for executing queries. - - inspection_id (str | UUID): UUID of the inspection to delete. - - user_id (str | UUID): UUID of the user performing the deletion. - - Returns: - - data_inspection.Inspection: The deleted inspection data from the database. - - """ - - # Delete the inspection and get the returned data - deleted_inspection = inspection.delete_inspection(cursor, self.model.id, self.model.user_id) - deleted_inspection = data_inspection.DBInspection.model_validate(deleted_inspection) - container_controller:ContainerController = datastore.get_container_controller(cursor,container_id) - - container_controller.delete_folder_permanently(cursor,self.model.user_id,self.model.folder_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}") + return inspection.get_all_user_inspection_filter_verified(cursor, user_id, verified) - return deleted_inspection - - def get_inspection_image_location_data( - self, - cursor: Cursor, - )->tuple[UUID,UUID]: - """ - Retrieve the relevant information regarding where the Inspection Images are stored - This is usefull to perform Container_Controller.get_folder_pictures() - - Parameters: - - cursor (Cursor): Database cursor for executing queries. - - Returns: - - tuple (container_id: UUID, folder: UUID): The id necessary to locate the images - """ - if not inspection.is_a_inspection_id(cursor=cursor, inspection_id=self.model.id): - raise inspection.InspectionNotFoundError( - f"Inspection not found based on the given id: {self.model.id}" - ) - picture_set_id = inspection.get_inspection_fk(cursor,self.model.id)[2] - - container_id = picture.get_picture_set_container_id( - cursor=cursor, - picture_set_id=picture_set_id - ) - return (container_id,picture_set_id) diff --git a/fertiscan/db/metadata/inspection/__init__.py b/fertiscan/db/metadata/inspection/__init__.py index 3b6dbccd..5384c68f 100644 --- a/fertiscan/db/metadata/inspection/__init__.py +++ b/fertiscan/db/metadata/inspection/__init__.py @@ -6,6 +6,7 @@ from datetime import datetime from typing import List, Optional +from uuid import UUID from pydantic import UUID4, BaseModel, ValidationError, model_validator @@ -145,9 +146,12 @@ class Inspection(ValidatedModel): instructions: SubLabel guaranteed_analysis: GuaranteedAnalysis ingredients: ValuesObjects + folder_id: UUID4 + container_id: UUID4 + -def build_inspection_import(analysis_form: dict, user_id) -> str: +def build_inspection_import(analysis_form: dict, user_id:UUID,folder_id:UUID,container_id:UUID) -> Inspection: """ This funtion build an inspection json object from the pipeline of digitalization analysis. This serves as the metadata for the inspection object in the database. @@ -349,9 +353,10 @@ def build_inspection_import(analysis_form: dict, user_id) -> str: guaranteed_analysis=guaranteed, registration_numbers=reg_numbers, ingredients=ingredients, + folder_id=folder_id, + container_id=container_id ) - Inspection(**inspection_formatted.model_dump()) - return inspection_formatted.model_dump_json() + return inspection_formatted except MetadataError: raise except ValidationError as e: @@ -360,7 +365,7 @@ def build_inspection_import(analysis_form: dict, user_id) -> str: raise BuildInspectionImportError(f"Unexpected error: {e}") from e -def build_inspection_export(cursor, inspection_id) -> str: +def build_inspection_export(cursor, inspection_id) -> Inspection: """ This funtion build an inspection json object from the database. """ @@ -429,8 +434,7 @@ def build_inspection_export(cursor, inspection_id) -> str: verified=db_inspection.verified, ingredients=ingredients, ) - - return inspection_formatted.model_dump_json() + return inspection_formatted except QueryError as e: raise BuildInspectionExportError(f"Error fetching data: {e}") from e except Exception as e: diff --git a/fertiscan/doc/inspection-view.md b/fertiscan/doc/inspection-view.md index f4270994..97033595 100644 --- a/fertiscan/doc/inspection-view.md +++ b/fertiscan/doc/inspection-view.md @@ -8,6 +8,8 @@ object representing it. ```JSON { "inspection_id": "inspection uuid", + "container_id": "container_uuid", + "folder_id": "folder_uuid", "owner_id": "Organization uuid", "organizations": [ { diff --git a/tests/fertiscan/test_datastore.py b/tests/fertiscan/test_datastore.py index bedf00c2..896b1714 100644 --- a/tests/fertiscan/test_datastore.py +++ b/tests/fertiscan/test_datastore.py @@ -8,6 +8,7 @@ import json import os import unittest +from uuid import UUID from PIL import Image @@ -86,21 +87,32 @@ def setUp(self): db.create_search_path(self.con, self.cursor, DB_SCHEMA) self.user_email = "testesss@email" self.tier = "test-user" + self.user_role = datastore.Role.INSPECTOR self.user = asyncio.run( datastore.new_user( - self.cursor, self.user_email, BLOB_CONNECTION_STRING, self.tier + self.cursor, self.user_email, BLOB_CONNECTION_STRING, self.tier, self.user_role ) ) - self.container_client = asyncio.run( - datastore.get_user_container_client( - self.user.id, - BLOB_CONNECTION_STRING, - BLOB_ACCOUNT, - BLOB_KEY, - self.tier, + self.container_model = next(iter(self.user.model.containers.values())) + self.container_controller = asyncio.run( + datastore.get_container_controller( + self.cursor, + self.container_model.id, + BLOB_CONNECTION_STRING,None ) ) - + self.container_client = self.container_controller.container_client + # Assure the user Storage space exists + self.assertTrue(self.container_client.exists()) + self.folder_id:UUID = self.container_controller.create_folder( + cursor=self.cursor, + performed_by=self.user.id, + folder_name="test-folder-fertiscan", + nb_pictures=0, + parent_folder_id=None + ) + + self.image = Image.new("RGB", (1980, 1080), "blue") self.image_byte_array = io.BytesIO() self.image.save(self.image_byte_array, format="TIFF") @@ -159,27 +171,29 @@ def tearDown(self): except Exception as e: print(e) - def test_register_analysis(self): - # print(self.user.id) - self.assertTrue(self.container_client.exists()) - analysis = asyncio.run( - fertiscan.register_analysis( + def test_new_inspection(self): + inspection_controller = asyncio.run( + fertiscan.new_inspection( self.cursor, - self.container_client, self.user.id, - [self.pic_encoded, self.pic_encoded], self.analysis_json, - ) + self.container_controller.id, + folder_id=self.folder_id) ) - self.assertIsNotNone(analysis) - self.assertTrue(validator.is_valid_uuid(analysis["inspection_id"])) - inspection_id = analysis["inspection_id"] - - self.assertTrue(validator.is_valid_uuid(analysis["product"]["label_id"])) - label_id = analysis["product"]["label_id"] + self.assertIsNotNone(inspection_controller) + self.assertIsInstance(inspection_controller,fertiscan.InspectionController) + analysis = inspection_controller.model + self.assertTrue(validator.is_valid_uuid(inspection_controller.id)) + self.assertIsInstance(analysis,fertiscan.data_inspection.Inspection) + analysis = fertiscan.data_inspection.Inspection.model_validate(analysis) + inspection_id = analysis.inspection_id + self.assertEqual(inspection_id,inspection_controller.id) + + self.assertTrue(validator.is_valid_uuid(analysis.product.label_id)) + label_id = analysis.product.label_id metrics = metric.get_metric_by_label( - self.cursor, str(analysis["product"]["label_id"]) + self.cursor, str(analysis.product.label_id) ) self.assertIsNotNone(metrics) @@ -187,11 +201,8 @@ def test_register_analysis(self): len(metrics), 4 ) # There are 4 metrics in the analysis_json (1 volume, 1 density, 2 weight ) - ingredients = ingredient.get_ingredient_json( - self.cursor, str(analysis["product"]["label_id"]) - ) - self.assertIsNotNone(ingredients) - # print(ingredients) + ingredients = ingredient.get_ingredient_json(self.cursor, str(analysis.product.label_id)) + print(ingredients) # specifications = specification.get_all_specifications( # cursor=self.cursor, label_id=str(analysis["product"]["label_id"]) @@ -210,7 +221,7 @@ def test_register_analysis(self): # len(nutrients_data), self.nb_micronutrients # ) # There are 2 nutrients in the analysis_json sub_labels = sub_label.get_sub_label_json( - self.cursor, str(analysis["product"]["label_id"]) + self.cursor, str(analysis.product.label_id) ) self.assertIsNotNone(sub_labels) @@ -238,12 +249,11 @@ def test_register_analysis(self): label_dimension = label.get_label_dimension(self.cursor, label_id) - self.assertEqual( - str(label_dimension[1][0]), str(analysis["organizations"][0]["id"]) - ) - self.assertEqual( - str(label_dimension[1][1]), str(analysis["organizations"][1]["id"]) - ) + company_info_id = str(label_dimension[1]) + manufacturer_info_id = str(label_dimension[3]) + + self.assertEqual(str(company_info_id), analysis.company.id) + self.assertEqual(str(manufacturer_info_id), analysis.manufacturer.id) self.assertEqual(len(label_dimension[2]), self.nb_instructions)