Repository: home-assistant/core
File: homeassistant/helpers/config_entry_oauth2_flow.py (Python)

Code before:
        )

        return {**token, **new_token}

    async def _token_request(self, data: dict) -> dict:
        """Make a token request.

        Raises OAuth2TokenRequestError on token request failure.
        """
        session = async_get_clientsession(self.hass)

        data["client_id"] = self.client_id
        if self.client_secret:
            data["client_secret"] = self.client_secret

        _LOGGER.debug("Sending token request to %s", self.token_url)

        try:
            resp = await session.post(self.token_url, data=data)
            if resp.status >= 400:
                error_body = ""
                try:
                    error_body = await resp.text()
                    error_data = json.loads(error_body)
                    detail = error_data.get("error_description") or "unknown error"
                except ClientError, json.JSONDecodeError, ValueError:
                    detail = error_body[:200] if error_body else "unknown error"
                _LOGGER.debug(
                    "Token request for %s failed (%s): %s",
                    self.domain,
                    resp.status,
                    detail,
                )
            resp.raise_for_status()
        except ClientResponseError as err:
            if err.status == HTTPStatus.TOO_MANY_REQUESTS or 500 <= err.status <= 599:
                # Recoverable error
                raise OAuth2TokenRequestTransientError(
                    request_info=err.request_info,
                    history=err.history,
                    status=err.status,
                    message=err.message,
                    headers=err.headers,
                    domain=self._domain,
                ) from err
            if 400 <= err.status <= 499:
                # Non-recoverable error
                raise OAuth2TokenRequestReauthError(
                    request_info=err.request_info,
                    history=err.history,
                    status=err.status,

Review comment (suggestion):
I believe JSON decode errors inherit from ValueError.

https://docs.python.org/3/library/json.html#json.JSONDecodeError



Code after:
        )

        return {**token, **new_token}

    async def _token_request(self, data: dict) -> dict:
        """Make a token request.

        Raises OAuth2TokenRequestError on token request failure.
        """
        session = async_get_clientsession(self.hass)

        data["client_id"] = self.client_id
        if self.client_secret:
            data["client_secret"] = self.client_secret

        _LOGGER.debug("Sending token request to %s", self.token_url)

        try:
            resp = await session.post(self.token_url, data=data)
            if resp.status >= 400:
                error_body = ""
                try:
                    error_body = await resp.text()
                    error_data = json.loads(error_body)
                    error_code = error_data.get("error", "unknown error")
                    error_description = error_data.get("error_description")
                    detail = (
                        f"{error_code}: {error_description}"
                        if error_description
                        else error_code
                    )
                except ClientError, ValueError, AttributeError:
                    detail = error_body[:200] if error_body else "unknown error"
                _LOGGER.debug(
                    "Token request for %s failed (%s): %s",
                    self.domain,
                    resp.status,
                    detail,
                )
            resp.raise_for_status()
        except ClientResponseError as err:
            if err.status == HTTPStatus.TOO_MANY_REQUESTS or 500 <= err.status <= 599:
                # Recoverable error
                raise OAuth2TokenRequestTransientError(
                    request_info=err.request_info,
                    history=err.history,
                    status=err.status,
                    message=err.message,
                    headers=err.headers,
                    domain=self._domain,
                ) from err