From 567762b37483843b591b777acb676f9c79394d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Strausmann?= Date: Sun, 14 Jun 2026 14:35:43 +0000 Subject: [PATCH] refactor(snipeit): typisiere response payload als TypedDict statt dict[str, Any] (#67) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit #67 Finding: das alte `dict[str, Any]` hat das Hub-mypy-strict-Verbot von `Any` in Plugin-Responses verletzt. Refactor zu `_SnipeITAssetResponse` TypedDict mit `total=False`: class _SnipeITAssetResponse(TypedDict, total=False): id: int asset_tag: str | None name: str | None serial: str | None Beschränkt auf die Felder die `_payload_to_label` tatsächlich konsumiert. Verhalten 1:1 (TypedDict wird zur Laufzeit als dict behandelt, nur mypy sieht die Struktur). Die Verwendung von `.get(...)` bleibt unverändert. Tests: - tests/unit/integrations/test_snipeit_plugin.py — 10 passed - mypy ✓ ruff ✓ Refs #67 Audit-Punkt: "PR #51 — snipeit/plugin.py:67 — Response payload dict[str, Any]" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/app/integrations/snipeit/plugin.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/backend/app/integrations/snipeit/plugin.py b/backend/app/integrations/snipeit/plugin.py index b7b277ff..5be795b4 100644 --- a/backend/app/integrations/snipeit/plugin.py +++ b/backend/app/integrations/snipeit/plugin.py @@ -8,7 +8,7 @@ from __future__ import annotations -from typing import Any +from typing import TypedDict from urllib.parse import quote import httpx @@ -17,6 +17,22 @@ from app.services.errors import AppLookupNotFoundError +class _SnipeITAssetResponse(TypedDict, total=False): + """Subset of Snipe-IT `/api/v1/hardware/bytag/{tag}` response we actually + read. Strikt-typisiert statt `dict[str, Any]` — beschränkt sich auf die + Felder die `_payload_to_label` konsumiert. `total=False` weil alle Felder + außer `id` optional sind (User-Mapping kann Snipe-IT-fields ausblenden). + + Audit #67 / PR #51 Finding: das alte `dict[str, Any]` hat das Hub-mypy- + strict-Verbot von `Any` in Plugin-Responses verletzt. + """ + + id: int + asset_tag: str | None + name: str | None + serial: str | None + + class SnipeITNotFoundError(AppLookupNotFoundError): """Raised when no Snipe-IT asset matches the given tag.""" @@ -72,10 +88,10 @@ async def lookup(self, asset_tag: str) -> LabelData: # decide whether to treat them as configuration errors vs transient failures. response.raise_for_status() - payload: dict[str, Any] = response.json() + payload: _SnipeITAssetResponse = response.json() return self._payload_to_label(payload, asset_tag) - def _payload_to_label(self, payload: dict[str, Any], asset_tag: str) -> LabelData: + def _payload_to_label(self, payload: _SnipeITAssetResponse, asset_tag: str) -> LabelData: asset_id = payload.get("id") if asset_id is None: raise ValueError(f"Snipe-IT response for {asset_tag!r} is missing required field 'id'")