From 5654b5d7bc37371b3077c27a4dd73e6325a99c30 Mon Sep 17 00:00:00 2001 From: Donkie Date: Mon, 10 Jul 2023 11:19:46 +0200 Subject: [PATCH] Added used_length and remaining_length API fields They're calculated from the weights Resolves #37 --- spoolman/api/v1/models.py | 37 +++++++++++++-- tests_integration/tests/conftest.py | 17 +++++++ tests_integration/tests/test_spool.py | 66 +++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/spoolman/api/v1/models.py b/spoolman/api/v1/models.py index d8f22b1..41e2126 100644 --- a/spoolman/api/v1/models.py +++ b/spoolman/api/v1/models.py @@ -6,6 +6,7 @@ from typing import Optional from pydantic import BaseModel, Field from spoolman.database import models +from spoolman.math import length_from_weight class Message(BaseModel): @@ -118,11 +119,26 @@ class Spool(BaseModel): default=None, ge=0, description=( - "Estimated remaining weight of filament on the spool. Only set if the filament type has a weight set." + "Estimated remaining weight of filament on the spool in grams. " + "Only set if the filament type has a weight set." ), - example=500, + example=500.6, + ) + used_weight: float = Field(ge=0, description="Consumed weight of filament from the spool in grams.", example=500.3) + remaining_length: Optional[float] = Field( + default=None, + ge=0, + description=( + "Estimated remaining length of filament on the spool in millimeters." + " Only set if the filament type has a weight set." + ), + example=5612.4, + ) + used_length: float = Field( + ge=0, + description="Consumed length of filament from the spool in millimeters.", + example=50.7, ) - used_weight: float = Field(ge=0, description="Consumed weight of filament from the spool.", example=500) location: Optional[str] = Field(max_length=64, description="Where this spool can be found.", example="Shelf A") lot_nr: Optional[str] = Field( max_length=64, @@ -139,9 +155,22 @@ class Spool(BaseModel): def from_db(item: models.Spool) -> "Spool": """Create a new Pydantic spool object from a database spool object.""" filament = Filament.from_db(item.filament) + remaining_weight: Optional[float] = None # noqa: FA100 + remaining_length: Optional[float] = None # noqa: FA100 if filament.weight is not None: remaining_weight = max(filament.weight - item.used_weight, 0) + remaining_length = length_from_weight( + weight=remaining_weight, + density=filament.density, + diameter=filament.diameter, + ) + + used_length = length_from_weight( + weight=item.used_weight, + density=filament.density, + diameter=filament.diameter, + ) return Spool( id=item.id, @@ -150,7 +179,9 @@ class Spool(BaseModel): last_used=item.last_used, filament=filament, used_weight=item.used_weight, + used_length=used_length, remaining_weight=remaining_weight, + remaining_length=remaining_length, location=item.location, lot_nr=item.lot_nr, comment=item.comment, diff --git a/tests_integration/tests/conftest.py b/tests_integration/tests/conftest.py index 8e91d1f..4a56d87 100644 --- a/tests_integration/tests/conftest.py +++ b/tests_integration/tests/conftest.py @@ -1,5 +1,6 @@ """Test fixtures for integration tests.""" +import math import os import time from enum import Enum @@ -92,3 +93,19 @@ def random_filament(random_vendor: dict[str, Any]): # Delete filament httpx.delete(f"{URL}/api/v1/filament/{filament['id']}").raise_for_status() + + +def length_from_weight(*, weight: float, diameter: float, density: float) -> float: + """Calculate the length of a piece of filament. + + Args: + weight (float): Filament weight in g + diameter (float): Filament diameter in mm + density (float): Density of filament material in g/cm3 + + Returns: + float: Length in mm + """ + volume_cm3 = weight / density + volume_mm3 = volume_cm3 * 1000 + return volume_mm3 / (math.pi * (diameter / 2) ** 2) diff --git a/tests_integration/tests/test_spool.py b/tests_integration/tests/test_spool.py index d99fba1..024cdf9 100644 --- a/tests_integration/tests/test_spool.py +++ b/tests_integration/tests/test_spool.py @@ -7,6 +7,8 @@ from typing import Any import httpx import pytest +from .conftest import length_from_weight + URL = "http://spoolman:8000" @@ -32,6 +34,18 @@ def test_add_spool_remaining_weight(random_filament: dict[str, Any]): result.raise_for_status() # Verify + used_weight = random_filament["weight"] - remaining_weight + used_length = length_from_weight( + weight=used_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + remaining_length = length_from_weight( + weight=remaining_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + spool = result.json() assert spool == { "id": spool["id"], @@ -40,7 +54,9 @@ def test_add_spool_remaining_weight(random_filament: dict[str, Any]): "last_used": "2023-01-02T11:00:00", "filament": random_filament, "remaining_weight": pytest.approx(remaining_weight), - "used_weight": pytest.approx(random_filament["weight"] - remaining_weight), + "used_weight": pytest.approx(used_weight), + "remaining_length": pytest.approx(remaining_length), + "used_length": pytest.approx(used_length), "location": location, "lot_nr": lot_nr, "comment": comment, @@ -74,6 +90,18 @@ def test_add_spool_used_weight(random_filament: dict[str, Any]): result.raise_for_status() # Verify + remaining_weight = random_filament["weight"] - used_weight + used_length = length_from_weight( + weight=used_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + remaining_length = length_from_weight( + weight=remaining_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + spool = result.json() assert spool == { "id": spool["id"], @@ -81,8 +109,10 @@ def test_add_spool_used_weight(random_filament: dict[str, Any]): "first_used": first_used, "last_used": last_used, "filament": random_filament, - "remaining_weight": pytest.approx(random_filament["weight"] - used_weight), + "remaining_weight": pytest.approx(remaining_weight), "used_weight": pytest.approx(used_weight), + "remaining_length": pytest.approx(remaining_length), + "used_length": pytest.approx(used_length), "location": location, "lot_nr": lot_nr, "comment": comment, @@ -106,13 +136,27 @@ def test_add_spool_required(random_filament: dict[str, Any]): result.raise_for_status() # Verify + remaining_weight = random_filament["weight"] - used_weight + used_length = length_from_weight( + weight=used_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + remaining_length = length_from_weight( + weight=remaining_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + spool = result.json() assert spool == { "id": spool["id"], "registered": spool["registered"], "filament": random_filament, "used_weight": pytest.approx(used_weight), - "remaining_weight": pytest.approx(random_filament["weight"] - used_weight), + "remaining_weight": pytest.approx(remaining_weight), + "used_length": pytest.approx(used_length), + "remaining_length": pytest.approx(remaining_length), } # Clean up @@ -389,11 +433,25 @@ def test_update_spool(random_filament: dict[str, Any]): result.raise_for_status() # Verify + used_weight = random_filament["weight"] - remaining_weight + used_length = length_from_weight( + weight=used_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + remaining_length = length_from_weight( + weight=remaining_weight, + density=random_filament["density"], + diameter=random_filament["diameter"], + ) + spool = result.json() assert spool["first_used"] == "2023-01-01T10:00:00" assert spool["last_used"] == "2023-01-02T10:00:00" assert spool["remaining_weight"] == pytest.approx(remaining_weight) - assert spool["used_weight"] == pytest.approx(random_filament["weight"] - remaining_weight) + assert spool["used_weight"] == pytest.approx(used_weight) + assert spool["remaining_length"] == pytest.approx(remaining_length) + assert spool["used_length"] == pytest.approx(used_length) assert spool["location"] == location assert spool["lot_nr"] == lot_nr assert spool["comment"] == comment