Skip to content

Commit dc52eb3

Browse files
authored
Merge pull request #3 from Hanagotchi/HAN-22/postgres-config-and-service
HAN-22: postgres config and service
2 parents 7b84ca4 + 9d27709 commit dc52eb3

File tree

11 files changed

+220
-24
lines changed

11 files changed

+220
-24
lines changed

README.md

88 Bytes

.env structure

POSTGRES_USER: username to access the database (pick anyone. i.e: testuser) POSTGRES_PASSWORD: password to access the database (pick anyone. i.e: test1234) POSTGRES_DB: "measurements" POSTGRES_HOST: localhostthe name of the postgres container (in this case: db) POSTGRES_PORT: 5432

Docker

Build container

$ docker-compose build

app/database/database.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from sqlalchemy import create_engine, select, delete, engine
2+
from sqlalchemy.orm import Session
3+
from dotenv import load_dotenv
4+
from os import environ
5+
from typing import Optional, Union
6+
7+
from app.database.models.DevicePlant import DevicePlant
8+
from app.database.models.Measurement import Measurement
9+
10+
load_dotenv()
11+
12+
13+
class SQLAlchemyClient():
14+
15+
db_url = engine.URL.create(
16+
"postgresql",
17+
database=environ["POSTGRES_DB"],
18+
username=environ["POSTGRES_USER"],
19+
password=environ["POSTGRES_PASSWORD"],
20+
host=environ["POSTGRES_HOST"],
21+
port=environ["POSTGRES_PORT"]
22+
)
23+
24+
engine = create_engine(db_url)
25+
26+
def __init__(self):
27+
self.conn = self.engine.connect()
28+
self.session = Session(self.engine)
29+
30+
def shutdown(self):
31+
self.conn.close()
32+
self.session.close()
33+
34+
def rollback(self):
35+
self.session.rollback()
36+
37+
def clean_table(self, table: Union[DevicePlant, Measurement]):
38+
query = delete(table)
39+
self.session.execute(query)
40+
self.session.commit()
41+
42+
def add_new(self, record: Union[DevicePlant, Measurement]):
43+
self.session.add(record)
44+
self.session.commit()
45+
46+
def find_device_plant(self, id_device: str) -> DevicePlant:
47+
query = select(DevicePlant).where(DevicePlant.id_device == id_device)
48+
result = self.session.scalars(query).one()
49+
return result
50+
51+
def update_device_plant(self,
52+
id_device: str,
53+
id_plant: Optional[int],
54+
plant_type: Optional[int],
55+
id_user: Optional[int]):
56+
57+
query = select(DevicePlant).where(DevicePlant.id_device == id_device)
58+
59+
device_plant = self.session.scalars(query).one()
60+
if id_plant:
61+
device_plant.id_plant = id_plant
62+
if plant_type:
63+
device_plant.id_plant = plant_type
64+
if id_user:
65+
device_plant.id_user = id_user
66+
self.session.commit()
67+
68+
def get_last_measurement(self, id_plant: int) -> Measurement:
69+
query = select(Measurement).where(
70+
Measurement.id_plant == id_plant
71+
).order_by(Measurement.id.desc()).limit(1)
72+
73+
result: Measurement = self.session.scalars(query).one()
74+
75+
return result

app/database/models/Base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from sqlalchemy.orm import DeclarativeBase
2+
3+
4+
class Base(DeclarativeBase):
5+
pass

app/database/models/DevicePlant.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from sqlalchemy import Integer, String, SmallInteger
2+
from sqlalchemy.orm import Mapped, mapped_column
3+
from app.database.models.Base import Base
4+
from app.schemas.DevicePlant import DevicePlantSchema
5+
6+
7+
class DevicePlant(Base):
8+
__tablename__ = "device_plant"
9+
__table_args__ = {'schema': 'dev'}
10+
11+
id_device: Mapped[str] = mapped_column(String(32), primary_key=True)
12+
id_plant: Mapped[int] = mapped_column(Integer, unique=True)
13+
plant_type: Mapped[int] = mapped_column(SmallInteger)
14+
id_user: Mapped[int] = mapped_column(Integer)
15+
16+
def __repr__(self) -> str:
17+
return format(
18+
"DevicePlant(id_device={0}, id_plant={1}, plant_type={2}, id_user={3})",
19+
self.id_device,
20+
self.id_plant,
21+
self.plant_type,
22+
self.id_user,
23+
)
24+
25+
@classmethod
26+
def from_pydantic(cls, pydantic_obj: DevicePlantSchema):
27+
return DevicePlant(
28+
id_device=pydantic_obj.id_device,
29+
id_plant=pydantic_obj.id_plant,
30+
plant_type=pydantic_obj.plant_type,
31+
id_user=pydantic_obj.id_user,
32+
)

