Files
home-automation/app/services/location.py
T

98 lines
2.8 KiB
Python
Raw Permalink Normal View History

from datetime import datetime, timezone
from sqlalchemy import delete, insert, select
from sqlalchemy.orm import Session
from app.models.location import Location
from app.schemas.location import LocationRecordRequest
2026-04-19 23:18:20 +02:00
def _parse_optional_float_compat(value: str | None) -> float:
try:
return float(value)
except (TypeError, ValueError):
return 0.0
2026-04-19 23:18:20 +02:00
def _parse_required_float(value: str, field_name: str) -> float:
try:
return float(value)
except (TypeError, ValueError) as exc:
raise ValueError(f"Invalid numeric value for {field_name}") from exc
def _utc_now_rfc3339() -> str:
now = datetime.now(timezone.utc).replace(microsecond=0)
return now.isoformat().replace("+00:00", "Z")
def record_location(session: Session, payload: LocationRecordRequest) -> None:
stmt = (
insert(Location)
.prefix_with("OR IGNORE")
.values(
person=payload.person,
datetime=_utc_now_rfc3339(),
2026-04-19 23:18:20 +02:00
latitude=_parse_required_float(payload.latitude, "latitude"),
longitude=_parse_required_float(payload.longitude, "longitude"),
altitude=_parse_optional_float_compat(payload.altitude),
)
)
session.execute(stmt)
session.commit()
def update_location(
session: Session,
person: str,
datetime_pk: str,
*,
latitude: float | None,
longitude: float | None,
altitude: float | None,
) -> Location | None:
"""Update non-PK fields of a single location row.
Returns the updated ORM object, or ``None`` if the PK does not exist.
The caller must not pass PK fields — they are immutable.
Only fields with a non-``None`` value are written; ``altitude`` being
``None`` in the request means "leave unchanged", not "clear to NULL".
"""
row = session.execute(
select(Location).where(
Location.person == person,
Location.datetime == datetime_pk,
)
).scalar_one_or_none()
if row is None:
return None
if latitude is not None:
row.latitude = latitude
if longitude is not None:
row.longitude = longitude
if altitude is not None:
row.altitude = altitude
session.commit()
session.refresh(row)
return row
def delete_location(session: Session, person: str, datetime_pk: str) -> bool:
"""Delete the single location row identified by its full composite PK.
Returns ``True`` if exactly one row was deleted, ``False`` if the PK did
not exist (caller should raise 404). The DELETE is scoped to the exact PK
— no batch/truncate path exists.
"""
result = session.execute(
delete(Location).where(
Location.person == person,
Location.datetime == datetime_pk,
)
)
session.commit()
return result.rowcount == 1