diff --git a/app/.vscode/settings.json b/app/.vscode/settings.json new file mode 100644 index 0000000..62f621e --- /dev/null +++ b/app/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.analysis.autoImportCompletions": true, + "python.analysis.typeCheckingMode": "basic" +} \ No newline at end of file diff --git a/app/controller/device_plant_controller.py b/app/controller/device_plant_controller.py index cb91a3f..038785c 100644 --- a/app/controller/device_plant_controller.py +++ b/app/controller/device_plant_controller.py @@ -4,11 +4,9 @@ from schemas.device_plant import ( DevicePlantCreateSchema, DevicePlantPartialUpdateSchema, - DevicePlantSchema, DevicePlantUpdateSchema, ) import logging -from psycopg2.errors import UniqueViolation from sqlalchemy.exc import PendingRollbackError, IntegrityError, NoResultFound from fastapi.responses import JSONResponse from service.plant_service import PlantService @@ -16,6 +14,7 @@ logger = logging.getLogger("app") logger.setLevel("DEBUG") + def handle_common_errors(err): if isinstance(err, IntegrityError): @@ -24,23 +23,25 @@ def handle_common_errors(err): status_code=status.HTTP_400_BAD_REQUEST, content=parsed_error, ) - + if isinstance(err, PendingRollbackError): logger.warning(format(err)) raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=format(err) + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=format(err) ) - + if isinstance(err, NoResultFound): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=format(err) ) - + logger.error(format(err)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=format(err) ) + def withSQLExceptionsHandle(async_mode: bool): def decorator(func): @@ -56,37 +57,51 @@ def handleSyncSQLException(*args, **kwargs): except Exception as err: return handle_common_errors(err) - return handleAsyncSQLException if async_mode else handleSyncSQLException - + return ( + handleAsyncSQLException if async_mode else handleSyncSQLException + ) + return decorator @withSQLExceptionsHandle(async_mode=True) -async def create_device_plant_relation(req: Request, device_plant: DevicePlantCreateSchema): +async def create_device_plant_relation( + req: Request, device_plant: DevicePlantCreateSchema +): try: plant = await PlantService.get_plant(device_plant.id_plant) if not plant: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, - content={"plant_id": f"Could not found any plant with id {device_plant.id_plant}"}, + content={ + "plant_id": ( + "Could not found any plant " + f"with id {device_plant.id_plant}" + ) + }, ) - + plant_type = await PlantService.get_plant_type(plant.scientific_name) if not plant_type: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, - content={"scientific_name": f"Could not found any plant type with scientific name {plant.scientific_name}"}, + content={ + "scientific_name": ( + "Could not found any plant type " + f"with scientific name {plant.scientific_name}" + ) + }, ) - + device_plant = DevicePlant( id_device=device_plant.id_device, id_plant=device_plant.id_plant, plant_type=plant_type.id, id_user=plant.id_user, - ) + ) # type: ignore req.app.database.add(device_plant) return device_plant - + except Exception as err: req.app.database.rollback() raise err @@ -108,14 +123,24 @@ async def update_device_plant( if not plant: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, - content={"plant_id": f"Could not found any plant with id {device_plant_update_set.id_plant}"}, + content={ + "plant_id": ( + "Could not found any plant with " + f"id {device_plant_update_set.id_plant}" + ) + }, ) - + plant_type = await PlantService.get_plant_type(plant.scientific_name) if not plant_type: return JSONResponse( status_code=status.HTTP_400_BAD_REQUEST, - content={"scientific_name": f"Could not found any plant type with scientific name {plant.scientific_name}"}, + content={ + "scientific_name": ( + "Could not found any plant type " + f"with scientific name {plant.scientific_name}" + ) + }, ) req.app.database.update_device_plant( @@ -131,7 +156,7 @@ async def update_device_plant( @withSQLExceptionsHandle(async_mode=False) -def get_device_plant_relation(req: Request, id_plant: str): +def get_device_plant_relation(req: Request, id_plant: int): return req.app.database.find_by_plant_id(id_plant) @@ -142,7 +167,10 @@ def get_all_device_plant_relations(req: Request, limit: int): @withSQLExceptionsHandle(async_mode=False) def delete_device_plant_relation( - req: Request, response: Response, type_id: Literal["id_device", "id_plant"], id: str + req: Request, + response: Response, + type_id: Literal["id_device", "id_plant"], + id: str ): result_rowcount = 0 if type_id == "id_device": diff --git a/app/controller/measurement_controller.py b/app/controller/measurement_controller.py index 2081b56..76c5cc8 100644 --- a/app/controller/measurement_controller.py +++ b/app/controller/measurement_controller.py @@ -2,7 +2,7 @@ from fastapi import Request -@withSQLExceptionsHandle +@withSQLExceptionsHandle(async_mode=False) def last_measurement_made_by_plant(req: Request, id_plant: int): try: return req.app.database.get_last_measurement(id_plant) diff --git a/app/database/database.py b/app/database/database.py index 97cb22b..bd8fab4 100644 --- a/app/database/database.py +++ b/app/database/database.py @@ -2,11 +2,10 @@ from sqlalchemy.orm import Session from dotenv import load_dotenv from os import environ -from typing import Optional, Union +from typing import Optional, Sequence, Union from database.models.device_plant import DevicePlant from database.models.measurement import Measurement -from typing import List load_dotenv() @@ -18,7 +17,7 @@ class SQLAlchemyClient(): username=environ["POSTGRES_USER"], password=environ["POSTGRES_PASSWORD"], host=environ["POSTGRES_HOST"], - port=environ["POSTGRES_PORT"] + port=int(environ["POSTGRES_PORT"]) ) engine = create_engine(db_url) @@ -35,7 +34,7 @@ def rollback(self): self.session.rollback() def clean_table(self, table: Union[DevicePlant, Measurement]): - query = delete(table) + query = delete(table) # type: ignore self.session.execute(query) self.session.commit() @@ -53,9 +52,9 @@ def find_by_plant_id(self, id_plant: str) -> DevicePlant: result = self.session.scalars(query).one() return result - def find_all(self, limit: int) -> List[DevicePlant]: + def find_all(self, limit: int) -> Sequence[DevicePlant]: query = select(DevicePlant).limit(limit) - result = self.session.scalars(query) + result = self.session.scalars(query).all() return result def update_device_plant(self, @@ -79,9 +78,7 @@ def get_last_measurement(self, id_plant: int) -> Measurement: query = select(Measurement).where( Measurement.id_plant == id_plant ).order_by(Measurement.id.desc()).limit(1) - result: Measurement = self.session.scalars(query).one() - return result def delete_by_field(self, field: str, value: str) -> int: diff --git a/app/database/models/device_plant.py b/app/database/models/device_plant.py index 441ee33..b57e216 100644 --- a/app/database/models/device_plant.py +++ b/app/database/models/device_plant.py @@ -13,7 +13,6 @@ class DevicePlant(Base): plant_type: Mapped[int] = mapped_column(SmallInteger) id_user: Mapped[int] = mapped_column(Integer) - def __repr__(self) -> str: return (f"DevicePlant(id_device={self.id_device}, " f"id_plant={self.id_plant}, " diff --git a/app/database/models/measurement.py b/app/database/models/measurement.py index 335577a..397c156 100644 --- a/app/database/models/measurement.py +++ b/app/database/models/measurement.py @@ -18,9 +18,15 @@ class Measurement(Base): watering: Mapped[Optional[int]] = mapped_column(SmallInteger) __table_args__ = ( - CheckConstraint('humidity >= 0 AND humidity <= 100', name='check_humidity'), + CheckConstraint( + 'humidity >= 0 AND humidity <= 100', + name='check_humidity' + ), CheckConstraint('humidity >= 0 ', name='check_light'), - CheckConstraint('humidity >= 0 AND humidity <= 100', name='check_watering'), + CheckConstraint( + 'humidity >= 0 AND humidity <= 100', + name='check_watering' + ), {'schema': 'dev'} ) diff --git a/app/exceptions/empty_package.py b/app/exceptions/empty_package.py index e4e579d..3f4ba95 100644 --- a/app/exceptions/empty_package.py +++ b/app/exceptions/empty_package.py @@ -1,4 +1,7 @@ class EmptyPackageError(Exception): def __init__(self, empty_folds: list): self.empty_folds = empty_folds - super().__init__(f"Package received with empty folds: {self.empty_folds}.") + super().__init__(( + "Package received with empty " + f"folds: {self.empty_folds}." + )) diff --git a/app/router/device_plant_router.py b/app/router/device_plant_router.py index 612ee7e..858d2a8 100644 --- a/app/router/device_plant_router.py +++ b/app/router/device_plant_router.py @@ -17,8 +17,10 @@ status_code=status.HTTP_201_CREATED, response_model=DevicePlantSchema ) -async def create_device_plant_relation(req: Request, - device_plant: DevicePlantCreateSchema = Body(...)): +async def create_device_plant_relation( + req: Request, + device_plant: DevicePlantCreateSchema = Body(...) +): return await controller.create_device_plant_relation(req, device_plant) @@ -27,11 +29,15 @@ async def create_device_plant_relation(req: Request, status_code=status.HTTP_200_OK, response_model=DevicePlantSchema ) -async def update_fields_in_device_plant(id_device: str, - req: Request, - device_plant_update_set: - DevicePlantPartialUpdateSchema = Body(...)): - return await controller.update_device_plant(req, id_device, device_plant_update_set) +async def update_fields_in_device_plant( + id_device: str, + req: Request, + device_plant_update_set: + DevicePlantPartialUpdateSchema = Body(...) +): + return await controller.update_device_plant( + req, id_device, device_plant_update_set + ) @device_plant.put( @@ -39,9 +45,11 @@ async def update_fields_in_device_plant(id_device: str, status_code=status.HTTP_200_OK, response_model=DevicePlantSchema ) -async def update_all_in_device_plant(id_device: str, - req: Request, - device_plant: DevicePlantUpdateSchema = Body(...)): +async def update_all_in_device_plant( + id_device: str, + req: Request, + device_plant: DevicePlantUpdateSchema = Body(...) +): return await controller.update_device_plant(req, id_device, device_plant) @@ -50,9 +58,11 @@ async def update_all_in_device_plant(id_device: str, status_code=status.HTTP_200_OK, response_model=List[DevicePlantSchema] ) -async def get_device_plant(req: Request, - id_plant: int = Query(None), - limit: int = Query(10)): +async def get_device_plant( + req: Request, + id_plant: int = Query(None), + limit: int = Query(10) +): if id_plant is None: return controller.get_all_device_plant_relations(req, limit) return [controller.get_device_plant_relation(req, id_plant)] @@ -62,7 +72,9 @@ async def get_device_plant(req: Request, "/{id}", status_code=status.HTTP_200_OK ) -async def delete_device_plant_relation(response: Response, req: Request, - type_id: Literal["id_device", "id_plant"], - id: str): +async def delete_device_plant_relation( + response: Response, req: Request, + type_id: Literal["id_device", "id_plant"], + id: str +): return controller.delete_device_plant_relation(req, response, type_id, id) diff --git a/app/schemas/device_plant.py b/app/schemas/device_plant.py index 41fcb7e..735cacd 100644 --- a/app/schemas/device_plant.py +++ b/app/schemas/device_plant.py @@ -1,6 +1,7 @@ from pydantic import BaseModel, Field from typing import Optional + class DevicePlantCreateSchema(BaseModel): id_device: str = Field(...) id_plant: int = Field(..., gt=0) @@ -13,6 +14,7 @@ class Config: } } + class DevicePlantSchema(DevicePlantCreateSchema): plant_type: int = Field(..., gt=0) id_user: int = Field(..., gt=0) diff --git a/app/schemas/plant.py b/app/schemas/plant.py index cca6c44..5abce74 100644 --- a/app/schemas/plant.py +++ b/app/schemas/plant.py @@ -1,9 +1,8 @@ from pydantic import BaseModel, Field -from typing import Optional class PlantSchema(BaseModel): id: int = Field(...) name: str = Field(...) scientific_name: str = Field(...) - id_user: int = Field(...) \ No newline at end of file + id_user: int = Field(...) diff --git a/app/schemas/plant_type.py b/app/schemas/plant_type.py index 6a454cb..0e24b1a 100644 --- a/app/schemas/plant_type.py +++ b/app/schemas/plant_type.py @@ -1,9 +1,10 @@ from pydantic import BaseModel, Field + class PlantTypeSchema(BaseModel): botanical_name: str = Field(..., max_length=70) id: int = Field(..., gt=0) common_name: str = Field(..., max_length=70) description: str = Field(..., max_length=1000) cares: str = Field(..., max_length=1000) - photo_link: str = Field(..., max_length=160) \ No newline at end of file + photo_link: str = Field(..., max_length=160) diff --git a/app/service/calculator_service.py b/app/service/calculator_service.py index 8470478..1a8e29f 100644 --- a/app/service/calculator_service.py +++ b/app/service/calculator_service.py @@ -6,7 +6,9 @@ class CalculatorService: def __init__(self): - engine = create_engine(os.environ.get('DATABASE_URL'), echo=True) + engine = create_engine( + os.environ.get('DATABASE_URL'), echo=True # type: ignore + ) self.session = Session(engine) def add_number(self, number): diff --git a/app/service/plant_service.py b/app/service/plant_service.py index 8da825d..0798270 100644 --- a/app/service/plant_service.py +++ b/app/service/plant_service.py @@ -19,10 +19,12 @@ class PlantService(): @staticmethod - async def get_plant(plant_id: str) -> Optional[PlantSchema]: + async def get_plant(plant_id: int) -> Optional[PlantSchema]: try: async with AsyncClient() as client: - response = await client.get(PLANT_SERVICE_URL + f"/plants/{plant_id}") + response = await client.get( + PLANT_SERVICE_URL + f"/plants/{plant_id}" + ) if response.status_code == codes.OK: return PlantSchema(**response.json()) elif response.status_code == codes.NOT_FOUND: @@ -43,7 +45,9 @@ async def get_plant(plant_id: str) -> Optional[PlantSchema]: async def get_plant_type(botanical_name: str) -> Optional[PlantTypeSchema]: try: async with AsyncClient() as client: - response = await client.get(PLANT_SERVICE_URL + f"/plant-type/{botanical_name}") + response = await client.get( + PLANT_SERVICE_URL + f"/plant-type/{botanical_name}" + ) if response.status_code == codes.OK: return PlantTypeSchema(**response.json()) elif response.status_code == codes.NOT_FOUND: diff --git a/app/service/rabbitmq/consumer.py b/app/service/rabbitmq/consumer.py index 506fd2a..3c6ceed 100644 --- a/app/service/rabbitmq/consumer.py +++ b/app/service/rabbitmq/consumer.py @@ -24,7 +24,7 @@ logging.getLogger("pika").setLevel(logging.WARNING) dbUrl = os.environ.get("DATABASE_URL") -engine = create_engine(dbUrl, echo=True, future=True) +engine = create_engine(dbUrl, echo=True, future=True) # type: ignore session = Session(engine) @@ -46,7 +46,9 @@ def obtain_device_plant(self, measurement_from_rabbit): return dp except Exception as err: logger.error(f"{err} - {type(err)}") - raise RowNotFoundError(measurement_from_rabbit.id_device, "DEVICE_PLANT") + raise RowNotFoundError( + measurement_from_rabbit.id_device, "DEVICE_PLANT" + ) def check_package(self, measurement_from_rabbit): logger.info("TO DO - Step #2 from Ticket HAN-14") @@ -84,7 +86,9 @@ def save_measurement(self, measurement_from_rabbit, device_plant): except Exception as err: logger.error(f"{err} - {type(err)}") self.__sqlAlchemyClient.rollback() - raise InvalidInsertionError(measurement_from_rabbit, "MEAUSUREMENT") + raise InvalidInsertionError( + measurement_from_rabbit, "MEAUSUREMENT" + ) def __callback(self, body): device_plant = None @@ -114,7 +118,9 @@ def __callback(self, body): logger.warn(LoggerMessages.EMPTY_PACKAGE_RECEIVED) logger.debug(LoggerMessages.ERROR_DETAILS.format(err, body)) - self.send_notification(device_plant.id_user, measurement, err, body) + self.send_notification( + device_plant.id_user, measurement, err, body + ) measurement = None # For not saving the measurement. except DeviatedParametersError as err: