Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Projecte-UrbanTree/UrbanTree
Browse files Browse the repository at this point in the history
  • Loading branch information
0x1026 committed Dec 10, 2024
2 parents caa6eff + 5e2ec66 commit 7e21208
Show file tree
Hide file tree
Showing 28 changed files with 590 additions and 313 deletions.
2 changes: 1 addition & 1 deletion api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apscheduler==3.11.0
databases[mysql]
fastapi[standard]==0.115.6
pydantic_core==2.27.1
pydantic-settings==2.6.1
pydantic==2.10.3
sentry-sdk[fastapi]==2.19.2
sqlmodel==0.0.22
6 changes: 0 additions & 6 deletions api/sensors.json
Original file line number Diff line number Diff line change
@@ -1,46 +1,40 @@
[
{
"id": 1,
"sensor_id": 1,
"temperature": 22.5,
"humidity": 55.3,
"inclination": 0.12,
"created_at": "2024-11-19T12:00:00"
},
{
"id": 2,
"sensor_id": 1,
"temperature": 23.0,
"humidity": 50.1,
"inclination": 0.15,
"created_at": "2024-11-19T12:30:00"
},
{
"id": 3,
"sensor_id": 2,
"temperature": 21.8,
"humidity": 60.4,
"inclination": 0.10,
"created_at": "2024-11-19T13:00:00"
},
{
"id": 4,
"sensor_id": 2,
"temperature": 22.2,
"humidity": 57.9,
"inclination": 0.20,
"created_at": "2024-11-19T13:30:00"
},
{
"id": 5,
"sensor_id": 3,
"temperature": 24.0,
"humidity": 49.0,
"inclination": 0.18,
"created_at": "2024-11-19T14:00:00"
},
{
"id": 6,
"sensor_id": 3,
"temperature": 23.5,
"humidity": 51.2,
Expand Down
2 changes: 2 additions & 0 deletions api/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class Settings(BaseSettings):
def check_image_version(cls, v):
if v == "":
return None
if v.startswith("v"):
return v[1:]
return v

@model_validator(mode="before")
Expand Down
18 changes: 18 additions & 0 deletions api/src/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from sqlmodel import Session, SQLModel, create_engine

from .config import settings

engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))


def create_db_and_tables(engine=engine):
SQLModel.metadata.create_all(engine)


def drop_db_and_tables(engine=engine):
SQLModel.metadata.drop_all(engine)


def get_session():
with Session(engine) as session:
return session
26 changes: 9 additions & 17 deletions api/src/main.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from contextlib import asynccontextmanager

import sentry_sdk
import sentry_sdk.crons
from fastapi import FastAPI
from sqlmodel import Session, SQLModel, create_engine
from src.config import settings
from src.services.sensor_service import insert_data
from src.utils.file_loader import load_sensor_data

from .config import settings
from .database import create_db_and_tables
from .services.scheduler_service import scheduler

# Initialize Sentry SDK
sentry_sdk.init(
dsn=settings.SENTRY_DSN,
environment=settings.APP_ENV,
Expand All @@ -27,23 +29,13 @@
},
)

engine = create_engine(str(settings.SQLALCHEMY_DATABASE_URI))


def create_db_and_tables():
SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables()
sensor_data = load_sensor_data("./sensors.json")
with Session(engine) as session:
if sensor_data:
insert_data(sensor_data, session)
else:
print("No data to insert")
scheduler.start()
yield
scheduler.shutdown()


app = FastAPI(lifespan=lifespan)
Expand All @@ -56,4 +48,4 @@ def hello():

@app.get("/health")
def health_check():
return {"status": "healthy", "version": settings.SENTRY_RELEASE}
return {"status": "healthy", "version": settings.SENTRY_RELEASE or "dev"}
38 changes: 38 additions & 0 deletions api/src/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from datetime import datetime

from sqlmodel import Field, Relationship, SQLModel


class Sensor(SQLModel, table=True):
__tablename__ = "sensors"

id: int | None = Field(default=None, primary_key=True)

contract_id: int = Field(index=True)
zone_id: int = Field(index=True)
point_id: int = Field(index=True)
model: str | None = Field(default=None, index=True)
is_active: bool | None = Field(default=None, index=True)

