Skip to content

Commit dca5dd1

Browse files
committed
Merge branch 'HAN-50' into HAN-55
2 parents 34907a0 + 6a238aa commit dca5dd1

16 files changed

+309
-113
lines changed

.env.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ DATABASE_URL=
1313

1414
# RabbitMQ
1515
CLOUDAMQP_URL=
16-
QUEUE_NAME=
16+
QUEUE_NAME=

app/.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"python.analysis.autoImportCompletions": true,
3+
"python.analysis.typeCheckingMode": "basic"
4+
}

app/controller/device_plant_controller.py

Lines changed: 123 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,93 +2,175 @@
22
from fastapi import Response, Request, status, HTTPException
33
from database.models.device_plant import DevicePlant
44
from schemas.device_plant import (
5+
DevicePlantCreateSchema,
56
DevicePlantPartialUpdateSchema,
6-
DevicePlantSchema,
77
DevicePlantUpdateSchema,
88
)
99
import logging
10-
from psycopg2.errors import UniqueViolation
1110
from sqlalchemy.exc import PendingRollbackError, IntegrityError, NoResultFound
1211
from fastapi.responses import JSONResponse
12+
from service.plant_service import PlantService
1313

1414
logger = logging.getLogger("app")
1515
logger.setLevel("DEBUG")
1616

1717

18-
def withSQLExceptionsHandle(func):
19-
def handleSQLException(*args, **kwargs):
20-
try:
21-
return func(*args, **kwargs)
22-
except IntegrityError as err:
23-
if isinstance(err.orig, UniqueViolation):
24-
parsed_error = err.orig.pgerror.split("\n")
25-
raise HTTPException(
26-
status_code=status.HTTP_400_BAD_REQUEST,
27-
detail={"error": parsed_error[0], "detail": parsed_error[1]},
28-
)
29-
30-
raise HTTPException(
31-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=format(err)
32-
)
18+
def handle_common_errors(err):
3319

34-
except PendingRollbackError as err:
35-
logger.warning(format(err))
36-
raise HTTPException(
37-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=format(err)
38-
)
20+
if isinstance(err, IntegrityError):
21+
parsed_error = err.orig.pgerror.split("\n")[1]
22+
return JSONResponse(
23+
status_code=status.HTTP_400_BAD_REQUEST,
24+
content=parsed_error,
25+
)
3926

40-
except NoResultFound as err:
41-
raise HTTPException(
42-
status_code=status.HTTP_400_BAD_REQUEST, detail=format(err)
43-
)
27+
if isinstance(err, PendingRollbackError):
28+
logger.warning(format(err))
29+
raise HTTPException(
30+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
31+
detail=format(err)
32+
)
33+
34+
if isinstance(err, NoResultFound):
35+
raise HTTPException(
36+
status_code=status.HTTP_400_BAD_REQUEST, detail=format(err)
37+
)
38+
39+
logger.error(format(err))
40+
raise HTTPException(
41+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=format(err)
42+
)
43+
44+
45+
def withSQLExceptionsHandle(async_mode: bool):
46+
47+
def decorator(func):
48+
async def handleAsyncSQLException(*args, **kwargs):
49+
try:
50+
return await func(*args, **kwargs)
51+
except Exception as err:
52+
return handle_common_errors(err)
53+
54+
def handleSyncSQLException(*args, **kwargs):
55+
try:
56+
return func(*args, **kwargs)
57+
except Exception as err:
58+
return handle_common_errors(err)
4459

45-
return handleSQLException
60+
return (
61+
handleAsyncSQLException if async_mode else handleSyncSQLException
62+
)
63+
64+
return decorator
4665

4766

