Optimize AMS Spoolman sync performance with spool caching

- Add cached_spools parameter to find_spool_by_tag, find_spools_by_location_prefix, sync_ams_tray, and clear_location_for_removed_spools
- Fetch spools once before loops in on_ams_change, sync_single_printer, and sync_all_printers endpoints
- Cache newly created spools during sync to avoid duplicate API calls
- Add 5 unit tests for caching functionality (all passing)
- Reduce redundant API calls when syncing multiple AMS trays
- Improve sync performance for users with large spool databases
- Maintain backward compatibility with optional cached_spools parameters
This commit is contained in:
bambuman
2026-02-07 22:29:52 +02:00
parent 833a0c62d2
commit deab81287f
4 changed files with 166 additions and 12 deletions
@@ -172,3 +172,81 @@ class TestSpoolmanClient:
assert call_kwargs["remaining_weight"] == expected, (
f"Expected {expected}g for {remain}% of {weight}g, got {call_kwargs['remaining_weight']}"
)
# ========================================================================
# Tests for caching functionality
# ========================================================================
@pytest.mark.asyncio
async def test_find_spool_by_tag_with_cached_spools(self, client):
"""Verify find_spool_by_tag uses cached spools when provided (no API call)."""
cached = [
{"id": 1, "extra": {"tag": '"ABC123"'}},
{"id": 2, "extra": {"tag": '"XYZ789"'}},
]
with patch.object(client, "get_spools", AsyncMock()) as mock_get:
result = await client.find_spool_by_tag("ABC123", cached_spools=cached)
assert result["id"] == 1
mock_get.assert_not_called() # Should NOT call get_spools
@pytest.mark.asyncio
async def test_find_spool_by_tag_without_cached_spools(self, client):
"""Verify find_spool_by_tag fetches spools when cache not provided."""
mock_spools = [{"id": 1, "extra": {"tag": '"ABC123"'}}]
with patch.object(client, "get_spools", AsyncMock(return_value=mock_spools)) as mock_get:
result = await client.find_spool_by_tag("ABC123")
assert result["id"] == 1
mock_get.assert_called_once() # Should call get_spools
@pytest.mark.asyncio
async def test_find_spools_by_location_prefix_with_cached_spools(self, client):
"""Verify find_spools_by_location_prefix uses cached spools when provided."""
cached = [
{"id": 1, "location": "Printer1 - AMS A1"},
{"id": 2, "location": "Printer2 - AMS A1"},
{"id": 3, "location": "Printer1 - AMS A2"},
]
with patch.object(client, "get_spools", AsyncMock()) as mock_get:
result = await client.find_spools_by_location_prefix("Printer1 - ", cached_spools=cached)
assert len(result) == 2
assert result[0]["id"] == 1
assert result[1]["id"] == 3
mock_get.assert_not_called() # Should NOT call get_spools
@pytest.mark.asyncio
async def test_sync_ams_tray_with_cached_spools(self, client, sample_tray, existing_spool):
"""Verify sync_ams_tray passes cached_spools to find_spool_by_tag."""
cached = [existing_spool]
with (
patch.object(client, "get_spools", AsyncMock()) as mock_get,
patch.object(client, "update_spool", AsyncMock(return_value={"id": 42})),
):
await client.sync_ams_tray(sample_tray, "TestPrinter", cached_spools=cached)
mock_get.assert_not_called() # Should NOT call get_spools
@pytest.mark.asyncio
async def test_clear_location_for_removed_spools_with_cached_spools(self, client):
"""Verify clear_location_for_removed_spools uses cached spools."""
cached = [
{"id": 1, "location": "Printer1 - AMS A1", "extra": {"tag": '"TAG1"'}},
{"id": 2, "location": "Printer1 - AMS A2", "extra": {"tag": '"TAG2"'}},
{"id": 3, "location": "Printer1 - AMS A3", "extra": {"tag": '"TAG3"'}},
]
current_tags = {"TAG1", "TAG2"} # TAG3 was removed
with (
patch.object(client, "get_spools", AsyncMock()) as mock_get,
patch.object(client, "update_spool", AsyncMock(return_value={"id": 3})) as mock_update,
):
cleared = await client.clear_location_for_removed_spools("Printer1", current_tags, cached_spools=cached)
assert cleared == 1
mock_get.assert_not_called() # Should NOT call get_spools
mock_update.assert_called_once()
# Verify it cleared TAG3 (not in current_tags)
call_kwargs = mock_update.call_args.kwargs
assert call_kwargs["spool_id"] == 3
assert call_kwargs.get("clear_location") is True