0.2.3 #99
4 changed files with 88 additions and 14 deletions
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
with ResponseHandler() as response_handler:
|
||||||
|
try:
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
|
response_handler.handle_response(response)
|
||||||
with ResponseHandler(response) as response_handler:
|
except RequestException as exception:
|
||||||
response_handler.handle_response()
|
response_handler.handle_exception(exception)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue