Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion notion_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,20 @@ def _parse_response(self, response: Response) -> Any:
try:
body = error.response.json()
code = body.get("code")
additional_data = body.get("additional_data")
request_id = body.get("request_id")
except json.JSONDecodeError:
code = None
additional_data = None
request_id = None
if code and is_api_error_code(code):
raise APIResponseError(response, body["message"], code)
raise APIResponseError(
response,
body["message"],
code,
additional_data,
request_id,
)
raise HTTPResponseError(error.response)

body = response.json()
Expand Down
13 changes: 11 additions & 2 deletions notion_client/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from enum import Enum
from typing import Optional
from typing import Any, Dict, Optional

import httpx

Expand Down Expand Up @@ -95,12 +95,21 @@ class APIResponseError(HTTPResponseError):
"""An error raised by Notion API."""

code: APIErrorCode
additional_data: Optional[Dict[str, Any]]
request_id: Optional[str]

def __init__(
self, response: httpx.Response, message: str, code: APIErrorCode
self,
response: httpx.Response,
message: str,
code: APIErrorCode,
additional_data: Optional[Dict[str, Any]] = None,
request_id: Optional[str] = None,
) -> None:
super().__init__(response, message)
self.code = code
self.additional_data = additional_data
self.request_id = request_id


def is_api_error_code(code: str) -> bool:
Expand Down
38 changes: 38 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,44 @@ async def test_api_async_request_bad_request_error(async_client):
await async_client.request(STATUS_PAGE_BAD_REQUEST, "GET")


@pytest.mark.vcr()
def test_api_response_error_request_id(client):
with pytest.raises(APIResponseError) as exc_info:
client.request("/invalid", "GET")

error = exc_info.value
assert isinstance(error.request_id, str)


@pytest.mark.vcr()
async def test_async_api_response_error_request_id(async_client):
with pytest.raises(APIResponseError) as exc_info:
await async_client.request("/invalid", "GET")

error = exc_info.value
assert isinstance(error.request_id, str)


@pytest.mark.vcr()
def test_api_response_error_additional_data(client):
with pytest.raises(APIResponseError) as exc_info:
client.request("/users", "GET", auth="invalid-token")

error = exc_info.value
if error.additional_data is not None:
assert isinstance(error.additional_data, dict)


@pytest.mark.vcr()
async def test_async_api_response_error_additional_data(async_client):
with pytest.raises(APIResponseError) as exc_info:
await async_client.request("/users", "GET", auth="invalid-token")

error = exc_info.value
if error.additional_data is not None:
assert isinstance(error.additional_data, dict)


async def test_is_api_error_code():
error_code = "unauthorized"
assert is_api_error_code(error_code)
Expand Down