Skip to content
Draft
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
41 changes: 41 additions & 0 deletions src/py/mat3ra/api_client/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Dict, Tuple

# Mapping of HTTP status codes to (display_text, suggestion)
HTTP_ERROR_MAP: Dict[int, Tuple[str, str]] = {
400: (
"Bad Request",
"Check your request parameters and data.",
),
401: (
"Unauthorized",
"Check your authentication token or log in again.",
),
403: (
"Forbidden",
"You do not have permission to access this resource. Set the correct project or check your account permissions.",
),
404: (
"Not Found",
"The requested resource does not exist. Check the ID or path.",
),
409: (
"Conflict",
"A resource with this identifier already exists.",
),
422: (
"Unprocessable Entity",
"The request data is invalid. Check your input.",
),
429: (
"Too Many Requests",
"You have exceeded your quota. Update your quota or try again later.",
),
500: (
"Internal Server Error",
"An error occurred on the server. Contact support if the problem persists.",
),
503: (
"Service Unavailable",
"The service is temporarily unavailable. Try again later.",
),
}
12 changes: 11 additions & 1 deletion src/py/mat3ra/api_client/utils/http.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import requests
import urllib.parse

from mat3ra.api_client.settings import HTTP_ERROR_MAP


class BaseConnection(object):
"""
Expand Down Expand Up @@ -32,7 +34,15 @@ def request(self, method, url, params=None, data=None, headers=None):
params (dict): URL parameters to append to the URL.
"""
self.response = self.session.request(method=method.lower(), url=url, params=params, data=data, headers=headers)
self.response.raise_for_status()
try:
self.response.raise_for_status()
except requests.HTTPError as e:
status_code = self.response.status_code
display_text, suggestion = HTTP_ERROR_MAP.get(status_code, ("HTTP Error", ""))
message = f"Error {status_code}: {display_text}."
if suggestion:
message += f" {suggestion}"
raise requests.HTTPError(message, response=self.response) from e

def get_response(self):
"""
Expand Down
61 changes: 61 additions & 0 deletions tests/py/unit/test_httpBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
API_VERSION_2 = "2018-10-2"
HTTP_STATUS_UNAUTHORIZED = 401
HTTP_REASON_UNAUTHORIZED = "Unauthorized"
HTTP_STATUS_INTERNAL_SERVER_ERROR = 500
HTTP_REASON_INTERNAL_SERVER_ERROR = "Internal Server Error"
HTTP_STATUS_TOO_MANY_REQUESTS = 429
HTTP_REASON_TOO_MANY_REQUESTS = "Too Many Requests"
HTTP_STATUS_FORBIDDEN = 403
HTTP_REASON_FORBIDDEN = "Forbidden"
HTTP_STATUS_UNKNOWN = 418
HTTP_REASON_UNKNOWN = "I'm a Teapot"
EMPTY_CONTENT = ""
TEST_ENTITY_ID = "28FMvD5knJZZx452H"
EMPTY_USERNAME = ""
Expand Down Expand Up @@ -41,3 +49,56 @@ def test_raise_http_error(self, mock_request):
with self.assertRaises(HTTPError):
conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True)
conn.request("POST", "login", data={"username": EMPTY_USERNAME, "password": EMPTY_PASSWORD})

@mock.patch("requests.sessions.Session.request")
def test_http_error_message_known_status(self, mock_request):
mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_UNAUTHORIZED,
reason=HTTP_REASON_UNAUTHORIZED)
with self.assertRaises(HTTPError) as ctx:
conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True)
conn.request("POST", "login", data={"username": EMPTY_USERNAME, "password": EMPTY_PASSWORD})
self.assertIn("Error 401", str(ctx.exception))
self.assertIn("Unauthorized", str(ctx.exception))
self.assertIn("authentication token", str(ctx.exception))

@mock.patch("requests.sessions.Session.request")
def test_http_error_message_500(self, mock_request):
mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_INTERNAL_SERVER_ERROR,
reason=HTTP_REASON_INTERNAL_SERVER_ERROR)
with self.assertRaises(HTTPError) as ctx:
conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True)
conn.request("POST", "jobs/id/submit")
self.assertIn("Error 500", str(ctx.exception))
self.assertIn("Internal Server Error", str(ctx.exception))
self.assertIn("Contact support", str(ctx.exception))

@mock.patch("requests.sessions.Session.request")
def test_http_error_message_429_quota(self, mock_request):
mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_TOO_MANY_REQUESTS,
reason=HTTP_REASON_TOO_MANY_REQUESTS)
with self.assertRaises(HTTPError) as ctx:
conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True)
conn.request("POST", "materials")
self.assertIn("Error 429", str(ctx.exception))
self.assertIn("quota", str(ctx.exception))

@mock.patch("requests.sessions.Session.request")
def test_http_error_message_403_project(self, mock_request):
mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_FORBIDDEN,
reason=HTTP_REASON_FORBIDDEN)
with self.assertRaises(HTTPError) as ctx:
conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True)
conn.request("GET", "workflows")
self.assertIn("Error 403", str(ctx.exception))
self.assertIn("project", str(ctx.exception))

@mock.patch("requests.sessions.Session.request")
def test_http_error_message_unknown_status(self, mock_request):
mock_request.return_value = self.mock_response(EMPTY_CONTENT, HTTP_STATUS_UNKNOWN,
reason=HTTP_REASON_UNKNOWN)
with self.assertRaises(HTTPError) as ctx:
conn = Connection(self.host, self.port, version=API_VERSION_1, secure=True)
conn.request("GET", "materials")
self.assertIn("Error 418", str(ctx.exception))
self.assertIn("HTTP Error", str(ctx.exception))