{"record":{"id":"ada21b51f3b354e9","repo":"ZhuLinsen/daily_stock_analysis","slug":"invalid-newsnow-response-expected-object","errorCode":null,"errorMessage":"invalid NewsNow response: expected object","messagePattern":"invalid NewsNow response: expected object","errorType":"exception","errorClass":"IntelligenceServiceError","httpStatus":400,"severity":"warning","filePath":"src/services/intelligence_service.py","lineNumber":612,"sourceCode":"                raise IntelligenceServiceError(\"source url must not target private or local network addresses\")\n\n    def _parse_feed(self, content: bytes, *, source_name: str, limit: int) -> List[FeedEntry]:\n        try:\n            root = ET.fromstring(content)\n        except ET.ParseError as exc:\n            raise IntelligenceServiceError(f\"invalid RSS/Atom feed: {exc}\") from exc\n        tag = self._strip_ns(root.tag).lower()\n        if tag == \"rss\":\n            nodes = root.findall(\"./channel/item\")\n            return [entry for entry in (self._parse_rss_item(node, source_name) for node in nodes[:limit]) if entry]\n        if tag == \"feed\":\n            nodes = root.findall(\"./{*}entry\") or root.findall(\"./entry\")\n            return [entry for entry in (self._parse_atom_entry(node, source_name) for node in nodes[:limit]) if entry]\n        raise IntelligenceServiceError(\"unsupported feed format; expected RSS or Atom\")\n\n    def _parse_newsnow_payload(self, payload: Any, *, source_name: str, limit: int) -> List[FeedEntry]:\n        if not isinstance(payload, dict):\n            raise IntelligenceServiceError(\"invalid NewsNow response: expected object\")\n        items = payload.get(\"items\")\n        if not isinstance(items, list):\n            raise IntelligenceServiceError(\"invalid NewsNow response: missing items\")\n        entries = []\n        for item in items[:limit]:\n            if not isinstance(item, dict):\n                continue\n            extra = item.get(\"extra\") if isinstance(item.get(\"extra\"), dict) else {}\n            published_raw = item.get(\"pubDate\") or extra.get(\"date\")\n            entries.append(self._build_entry(\n                str(item.get(\"title\") or \"\"),\n                str(extra.get(\"info\") or extra.get(\"hover\") or \"\"),\n                str(item.get(\"url\") or item.get(\"mobileUrl\") or \"\"),\n                source_name,\n                self._parse_datetime_or_timestamp(published_raw),\n            ))\n        return [entry for entry in entries if entry]\n","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/services/intelligence_service.py#L594-L630","documentation":"IntelligenceServiceError('invalid NewsNow response: expected object') is raised by _parse_newsnow_payload when the decoded JSON payload from a NewsNow endpoint is not a dict (JSON object). The NewsNow parser expects a top-level object containing an 'items' list; a top-level array, string, number, or null fails this check.","triggerScenarios":"Calling a NewsNow-type source whose API returns a top-level JSON array of items, a bare string/number, 'null', or an error payload like \"unauthorized\" instead of an object.","commonSituations":"Wrong NewsNow endpoint variant (some deployments return a bare array), an API version change on self-hosted NewsNow instances, an auth failure returning a plain-text body that happens to decode as a JSON scalar, or a proxy returning a JSON-encoded error string.","solutions":["Inspect the raw response: curl -sL <newsnow-url> | python -m json.tool and check the top-level type","If the endpoint returns {'items': [...]}, make sure nothing in the chain (proxy, transform) unwraps it into an array","Point the source at the correct NewsNow endpoint that returns the documented {items: [...]} shape","If the body is an auth/error message, fix credentials or the URL before parsing"],"exampleFix":"# before: endpoint returns a bare array [ {...}, {...} ]\nurl = \"https://newsnow.example.com/api/sitemap?id=36kr\"\n\n# after: endpoint that returns {\"items\": [...]}\nurl = \"https://newsnow.example.com/api/s?id=36kr\"","handlingStrategy":"validation","validationCode":"import json\n\ndef is_newsnow_object(payload_text: str) -> bool:\n    try:\n        return isinstance(json.loads(payload_text), dict)\n    except json.JSONDecodeError:\n        return False","typeGuard":"def is_newsnow_payload(payload: Any) -> bool:\n    return isinstance(payload, dict) and isinstance(payload.get(\"items\"), list)","tryCatchPattern":"try:\n    entries = svc._parse_newsnow_payload(payload, source_name=src, limit=20)\nexcept IntelligenceServiceError as exc:\n    if \"expected object\" in str(exc):\n        logger.warning(\"NewsNow endpoint shape changed for %s\", src)","preventionTips":["Pin the NewsNow API version you integrate against","Contract-test the endpoint shape ({\"items\": [...]}) in CI against a recorded fixture","Treat shape changes as upstream API drift, not transient errors — do not retry unchanged"],"tags":["feed","json","parsing","newsnow","api-contract"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}