{"record":{"id":"586bea9a7db4362d","repo":"redis/redis-py","slug":"provide-either-json-body-or-data-not-both","errorCode":null,"errorMessage":"Provide either json_body or data, not both.","messagePattern":"Provide either json_body or data, not both\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"redis/http/http_client.py","lineNumber":356,"sourceCode":"        resp = self.request(\n            method=method,\n            path=path,\n            params=params,\n            headers=headers,\n            body=body,\n            timeout=timeout,\n        )\n        if not (200 <= resp.status < 400):\n            raise HttpError(resp.status, resp.url, resp.text())\n        if expect_json:\n            return resp.json()\n        return resp\n\n    def _prepare_body(\n        self, json_body: Optional[Any] = None, data: Optional[Union[bytes, str]] = None\n    ) -> Optional[Union[bytes, str]]:\n        if json_body is not None and data is not None:\n            raise ValueError(\"Provide either json_body or data, not both.\")\n        if json_body is not None:\n            return json.dumps(json_body, ensure_ascii=False, separators=(\",\", \":\"))\n        return data\n\n    def _build_url(\n        self,\n        path: str,\n        params: Optional[\n            Mapping[str, Union[None, str, int, float, bool, list, tuple]]\n        ] = None,\n    ) -> str:\n        url = urljoin(self.base_url or \"\", path)\n        if params:\n            # urlencode with doseq=True supports list/tuple values\n            query = urlencode(\n                {k: v for k, v in params.items() if v is not None}, doseq=True\n            )\n            separator = \"&\" if (\"?\" in url) else \"?\"","sourceCodeStart":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/http/http_client.py#L338-L374","documentation":"_prepare_body (redis/http/http_client.py:356) raises ValueError when both json_body and data are supplied to a POST/PUT/PATCH. The two parameters are mutually exclusive: json_body is serialized to compact JSON, data is sent as raw bytes/str. Accepting both would make the on-wire body ambiguous, so the client refuses rather than guess.","triggerScenarios":"Calling http.post(path, json_body={'a': 1}, data=b'raw') ; passing both because a wrapper forwards a generic payload dict alongside a pre-serialized body; mixing the JSON helper with a manual bytes body.","commonSituations":"A generic request helper that accepts both forms and forgets to clear one; refactoring from data= to json_body= and leaving the old argument in place; copy-paste between calls.","solutions":["Pass exactly one of json_body (for JSON) or data (for raw bytes/str).","If you have a dict, prefer json_body; if you have pre-serialized bytes, use data.","In wrapper functions, normalize to one form before calling HttpClient and drop the other."],"exampleFix":"// before\nhttp.post('/items', json_body=payload, data=json.dumps(payload))\n// after\nhttp.post('/items', json_body=payload)","handlingStrategy":"validation","validationCode":"assert (json_body is None) or (data is None), 'pass json_body or data, not both'","typeGuard":"def exactly_one_body(json_body, data) -> bool:\n    return (json_body is not None) ^ (data is not None) or (json_body is None and data is None)","tryCatchPattern":null,"preventionTips":["Pass json_body for JSON payloads, data for raw bytes/str — never both.","In wrappers, normalize to one form before calling HttpClient.","When migrating from data= to json_body=, remove the old argument."],"tags":["http","validation","request-body","mutual-exclusion"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}