48-
@withSQLExceptionsHandle
49-
def create_device_plant_relation(req: Request, device_plant: DevicePlantSchema):
67+
@withSQLExceptionsHandle(async_mode=True)
68+
async def create_device_plant_relation(
69+
req: Request, device_plant: DevicePlantCreateSchema
70+
):
5071
try:
51-
req.app.database.add(DevicePlant.from_pydantic(device_plant))
52-
return req.app.database.find_by_device_id(device_plant.id_device)
72+
plant = await PlantService.get_plant(device_plant.id_plant)
73+
if not plant:
74+
return JSONResponse(
75+
status_code=status.HTTP_400_BAD_REQUEST,
76+
content={
77+
"plant_id": (
78+
"Could not found any plant "
79+
f"with id {device_plant.id_plant}"
80+
)
81+
},
82+
)
83+
84+
plant_type = await PlantService.get_plant_type(plant.scientific_name)
85+
if not plant_type:
86+
return JSONResponse(
87+
status_code=status.HTTP_400_BAD_REQUEST,
88+
content={
89+
"scientific_name": (
90+
"Could not found any plant type "
91+
f"with scientific name {plant.scientific_name}"
92+
)
93+
},
94+
)
95+
96+
device_plant = DevicePlant(
97+
id_device=device_plant.id_device,
98+
id_plant=device_plant.id_plant,
99+
plant_type=plant_type.id,
100+
id_user=plant.id_user,
101+
) # type: ignore
102+
req.app.database.add(device_plant)
103+
return device_plant
104+
53105
except Exception as err:
54106
req.app.database.rollback()
55107
raise err
56108

57109

