Improved compatibility with postgres

This commit is contained in:
Donkie
2023-05-09 20:03:19 +02:00
parent d8fc5fc37b
commit ed3e30ea75
3 changed files with 50 additions and 20 deletions

View File

@@ -12,7 +12,7 @@ dependencies = {file = ["requirements.txt"]}
[tool.ruff] [tool.ruff]
select = ["ALL"] select = ["ALL"]
ignore = ["A003", "D101", "D104", "D406", "D407", "S104", "TRY201", "TRY003", "EM101", "EM102"] ignore = ["A003", "D101", "D104", "D406", "D407", "S104", "TRY201", "TRY003", "EM101", "EM102", "DTZ003"]
line-length = 120 line-length = 120
target-version = "py39" target-version = "py39"

View File

@@ -20,7 +20,13 @@ class Database:
def connect(self: "Database") -> None: def connect(self: "Database") -> None:
"""Connect to the database.""" """Connect to the database."""
self.engine = create_async_engine(self.connection_url, connect_args={"check_same_thread": False}) connect_args = {}
if self.connection_url.drivername == "sqlite":
connect_args["check_same_thread"] = False
elif self.connection_url.drivername == "postgresql":
connect_args["options"] = "-c timezone=utc"
self.engine = create_async_engine(self.connection_url, connect_args=connect_args)
self.session_maker = async_sessionmaker(self.engine, autocommit=False, autoflush=True) self.session_maker = async_sessionmaker(self.engine, autocommit=False, autoflush=True)
async def create_tables(self: "Database") -> None: async def create_tables(self: "Database") -> None:

View File

@@ -3,7 +3,7 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Optional from typing import Optional
from sqlalchemy import select import sqlalchemy
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import contains_eager, joinedload from sqlalchemy.orm import contains_eager, joinedload
@@ -48,12 +48,11 @@ async def create(
return db_item return db_item
async def get_by_id(db: AsyncSession, spool_id: int, with_for_update: Optional[bool] = None) -> models.Spool: async def get_by_id(db: AsyncSession, spool_id: int) -> models.Spool:
"""Get a spool object from the database by the unique ID.""" """Get a spool object from the database by the unique ID."""
spool = await db.get( spool = await db.get(
models.Spool, models.Spool,
spool_id, spool_id,
with_for_update=with_for_update, # type: ignore # noqa: PGH003
options=[joinedload("*")], # Load all nested objects as well options=[joinedload("*")], # Load all nested objects as well
) )
if spool is None: if spool is None:
@@ -74,7 +73,7 @@ async def find(
) -> list[models.Spool]: ) -> list[models.Spool]:
"""Find a list of spool objects by search criteria.""" """Find a list of spool objects by search criteria."""
stmt = ( stmt = (
select(models.Spool) sqlalchemy.select(models.Spool)
.join(models.Spool.filament) .join(models.Spool.filament)
.join(models.Filament.vendor) .join(models.Filament.vendor)
.options(contains_eager(models.Spool.filament).contains_eager(models.Filament.vendor)) .options(contains_eager(models.Spool.filament).contains_eager(models.Filament.vendor))
@@ -125,6 +124,21 @@ async def delete(db: AsyncSession, spool_id: int) -> None:
await db.delete(spool) await db.delete(spool)
async def use_weight_safe(db: AsyncSession, spool_id: int, weight: float) -> None:
"""Consume filament from a spool by weight in a way that is safe against race conditions.
Args:
db (AsyncSession): Database session
spool_id (int): Spool ID
weight (float): Filament weight to consume, in grams
"""
await db.execute(
sqlalchemy.update(models.Spool)
.where(models.Spool.id == spool_id)
.values(used_weight=models.Spool.used_weight + weight),
)
# TODO: Make unit tests for race conditions on these # TODO: Make unit tests for race conditions on these
async def use_weight(db: AsyncSession, spool_id: int, weight: float) -> models.Spool: async def use_weight(db: AsyncSession, spool_id: int, weight: float) -> models.Spool:
"""Consume filament from a spool by weight. """Consume filament from a spool by weight.
@@ -140,12 +154,13 @@ async def use_weight(db: AsyncSession, spool_id: int, weight: float) -> models.S
Returns: Returns:
models.Spool: Updated spool object models.Spool: Updated spool object
""" """
spool = await get_by_id(db, spool_id, with_for_update=True) await use_weight_safe(db, spool_id, weight)
spool.used_weight += weight
spool = await get_by_id(db, spool_id)
if spool.first_used is None: if spool.first_used is None:
spool.first_used = datetime.now(tz=timezone.utc) spool.first_used = datetime.utcnow()
spool.last_used = datetime.now(tz=timezone.utc) spool.last_used = datetime.utcnow()
await db.flush() await db.flush()
return spool return spool
@@ -165,19 +180,28 @@ async def use_length(db: AsyncSession, spool_id: int, length: float) -> models.S
Returns: Returns:
models.Spool: Updated spool object models.Spool: Updated spool object
""" """
spool = await get_by_id(db, spool_id, with_for_update=True) # Get filament diameter and density
result = await db.execute(
filament = spool.filament sqlalchemy.select(models.Filament.diameter, models.Filament.density)
.join(models.Spool, models.Spool.filament_id == models.Filament.id)
spool.used_weight += weight_from_length( .where(models.Spool.id == spool_id),
length=length,
radius=filament.diameter / 2,
density=filament.density,
) )
filament_info = result.one()
# Calculate and use weight
weight = weight_from_length(
length=length,
radius=filament_info[0] / 2,
density=filament_info[1],
)
await use_weight_safe(db, spool_id, weight)
# Get spool with new weight and update first_used and last_used
spool = await get_by_id(db, spool_id)
if spool.first_used is None: if spool.first_used is None:
spool.first_used = datetime.now(tz=timezone.utc) spool.first_used = datetime.utcnow()
spool.last_used = datetime.now(tz=timezone.utc) spool.last_used = datetime.utcnow()
await db.flush() await db.flush()
return spool return spool