app/database/models/Measurement.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from sqlalchemy import CheckConstraint, Integer, String, SmallInteger
2+
from sqlalchemy.orm import Mapped, mapped_column
3+
from typing import Optional
4+
from app.database.models.Base import Base
5+
6+
7+
class Measurement(Base):
8+
__tablename__ = "measurements"
9+
__table_args__ = {'schema': 'dev'}
10+
11+
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
12+
id_plant: Mapped[int] = mapped_column(Integer, unique=True)
13+
plant_type: Mapped[int] = mapped_column(SmallInteger)
14+
time_stamp: Mapped[str] = mapped_column(String(50))
15+
temperature: Mapped[Optional[int]] = mapped_column(SmallInteger)
16+
humidity: Mapped[Optional[int]] = mapped_column(SmallInteger)
17+
light: Mapped[Optional[int]] = mapped_column(SmallInteger)
18+
watering: Mapped[Optional[int]] = mapped_column(SmallInteger)
19+
20+
__table_args__ = (
21+
CheckConstraint('humidity >= 0 AND humidity <= 100', name='check_humidity'),
22+
CheckConstraint('humidity >= 0 ', name='check_light'),
23+
CheckConstraint('humidity >= 0 AND humidity <= 100', name='check_watering'),
24+
{'schema': 'dev'}
25+
)
26+
27+
def __repr__(self):
28+
return format(
29+
("Measurement(id={0!r}, id_plant={1!r}, "
30+
"plant_type={2!r}, time_stamp={3!r}, "
31+
"temperature={4!r}, humidity={5!r}, "
32+
"light={6!r}, watering={7!r})"),
33+
self.id,
34+
self.id_plant,
35+
self.plant_type,
36+
self.time_stamp,
37+
self.temperature,
38+
self.humidity,
39+
self.light,
40+
self.watering
41+
)

app/main.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,44 @@
1-
from fastapi import FastAPI
1+
from fastapi import Body, FastAPI, Request
2+
from app.database.database import SQLAlchemyClient
3+
from app.database.models.DevicePlant import DevicePlant
4+
from app.schemas.DevicePlant import DevicePlantSchema
5+
import logging
26

37
app = FastAPI()
8+
logger = logging.getLogger("measurements")
9+
logger.setLevel("DEBUG")
10+
11+
12+
@app.on_event("startup")
13+
async def start_up():
14+
app.logger = logger
15+
16+
try:
17+
app.database = SQLAlchemyClient()
18+
app.logger.info("Postgres connection established")
19+
except Exception as e:
20+
app.logger.error(e)
21+
app.logger.error("Could not connect to Postgres client")
22+
23+
24+
@app.on_event("shutdown")
25+
async def shutdown_db_client():
26+
app.database.shutdown()
27+
app.logger.info("Postgres shutdown succesfully")
428

529

630
@app.get("/")
731
async def root():
832
return {"message": "Hello World"}
33+
34+
35+
# Endpoint only for DB conection testing.
36+
@app.post("/device-plant")
37+
async def add_new_device_plant(req: Request,
38+
device_plant: DevicePlantSchema = Body(...)):
39+
try:
40+
req.app.database.add_new(DevicePlant.from_pydantic(device_plant))
41+
return req.app.database.find_device_plant(device_plant.id_device)
42+
except Exception as e:
43+
req.app.database.rollback()
44+
raise e

app/schemas/DevicePlant.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from pydantic import BaseModel
2+
3+
4+
class DevicePlantSchema(BaseModel):
5+
id_device: str
6+
id_plant: int
7+
plant_type: int
8+
id_user: int

docker-compose.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,24 @@ services:
55
build:
66
context: .
77
container_name: app
8+
env_file:
9+
- .env
810
ports:
911
- "8080:8080"
12+
depends_on:
13+
- db
14+
15+
rabbitmq:
16+
image: rabbitmq:3.12-management
17+
ports:
18+
- "5672:5672"
19+
- "15672:15672"
20+
21+
db:
22+
image: postgres:latest
23+
env_file:
24+
- .env
25+
ports:
26+
- "5432:5432"
27+
volumes:
28+
- ./sql:/docker-entrypoint-initdb.d

docker-compose.yml

Lines changed: 0 additions & 20 deletions
This file was deleted.

sql/tablas.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ CREATE TABLE IF NOT EXISTS dev.measurements (
1313
plant_type SMALLINT,
1414
time_stamp VARCHAR(50),
1515
temperature SMALLINT,
16-
humidity SMALLINT,
17-
light SMALLINT,
18-
watering SMALLINT
16+
humidity SMALLINT CHECK (humidity >= 0 AND humidity <= 100),
17+
light SMALLINT CHECK (light >= 0),
18+
watering SMALLINT CHECK (watering >= 0 AND watering <= 100)
1919
);

0 commit comments

Comments
 (0)