diff --git a/telegram/request/_baserequest.py b/telegram/request/_baserequest.py index 9d0939ada..55d900f14 100644 --- a/telegram/request/_baserequest.py +++ b/telegram/request/_baserequest.py @@ -68,6 +68,11 @@ class BaseRequest( finally: await request_object.shutdown() + Tip: + JSON encoding and decoding is done with the standard library's :mod:`json` by default. + To use a custom library for this, you can override :meth:`parse_json_payload` and implement + custom logic to encode the keys of :attr:`telegram.request.RequestData.parameters`. + .. versionadded:: 20.0 """ @@ -163,7 +168,7 @@ class BaseRequest( connect_timeout=connect_timeout, pool_timeout=pool_timeout, ) - json_data = self._parse_json_response(result) + json_data = self.parse_json_payload(result) # For successful requests, the results are in the 'result' entry # see https://core.telegram.org/bots/api#making-requests return json_data["result"] @@ -285,7 +290,7 @@ class BaseRequest( # 200-299 range are HTTP success statuses return payload - response_data = self._parse_json_response(payload) + response_data = self.parse_json_payload(payload) description = response_data.get("description") if description: @@ -325,16 +330,24 @@ class BaseRequest( raise NetworkError(f"{message} ({code})") @staticmethod - def _parse_json_response(json_payload: bytes) -> JSONDict: - """Try and parse the JSON returned from Telegram. + def parse_json_payload(payload: bytes) -> JSONDict: + """Parse the JSON returned from Telegram. + + Tip: + By default, this method uses the standard library's :func:`json.loads` and + ``errors="replace"`` in :meth:`bytes.decode`. + You can override it to customize either of these behaviors. + + Args: + payload (:obj:`bytes`): The UTF-8 encoded JSON payload as returned by Telegram. Returns: dict: A JSON parsed as Python dict with results. Raises: - TelegramError: If the data could not be json_loaded + TelegramError: If loading the JSON data failed """ - decoded_s = json_payload.decode("utf-8", "replace") + decoded_s = payload.decode("utf-8", "replace") try: return json.loads(decoded_s) except ValueError as exc: diff --git a/telegram/request/_requestdata.py b/telegram/request/_requestdata.py index 088d1e93b..6d78bd174 100644 --- a/telegram/request/_requestdata.py +++ b/telegram/request/_requestdata.py @@ -64,6 +64,11 @@ class RequestData: def json_parameters(self) -> Dict[str, str]: """Gives the parameters as mapping of parameter name to the respective JSON encoded value. + + Tip: + By default, this property uses the standard library's :func:`json.dumps`. + To use a custom library for JSON encoding, you can directly encode the keys of + :attr:`parameters` - note that string valued keys should not be JSON encoded. """ return { param.name: param.json_value @@ -96,7 +101,13 @@ class RequestData: @property def json_payload(self) -> bytes: - """The parameters as UTF-8 encoded JSON payload.""" + """The :attr:`parameters` as UTF-8 encoded JSON payload. + + Tip: + By default, this property uses the standard library's :func:`json.dumps`. + To use a custom library for JSON encoding, you can directly encode the keys of + :attr:`parameters` - note that string valued keys should not be JSON encoded. + """ return json.dumps(self.json_parameters).encode("utf-8") @property diff --git a/tests/test_request.py b/tests/test_request.py index 87035173f..699d69397 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -129,6 +129,9 @@ class TestRequest: monkeypatch.setattr(httpx_request, "do_request", mocker_factory(response=server_response)) assert await httpx_request.post(None, None, None) == "test_string�" + # Explicitly call `parse_json_payload` here is well so that this public method is covered + # not only implicitly. + assert httpx_request.parse_json_payload(server_response) == {"result": "test_string�"} async def test_illegal_json_response(self, monkeypatch, httpx_request: HTTPXRequest): # for proper JSON it should be `"result":` instead of `result:`