Handle request exceptions

This commit is contained in:
Sonny 2019-07-01 11:38:23 +02:00
parent cfd064ec85
commit ed658c4dfd
4 changed files with 88 additions and 14 deletions

View file

@ -26,3 +26,7 @@ class StreamForbiddenException(StreamException):
class StreamParseException(StreamException): class StreamParseException(StreamException):
message = "Stream could not be parsed" message = "Stream could not be parsed"
class StreamConnectionError(StreamException):
message = "A connection to the stream could not be made"

View file

@ -1,9 +1,18 @@
from typing import ContextManager from typing import ContextManager
from requests import Response from requests import Response
from requests.exceptions import ConnectionError as RequestConnectionError
from requests.exceptions import (
HTTPError,
RequestException,
SSLError,
TooManyRedirects,
)
from newsreader.news.collection.exceptions import ( from newsreader.news.collection.exceptions import (
StreamConnectionError,
StreamDeniedException, StreamDeniedException,
StreamException,
StreamForbiddenException, StreamForbiddenException,
StreamNotFoundException, StreamNotFoundException,
StreamTimeOutException, StreamTimeOutException,
@ -11,24 +20,32 @@ from newsreader.news.collection.exceptions import (
class ResponseHandler: class ResponseHandler:
message_mapping = { status_code_mapping = {
404: StreamNotFoundException, 404: StreamNotFoundException,
401: StreamDeniedException, 401: StreamDeniedException,
403: StreamForbiddenException, 403: StreamForbiddenException,
408: StreamTimeOutException, 408: StreamTimeOutException,
} }
def __init__(self, response: Response) -> None: exception_mapping = {RequestConnectionError: StreamConnectionError}
self.response = response
def __enter__(self) -> ContextManager: def __enter__(self) -> ContextManager:
return self return self
def handle_response(self) -> None: def handle_response(self, response) -> None:
status_code = self.response.status_code status_code = response.status_code
if status_code in self.message_mapping: if status_code in self.status_code_mapping:
raise self.message_mapping[status_code] raise self.status_code_mapping[status_code]
def handle_exception(self, exception):
try:
stream_exception = self.exception_mapping[type(exception)]
except KeyError:
stream_exception = StreamException
message = getattr(exception, "message", str(exception))
raise stream_exception(message=message) from exception
def __exit__(self, *args, **kwargs) -> None: def __exit__(self, *args, **kwargs) -> None:
self.response = None pass

View file

@ -1,9 +1,19 @@
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from requests.exceptions import ConnectionError as RequestConnectionError
from requests.exceptions import (
HTTPError,
RequestException,
SSLError,
TooManyRedirects,
)
from django.test import TestCase from django.test import TestCase
from newsreader.news.collection.exceptions import ( from newsreader.news.collection.exceptions import (
StreamConnectionError,
StreamDeniedException, StreamDeniedException,
StreamException,
StreamForbiddenException, StreamForbiddenException,
StreamNotFoundException, StreamNotFoundException,
StreamTimeOutException, StreamTimeOutException,
@ -55,3 +65,43 @@ class FetchTestCase(TestCase):
with self.assertRaises(StreamTimeOutException): with self.assertRaises(StreamTimeOutException):
fetch(url) fetch(url)
def test_raises_stream_error_on_ssl_error(self):
self.mocked_get.side_effect = SSLError
url = "https://www.bbc.co.uk/news"
with self.assertRaises(StreamException):
fetch(url)
def test_raises_stream_error_on_connection_error(self):
self.mocked_get.side_effect = RequestConnectionError
url = "https://www.bbc.co.uk/news"
with self.assertRaises(StreamConnectionError):
fetch(url)
def test_raises_stream_error_on_http_error(self):
self.mocked_get.side_effect = HTTPError
url = "https://www.bbc.co.uk/news"
with self.assertRaises(StreamException):
fetch(url)
def test_raises_stream_error_on_request_exception(self):
self.mocked_get.side_effect = RequestException
url = "https://www.bbc.co.uk/news"
with self.assertRaises(StreamException):
fetch(url)
def test_raises_stream_error_on_too_many_redirects(self):
self.mocked_get.side_effect = TooManyRedirects
url = "https://www.bbc.co.uk/news"
with self.assertRaises(StreamException):
fetch(url)

View file

@ -1,9 +1,10 @@
from datetime import datetime, tzinfo from datetime import datetime, tzinfo
from time import mktime, struct_time from time import mktime, struct_time
from typing import Optional, Tuple from typing import Tuple
import requests import requests
from requests.exceptions import RequestException
from requests.models import Response from requests.models import Response
from django.utils import timezone from django.utils import timezone
@ -20,10 +21,12 @@ def build_publication_date(dt: struct_time, tz: tzinfo) -> Tuple:
return True, published_parsed return True, published_parsed
def fetch(url: str) -> Optional[Response]: def fetch(url: str) -> Response:
response = requests.get(url) with ResponseHandler() as response_handler:
try:
with ResponseHandler(response) as response_handler: response = requests.get(url)
response_handler.handle_response() response_handler.handle_response(response)
except RequestException as exception:
response_handler.handle_exception(exception)
return response return response