SPB Git forge

spb/home-ka

Public
10commits 1branches 0releases
793.0 KBsize
maindefault branch
20 days agolast push
Python 49.6% TypeScript 25.5% CSS 24.1%
3.2 KB · 77 lines python
Raw Blame History
1# -----------------------------------------------------------------------------2# Home-Ka — US real-estate aggregator (Groupe KA)3# Author: Simon-Pierre Boucher — contact@spboucher.ai4# connectors/json/feed.py : generic JSON API connector — partner APIs,5# brokerage internal search APIs, WordPress REST (wp-json) listing CPTs.6# Config-driven; pagination by page parameter or by next-link path.7#8#   {"id": "acme_api", "connector_type": "json", "config": {9#       "url": "https://www.acmerealty.com/api/listings?page={page}",10#       "method": "GET", "headers": {}, "body": null,11#       "items_path": "data.results",          # dot path to the record list12#       "next_path": "data.next",              # OR page-based: start_page/max_pages13#       "start_page": 1, "max_pages": 50, "stop_when_empty": true,14#       "mapping": {"external_id": "id", "url": "permalink",15#                   "list_price": "price", "images": "photos[].url", ...},16#       "static": {"state": "CO"}}}17# -----------------------------------------------------------------------------18from __future__ import annotations1920from ..base import BaseConnector21from .._maputil import apply_mapping, dig22from ...schema import Listing232425class JSONFeedConnector(BaseConnector):26    family = "json"27    request_delay = 0.52829    def fetch(self) -> list[Listing]:30        cfg = self.config31        mapping = cfg.get("mapping") or {}32        static = cfg.get("static") or {}33        items_path = cfg.get("items_path", "")34        headers = cfg.get("headers") or {}35        out: list[Listing] = []36        seen: set[str] = set()3738        url_tpl = cfg.get("url", "")39        page = int(cfg.get("start_page", 1))40        max_pages = int(cfg.get("max_pages", 100))41        next_url: str | None = None4243        for _ in range(max_pages):44            url = next_url or url_tpl.format(page=page)45            if cfg.get("method", "GET").upper() == "POST":46                body = cfg.get("body")47                if isinstance(body, dict):48                    body = {k: (str(v).format(page=page) if isinstance(v, str) else v)49                            for k, v in body.items()}50                resp = self.post(url, json=body, headers=headers)51            else:52                resp = self.get(url, headers=headers)53            data = resp.json()54            items = dig(data, items_path) if items_path else data55            if not isinstance(items, list):56                items = [items] if isinstance(items, dict) else []57            new = 058            for rec in items:59                if not isinstance(rec, dict):60                    continue61                lst = apply_mapping(self.source_id, rec, mapping, static)62                if lst is not None and lst.uid not in seen:63                    seen.add(lst.uid)64                    out.append(lst)65                    new += 166            if cfg.get("next_path"):67                next_url = dig(data, cfg["next_path"])68                if not next_url:69                    break70            else:71                page += 172                if cfg.get("stop_when_empty", True) and new == 0:73                    break74                if "{page}" not in url_tpl:75                    break76        return out77