BuyingAgent/backend/app/sources/geizhals/adapter.py
Lars b276758ee1
All checks were successful
Deploy Development / deploy (push) Successful in 1m19s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-checks (push) Successful in 28s
Stelle den Frontend-MVP und die v0.3-API auf develop bereit, damit Dev API und PWA zusammen deployen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-04 13:04:59 +02:00

82 lines
3.2 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from urllib.parse import parse_qs, urlparse
from app.discovery.enums import FilterType
from app.errors import AppError
FIXTURE_DIR = Path(__file__).resolve().parents[3] / "fixtures" / "geizhals"
class GeizhalsAdapter:
source_key = "geizhals"
def __init__(self, fixture_dir: Path | None = None, live: bool = False) -> None:
self.fixture_dir = fixture_dir or FIXTURE_DIR
self.live = live
def discover_taxonomy(self, category_ref: str) -> dict[str, Any]:
payload = self._load(f"{category_ref}-taxonomy.json")
return payload
def build_query(self, criteria: list[dict[str, Any]], mappings: list[dict[str, Any]]) -> dict[str, Any]:
from app.discovery.urlgen import generate_search_url
category_url = f"https://geizhals.de/?cat={self._category(mappings, criteria)}"
url, internal_only = generate_search_url(
category_url=category_url,
criteria_items=criteria,
mappings=mappings,
)
return {"url": url, "internal_only": internal_only}
def fetch_results(self, query: dict[str, Any], pagination: dict[str, Any]) -> dict[str, Any]:
if self.live:
raise AppError(422, "LIVE_FETCH_DISABLED", "live Geizhals fetch is not enabled in this process")
page = int(pagination.get("page") or 1)
category = parse_qs(urlparse(query["url"]).query).get("cat", ["nb"])[0]
return self._load(f"{category}-search-page{page}.json")
def parse_results(self, response: dict[str, Any]) -> list[dict[str, Any]]:
return list(response.get("items") or [])
def normalize_result(self, result: dict[str, Any]) -> dict[str, Any]:
attrs = result.get("normalized_attributes") or {}
return {
"source_external_id": result.get("source_external_id"),
"source_url": result.get("source_url"),
"source_title": result.get("source_title"),
"manufacturer": result.get("manufacturer"),
"model": result.get("model"),
"manufacturer_sku": result.get("manufacturer_sku"),
"gtin_ean": result.get("gtin_ean"),
"best_price": result.get("best_price"),
"currency": result.get("currency") or "EUR",
"availability": result.get("availability") or "unknown",
"seller_count": result.get("seller_count"),
"raw_attributes": result.get("raw_attributes") or {},
"normalized_attributes": attrs,
}
def _load(self, name: str) -> dict[str, Any]:
path = self.fixture_dir / name
if not path.exists():
raise AppError(404, "FIXTURE_NOT_FOUND", f"geizhals fixture missing: {name}")
return json.loads(path.read_text(encoding="utf-8"))
def _category(self, mappings: list[dict[str, Any]], criteria: list[dict[str, Any]]) -> str:
for mapping in mappings:
if mapping.get("category_key"):
return str(mapping["category_key"])
return "nb"
def filter_type(raw: str) -> str:
try:
return FilterType(raw).value
except ValueError:
return FilterType.UNKNOWN.value