82 lines
3.2 KiB
Python
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
|