Skip to content

Commit

Permalink
refactor: add ChaturbateUtils class for Chaturbate poller utility fun…
Browse files Browse the repository at this point in the history
…ctions
  • Loading branch information
MountainGod2 committed Sep 26, 2024
1 parent 40c13b8 commit 9779448
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 88 deletions.
1 change: 1 addition & 0 deletions src/chaturbate_poller/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ async def start_polling( # pylint: disable=too-many-arguments # noqa: PLR0913
"""Start polling Chaturbate events."""
if verbose is True:
logging.getLogger("chaturbate_poller").setLevel(logging.DEBUG)

if not username or not token:
logging.error(
"CB_USERNAME and CB_TOKEN must be provided as arguments or environment variables."
Expand Down
84 changes: 8 additions & 76 deletions src/chaturbate_poller/chaturbate_client.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,15 @@
"""Chaturbate poller module."""

import logging
from logging.config import dictConfig
from types import TracebackType

import httpx
from backoff import constant, expo, on_exception
from backoff._typing import Details

from chaturbate_poller.constants import DEFAULT_BASE_URL, TESTBED_BASE_URL, HttpStatusCode
from chaturbate_poller.influxdb_client import InfluxDBHandler
from chaturbate_poller.logging_config import LOGGING_CONFIG
from chaturbate_poller.models import EventsAPIResponse

dictConfig(LOGGING_CONFIG)

logger = logging.getLogger(__name__)
"""Logger for the module."""


def backoff_handler(details: Details) -> None:
"""Handle backoff events.
Args:
details (Details): The backoff details.
"""
wait = details["wait"]
tries = details["tries"]
logger.info("Backing off %s seconds after %s tries", int(wait), int(tries))


def giveup_handler(details: Details) -> None:
"""Handle giveup events.
Args:
details (Details): The giveup details.
"""
tries = details.get("tries", 0)
exception = details.get("exception")

if exception and hasattr(exception, "response"):
response = exception.response
status_code = response.status_code
try:
response_dict = response.json()
status_text = response_dict.get("status", "Unknown error")
except ValueError:
status_text = "Error parsing response JSON"
else:
status_code = None
status_text = "No response available"

logger.error(
"Giving up after %s tries due to server error code %s: %s",
int(tries),
status_code,
status_text,
)
from chaturbate_poller.utils import ChaturbateUtils


class ChaturbateClient:
Expand Down Expand Up @@ -91,6 +44,7 @@ def __init__(
self.token = token
self._client: httpx.AsyncClient | None = None
self.influxdb_handler: InfluxDBHandler = InfluxDBHandler()
self.logger = logging.getLogger(__name__)

@property
def client(self) -> httpx.AsyncClient:
Expand Down Expand Up @@ -127,8 +81,8 @@ async def __aexit__(
jitter=None,
exception=httpx.ReadError,
max_tries=10,
on_giveup=giveup_handler,
on_backoff=backoff_handler,
on_giveup=ChaturbateUtils().giveup_handler,
on_backoff=ChaturbateUtils().backoff_handler,
logger=None,
)
@on_exception(
Expand All @@ -137,10 +91,10 @@ async def __aexit__(
base=1.25,
factor=5,
exception=httpx.HTTPStatusError,
giveup=lambda retry: not need_retry(retry),
on_giveup=giveup_handler,
giveup=lambda retry: not ChaturbateUtils().need_retry(retry),
on_giveup=ChaturbateUtils().giveup_handler,
max_tries=6,
on_backoff=backoff_handler,
on_backoff=ChaturbateUtils().backoff_handler,
logger=None,
raise_on_giveup=False,
)
Expand All @@ -158,7 +112,7 @@ async def fetch_events(self, url: str | None = None) -> EventsAPIResponse:
httpx.HTTPStatusError: For other HTTP errors.
"""
url = url or self._construct_url()
logger.debug("%s", url)
self.logger.debug("%s", url)
response = await self.client.get(url, timeout=None)
try:
response.raise_for_status()
Expand All @@ -178,25 +132,3 @@ def _construct_url(self) -> str:
"""
timeout_param = f"?timeout={self.timeout}" if self.timeout else ""
return f"{self.base_url.format(username=self.username, token=self.token)}{timeout_param}"


def need_retry(exception: Exception) -> bool:
"""Determine if the request should be retried based on the exception.
Args:
exception (Exception): The exception raised.
Returns:
bool: True if the request should be retried, False otherwise.
"""
if isinstance(exception, httpx.HTTPStatusError):
status_code = exception.response.status_code
if status_code in {
HttpStatusCode.INTERNAL_SERVER_ERROR,
HttpStatusCode.BAD_GATEWAY,
HttpStatusCode.SERVICE_UNAVAILABLE,
HttpStatusCode.GATEWAY_TIMEOUT,
HttpStatusCode.WEB_SERVER_IS_DOWN,
}:
return True
return False
75 changes: 75 additions & 0 deletions src/chaturbate_poller/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Utility functions for the Chaturbate poller."""

import logging

import httpx
from backoff._typing import Details

from chaturbate_poller.constants import HttpStatusCode


class ChaturbateUtils:
"""Utility functions for the Chaturbate poller."""

def __init__(self) -> None:
"""Initialize the utility class."""
self.logger = logging.getLogger(__name__)

def backoff_handler(self, details: Details) -> None:
"""Handle backoff events.
Args:
details (Details): The backoff details.
"""
wait = details["wait"]
tries = details["tries"]
self.logger.info("Backing off %s seconds after %s tries", int(wait), int(tries))

def giveup_handler(self, details: Details) -> None:
"""Handle giveup events.
Args:
details (Details): The giveup details.
"""
tries = details.get("tries", 0)
exception = details.get("exception")

if exception and hasattr(exception, "response"):
response = exception.response
status_code = response.status_code
try:
response_dict = response.json()
status_text = response_dict.get("status", "Unknown error")
except ValueError:
status_text = "Error parsing response JSON"
else:
status_code = None
status_text = "No response available"

self.logger.error(
"Giving up after %s tries due to server error code %s: %s",
int(tries),
status_code,
status_text,
)

def need_retry(self, exception: Exception) -> bool:
"""Determine if the request should be retried based on the exception.
Args:
exception (Exception): The exception raised.
Returns:
bool: True if the request should be retried, False otherwise.
"""
if isinstance(exception, httpx.HTTPStatusError):
status_code = exception.response.status_code
if status_code in {
HttpStatusCode.INTERNAL_SERVER_ERROR,
HttpStatusCode.BAD_GATEWAY,
HttpStatusCode.SERVICE_UNAVAILABLE,
HttpStatusCode.GATEWAY_TIMEOUT,
HttpStatusCode.WEB_SERVER_IS_DOWN,
}:
return True
return False
20 changes: 8 additions & 12 deletions tests/test_chaturbate_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,7 @@
from pydantic import ValidationError
from pytest_mock import MockerFixture

from chaturbate_poller.chaturbate_client import (
ChaturbateClient,
backoff_handler,
giveup_handler,
need_retry,
)
from chaturbate_poller.chaturbate_client import ChaturbateClient
from chaturbate_poller.constants import API_TIMEOUT, TESTBED_BASE_URL, HttpStatusCode
from chaturbate_poller.format_messages import format_message, format_user_event
from chaturbate_poller.logging_config import LOGGING_CONFIG, CustomFormatter
Expand All @@ -37,6 +32,7 @@
Tip,
User,
)
from chaturbate_poller.utils import ChaturbateUtils

USERNAME = "testuser"
"""str: The Chaturbate username."""
Expand Down Expand Up @@ -183,7 +179,7 @@ def test_backoff_handler(self, caplog) -> None: # noqa: ANN001
"""Test the backoff handler."""
caplog.set_level(logging.INFO)
# Providing required keys "wait" and "tries" in the details dict
backoff_handler(
ChaturbateUtils().backoff_handler(
{
"wait": 1.0,
"tries": 1,
Expand All @@ -198,7 +194,7 @@ def test_backoff_handler(self, caplog) -> None: # noqa: ANN001
def test_giveup_handler(self, caplog) -> None: # noqa: ANN001
"""Test the giveup handler."""
caplog.set_level(logging.ERROR)
giveup_handler(
ChaturbateUtils().giveup_handler(
{ # type: ignore[typeddict-item]
"tries": 6,
"exception": HTTPStatusError(
Expand All @@ -213,7 +209,7 @@ def test_giveup_handler(self, caplog) -> None: # noqa: ANN001
def test_giveup_handler_no_exception(self, caplog) -> None: # noqa: ANN001
"""Test the giveup handler with no exception."""
caplog.set_level(logging.ERROR)
giveup_handler(
ChaturbateUtils().giveup_handler(
{ # type: ignore[typeddict-item]
"tries": 6,
}
Expand Down Expand Up @@ -335,7 +331,7 @@ def test_need_retry(
expected_retry: bool, # noqa: FBT001
) -> None:
"""Test need_retry function."""
assert need_retry(exception) == expected_retry
assert ChaturbateUtils().need_retry(exception) == expected_retry


class TestTipModel:
Expand Down Expand Up @@ -1032,9 +1028,9 @@ def test_http_status_error(self, status_code: int, *, expected: bool) -> None:
request=Request("GET", "https://error.url.com"),
response=Response(status_code),
)
assert need_retry(exception) == expected
assert ChaturbateUtils().need_retry(exception) == expected

def test_non_http_status_error(self) -> None:
"""Test need_retry with a non-HTTPStatusError exception."""
exception = TimeoutException("Timeout occurred")
assert not need_retry(exception)
assert not ChaturbateUtils().need_retry(exception)

0 comments on commit 9779448

Please sign in to comment.