Auto-create Spoolman 'tag' extra field on connect (Issue #123)

The 'tag' extra field is required in Spoolman for storing RFID/UUID
identifiers that link Bambu Lab spools to Spoolman entries. Previously,
users had to manually create this field in Spoolman, causing sync
failures for fresh installations.

Added ensure_tag_extra_field() method to SpoolmanClient that:
- Checks if the 'tag' field exists via GET /api/v1/field/spool/tag
- Creates it via POST if missing

The method is called automatically:
- On app startup when auto-connecting to Spoolman
- When user clicks "Connect" in Spoolman settings
This commit is contained in:
maziggy
2026-01-22 09:23:40 +01:00
parent 01ab2cbb45
commit 0caa68b77f
5 changed files with 43 additions and 0 deletions
+1
View File
@@ -15,6 +15,7 @@ All notable changes to Bambuddy will be documented in this file.
### Fixed
- **Filament cost using wrong default** - Statistics now correctly uses the "Default filament cost (per kg)" setting instead of hardcoded €25 value (Issue #120)
- **Spoolman tag field not auto-created** - The required "tag" extra field is now automatically created in Spoolman on first connect, fixing sync failures for fresh Spoolman installs (Issue #123)
## [0.1.6b10] - 2026-01-21
+3
View File
@@ -109,6 +109,9 @@ async def connect_spoolman(db: AsyncSession = Depends(get_db)):
detail=f"Could not connect to Spoolman at {url}",
)
# Ensure the 'tag' extra field exists for RFID/UUID storage
await client.ensure_tag_extra_field()
return {"success": True, "message": f"Connected to Spoolman at {url}"}
except Exception as e:
logger.error(f"Failed to connect to Spoolman: {e}")
+2
View File
@@ -1903,6 +1903,8 @@ async def lifespan(app: FastAPI):
client = await init_spoolman_client(spoolman_url)
if await client.health_check():
logging.info(f"Auto-connected to Spoolman at {spoolman_url}")
# Ensure the 'tag' extra field exists for RFID/UUID storage
await client.ensure_tag_extra_field()
else:
logging.warning(f"Spoolman at {spoolman_url} is not reachable")
except Exception as e:
+36
View File
@@ -488,6 +488,42 @@ class SpoolmanClient:
vendor = await self.create_vendor("Bambu Lab")
return vendor["id"] if vendor else None
async def ensure_tag_extra_field(self) -> bool:
"""Ensure the 'tag' extra field exists for spools.
Spoolman requires extra fields to be registered before use.
This creates the 'tag' field used to store RFID/UUID identifiers.
Returns:
True if field exists or was created, False on failure.
"""
try:
client = await self._get_client()
# Check if field already exists
response = await client.get(f"{self.api_url}/field/spool/tag")
if response.status_code == 200:
logger.debug("Spoolman 'tag' extra field already exists")
return True
# Field doesn't exist - create it
field_data = {
"name": "tag",
"field_type": "text",
"default_value": None,
}
response = await client.post(f"{self.api_url}/field/spool/tag", json=field_data)
if response.status_code in (200, 201):
logger.info("Created 'tag' extra field in Spoolman")
return True
logger.warning(f"Failed to create 'tag' extra field: {response.status_code} - {response.text}")
return False
except Exception as e:
logger.warning(f"Failed to ensure 'tag' extra field exists: {e}")
return False
def parse_ams_tray(self, ams_id: int, tray_data: dict) -> AMSTray | None:
"""Parse AMS tray data into AMSTray object.
@@ -39,6 +39,7 @@ class TestSpoolmanAPI:
mock_client.is_connected = True
mock_client.base_url = "http://localhost:7912"
mock_client.health_check = AsyncMock(return_value=True)
mock_client.ensure_tag_extra_field = AsyncMock(return_value=True)
mock_client.get_spools = AsyncMock(return_value=[])
mock_client.get_filaments = AsyncMock(return_value=[])
mock_client.create_spool = AsyncMock(return_value={"id": 1})