histories: list["SensorHistory"] = Relationship(back_populates="sensor")


class SensorHistoryBase(SQLModel):
temperature: float
humidity: float
inclination: float
created_at: datetime

sensor_id: int = Field(foreign_key="sensors.id")


class SensorHistory(SensorHistoryBase, table=True):
__tablename__ = "sensor_history"

id: int | None = Field(default=None, primary_key=True)

sensor: Sensor = Relationship(back_populates="histories")


class SensorHistoryCreate(SensorHistoryBase):
pass
Empty file removed api/src/models/__init__.py
Empty file.
50 changes: 0 additions & 50 deletions api/src/models/sensor_model.py

This file was deleted.

Empty file removed api/src/schemas/__init__.py
Empty file.
17 changes: 0 additions & 17 deletions api/src/schemas/sensor.py

This file was deleted.

13 changes: 13 additions & 0 deletions api/src/services/scheduler_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from datetime import datetime

from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger

from .sensor_service import check_sensor_task

trigger8h = IntervalTrigger(hours=8)

now = datetime.now()

scheduler = BackgroundScheduler()
scheduler.add_job(check_sensor_task, trigger8h, next_run_time=now)
49 changes: 29 additions & 20 deletions api/src/services/sensor_service.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
from typing import List
from sentry_sdk.crons import monitor
from sqlmodel import select

from sqlalchemy.orm import Session
from src.models.sensor_model import Sensor, SensorHistory
from src.schemas.sensor import ModelItem
from src.database import get_session
from src.models import SensorHistory
from src.utils.file_loader import load_json_file


def insert_data(sensor_data: List[ModelItem], session: Session):
# Define a cron job to check the sensors
@monitor(monitor_slug="check-sensors")
def check_sensor_task():
try:
for sensor in sensor_data:
# Search existent sensor
db_sensor = session.query(Sensor).filter_by(id=sensor.sensor_id).first()
sensor_file_data = load_json_file("sensors.json")
session = get_session()

if db_sensor:
db_history = SensorHistory(
sensor_id=sensor.sensor_id,
temperature=sensor.temperature,
humidity=sensor.humidity,
inclination=sensor.inclination,
created_at=sensor.created_at,
)
session.add(db_history)
if not sensor_file_data:
raise ValueError("No data to insert")

for sensor_history_entry in sensor_file_data:
sensor_history = SensorHistory.model_validate(sensor_history_entry)
statement = select(SensorHistory).where(
SensorHistory.sensor_id == sensor_history.sensor_id,
SensorHistory.created_at == sensor_history.created_at,
)
sensor_db = session.exec(statement).first()
if sensor_db:
sensor_db.temperature = sensor_history.temperature
sensor_db.humidity = sensor_history.humidity
sensor_db.inclination = sensor_history.inclination
session.add(sensor_db)
else:
session.add(sensor_history)
session.commit()

session.commit()
print("Datos insertados correctamente 👍🏼")
except Exception as e:
session.rollback()
print(f"Error al insertar los datos: {e}")
raise e
13 changes: 3 additions & 10 deletions api/src/utils/file_loader.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import json
from typing import List

from src.schemas.sensor import ModelItem


def load_sensor_data(file_path: str) -> List[ModelItem]:
def load_json_file(file_path: str) -> json:
try:
with open(file_path, "r") as f:
raw_data = json.load(f)
# raw_data: List[dict]

return [ModelItem(**sensor) for sensor in raw_data]
return json.load(f)
except Exception as e:
print("Error", e)
return []
raise e
4 changes: 4 additions & 0 deletions api/tests/resources/invalid_test_file_loader.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"test": "invalid_test_file_loader"
"description": "This is a test file with invalid JSON format"
}
9 changes: 9 additions & 0 deletions api/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ def test_settings_with_custom_settings():
assert custom.SENTRY_RELEASE == "[email protected]"


def test_v_prefixed_image_version():
custom = Settings(
APP_ENV="production",
IMAGE_VERSION="v1.0.0",
)
assert custom.IMAGE_VERSION == "1.0.0"
assert custom.SENTRY_RELEASE == "[email protected]"


def test_settings_missing_password():
with pytest.raises(ValidationError):
Settings(
Expand Down
Loading

0 comments on commit 7e21208

Please sign in to comment.