فهرست منبع

follow-up(queue): cache per-plate 3MF metadata so queue polling stops re-parsing every row (#2573)

maziggy 1 ماه پیش
والد
کامیت
3f21e0b8ff
2فایلهای تغییر یافته به همراه54 افزوده شده و 18 حذف شده
  1. 23 18
      backend/app/utils/threemf_tools.py
  2. 31 0
      backend/tests/unit/test_threemf_tools.py

+ 23 - 18
backend/app/utils/threemf_tools.py

@@ -493,19 +493,23 @@ def _parse_plate_metadata_uncached(file_path: Path, plate_id: int | None) -> Pla
         out: list[dict] = []
         for f in plate_elem.findall("filament"):
             filament_id = f.get("id")
+            # Both the used_g float() and the id int() must stay inside the guard:
+            # a non-numeric id or used_g is silently skipped (matches the legacy
+            # helpers, which tolerated garbage rows rather than raising — a raise
+            # here would 500 the whole queue listing).
             try:
                 used_amount = float(f.get("used_g", "0"))
+                if filament_id:
+                    out.append(
+                        {
+                            "slot_id": int(filament_id),
+                            "used_g": used_amount,
+                            "type": f.get("type", ""),
+                            "color": f.get("color", ""),
+                        }
+                    )
             except (ValueError, TypeError):
                 continue
-            if filament_id:
-                out.append(
-                    {
-                        "slot_id": int(filament_id),
-                        "used_g": used_amount,
-                        "type": f.get("type", ""),
-                        "color": f.get("color", ""),
-                    }
-                )
         return out
 
     print_time: int | None = None
@@ -539,19 +543,20 @@ def _parse_plate_metadata_uncached(file_path: Path, plate_id: int | None) -> Pla
         # Legacy plate_id=None usage: every filament in the file, not just plate 1.
         for f in root.findall(".//filament"):
             filament_id = f.get("id")
+            # int()/float() both guarded — a garbage id/used_g row is skipped, not raised.
             try:
                 used_amount = float(f.get("used_g", "0"))
+                if filament_id:
+                    filament_usage.append(
+                        {
+                            "slot_id": int(filament_id),
+                            "used_g": used_amount,
+                            "type": f.get("type", ""),
+                            "color": f.get("color", ""),
+                        }
+                    )
             except (ValueError, TypeError):
                 continue
-            if filament_id:
-                filament_usage.append(
-                    {
-                        "slot_id": int(filament_id),
-                        "used_g": used_amount,
-                        "type": f.get("type", ""),
-                        "color": f.get("color", ""),
-                    }
-                )
 
     return PlateMetadata(
         print_time_seconds=print_time,

+ 31 - 0
backend/tests/unit/test_threemf_tools.py

@@ -1151,6 +1151,37 @@ class TestExtractPlateMetadataFrom3mf:
         assert len(cached.filament_usage) == 2
         assert all(f["used_g"] > 0 for f in cached.filament_usage)
 
+    def test_non_numeric_filament_id_is_skipped_not_raised(self, tmp_path):
+        # A garbage filament id (or used_g) must be silently skipped, exactly as
+        # the legacy helpers did — a raise here would 500 the queue listing that
+        # calls this per row. Guards both the plate-specific and plate_id=None paths.
+        from backend.app.utils.threemf_tools import (
+            extract_filament_usage_from_3mf,
+            extract_plate_metadata_from_3mf,
+        )
+
+        xml_content = """<?xml version="1.0" encoding="UTF-8"?>
+        <config>
+            <plate>
+                <metadata key="index" value="1"/>
+                <metadata key="prediction" value="3600"/>
+                <filament id="abc" used_g="5.0" type="PLA" color="#FFFFFF"/>
+                <filament id="1" used_g="10.0" type="PLA" color="#FF0000"/>
+                <filament id="2" used_g="bad" type="PLA" color="#00FF00"/>
+            </plate>
+        </config>
+        """
+        file_path = self._write(tmp_path, xml_content)
+
+        meta = extract_plate_metadata_from_3mf(file_path, plate_id=1)
+        assert [f["slot_id"] for f in meta.filament_usage] == [1]
+        assert meta.filament_used_grams == 10.0
+        assert meta.print_time_seconds == 3600
+
+        # plate_id=None path (collects all filaments in the file) must skip too.
+        none_result = extract_filament_usage_from_3mf(file_path, plate_id=None)
+        assert [f["slot_id"] for f in none_result] == [1]
+
     def test_missing_file_returns_empty_and_is_not_cached(self, tmp_path):
         from unittest.mock import patch