58-
@withSQLExceptionsHandle
59-
def update_device_plant(
110+
@withSQLExceptionsHandle(async_mode=True)
111+
async def update_device_plant(
60112
req: Request,
61113
id_device: str,
62114
device_plant_update_set: Union[
63115
DevicePlantUpdateSchema, DevicePlantPartialUpdateSchema
64116
],
65117
):
66118
try:
119+
if not device_plant_update_set.id_plant:
120+
return req.app.database.find_by_device_id(id_device)
121+
122+
plant = await PlantService.get_plant(device_plant_update_set.id_plant)
123+
if not plant:
124+
return JSONResponse(
125+
status_code=status.HTTP_400_BAD_REQUEST,
126+
content={
127+
"plant_id": (
128+
"Could not found any plant with "
129+
f"id {device_plant_update_set.id_plant}"
130+
)
131+
},
132+
)
133+
134+
plant_type = await PlantService.get_plant_type(plant.scientific_name)
135+
if not plant_type:
136+
return JSONResponse(
137+
status_code=status.HTTP_400_BAD_REQUEST,
138+
content={
139+
"scientific_name": (
140+
"Could not found any plant type "
141+
f"with scientific name {plant.scientific_name}"
142+
)
143+
},
144+
)
145+
67146
req.app.database.update_device_plant(
68147
id_device,
69-
device_plant_update_set.id_plant,
70-
device_plant_update_set.plant_type,
71-
device_plant_update_set.id_user,
148+
plant.id,
149+
plant_type.id,
150+
plant.id_user,
72151
)
73152
return req.app.database.find_by_device_id(id_device)
74153
except Exception as err:
75154
req.app.database.rollback()
76155
raise err
77156

78157

79-
@withSQLExceptionsHandle
80-
def get_device_plant_relation(req: Request, id_plant: str):
158+
@withSQLExceptionsHandle(async_mode=False)
159+
def get_device_plant_relation(req: Request, id_plant: int):
81160
return req.app.database.find_by_plant_id(id_plant)
82161

83162

84-
@withSQLExceptionsHandle
163+
@withSQLExceptionsHandle(async_mode=False)
85164
def get_all_device_plant_relations(req: Request, limit: int):
86165
return req.app.database.find_all(limit)
87166

88167

89-
@withSQLExceptionsHandle
168+
@withSQLExceptionsHandle(async_mode=False)
90169
def delete_device_plant_relation(
91-
req: Request, response: Response, type_id: Literal["id_device", "id_plant"], id: str
170+
req: Request,
171+
response: Response,
172+
type_id: Literal["id_device", "id_plant"],
173+
id: str
92174
):
93175
result_rowcount = 0
94176
if type_id == "id_device":
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from controller.device_plant_controller import withSQLExceptionsHandle
2-
from fastapi import Request
3-
4-
5-
@withSQLExceptionsHandle
6-
def last_measurement_made_by_plant(req: Request, id_plant: int):
7-
try:
8-
return req.app.database.get_last_measurement(id_plant)
9-
except Exception as err:
10-
req.app.database.rollback()
11-
raise err
1+
from controller.device_plant_controller import withSQLExceptionsHandle
2+
from fastapi import Request
3+
4+
5+
@withSQLExceptionsHandle(async_mode=False)
6+
def last_measurement_made_by_plant(req: Request, id_plant: int):
7+
try:
8+
return req.app.database.get_last_measurement(id_plant)
9+
except Exception as err:
10+
req.app.database.rollback()
11+
raise err

app/database/database.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
from sqlalchemy.orm import Session
33
from dotenv import load_dotenv
44
from os import environ
5-
from typing import Optional, Union
5+
from typing import Optional, Sequence, Union
66

77
from database.models.device_plant import DevicePlant
88
from database.models.measurement import Measurement
9-
from typing import List
109

1110
load_dotenv()
1211

@@ -28,7 +27,7 @@ def rollback(self):
2827
self.session.rollback()
2928

3029
def clean_table(self, table: Union[DevicePlant, Measurement]):
31-
query = delete(table)
30+
query = delete(table) # type: ignore
3231
self.session.execute(query)
3332
self.session.commit()
3433

@@ -46,9 +45,9 @@ def find_by_plant_id(self, id_plant: str) -> DevicePlant:
4645
result = self.session.scalars(query).one()
4746
return result
4847

49-
def find_all(self, limit: int) -> List[DevicePlant]:
48+
def find_all(self, limit: int) -> Sequence[DevicePlant]:
5049
query = select(DevicePlant).limit(limit)
51-
result = self.session.scalars(query)
50+
result = self.session.scalars(query).all()
5251
return result
5352

5453
def update_device_plant(self,
@@ -72,9 +71,7 @@ def get_last_measurement(self, id_plant: int) -> Measurement:
7271
query = select(Measurement).where(
7372
Measurement.id_plant == id_plant
7473
).order_by(Measurement.id.desc()).limit(1)
75-
7674
result: Measurement = self.session.scalars(query).one()
77-
7875
return result
7976

8077
def delete_by_field(self, field: str, value: str) -> int:

app/database/models/measurement.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from typing import Optional
44
from database.models.base import Base
55
from os import environ
6-
6+
SCHEMA = environ.get("POSTGRES_SCHEMA", "measurements")
77

88
class Measurement(Base):
99
__tablename__ = "measurements"
10-
__table_args__ = {'schema': environ.get("POSTGRES_SCHEMA", "measurements")}
10+
__table_args__ = {'schema': SCHEMA}
1111

1212
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
1313
id_plant: Mapped[int] = mapped_column(Integer, unique=True)
@@ -19,10 +19,13 @@ class Measurement(Base):
1919
watering: Mapped[Optional[int]] = mapped_column(SmallInteger)
2020

2121
__table_args__ = (
22-
CheckConstraint('humidity >= 0 AND humidity <= 100', name='check_humidity'),
22+
CheckConstraint(
23+
'humidity >= 0 AND humidity <= 100',
24+
name='check_humidity'
25+
),
2326
CheckConstraint('humidity >= 0 ', name='check_light'),
2427
CheckConstraint('humidity >= 0 AND humidity <= 100', name='check_watering'),
25-
{'schema': environ.get("POSTGRES_SCHEMA", "measurements")}
28+
{'schema': SCHEMA}
2629
)
2730

2831
def __repr__(self):

app/exceptions/empty_package.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
class EmptyPackageError(Exception):
2-
def __init__(self, empty_folds: list):
3-
self.empty_folds = empty_folds
4-
super().__init__(f"Package received with empty folds: {self.empty_folds}.")
1+
class EmptyPackageError(Exception):
2+
def __init__(self, empty_folds: list):
3+
self.empty_folds = empty_folds
4+
super().__init__((
5+
"Package received with empty "
6+
f"folds: {self.empty_folds}."
7+
))

0 commit comments

Comments
 (0)