Skip to content

Commit

Permalink
fix: refactor package and update README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
MountainGod2 committed Apr 7, 2024
1 parent 29c0509 commit bea835a
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 410 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
run: poetry install --no-interaction --no-root
- name: Install library
run: poetry install --no-interaction
- name: Format with ruff
run: poetry run ruff format ./
- name: Lint with ruff
run: poetry run ruff check --fix ./
- name: Type check with mypy
run: poetry run mypy ./
- name: Test with pytest
env:
CB_USERNAME: ${{ secrets.CB_USERNAME }}
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ Replace `your_chaturbate_username` and `your_api_token` with your actual Chaturb
To run the Chaturbate Poller, use the following command from the root directory of the project:

```bash
python -m chaturbate_poller
python examples/example.py
```

The application will start, log into the console, and begin fetching events from the Chaturbate API using the credentials provided in the `.env` file.

See `example.py` for more detailed usage instructions.
See `examples/example.py` for more detailed usage instructions.

## Development

Expand Down
5 changes: 2 additions & 3 deletions example.py → examples/example.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# chaturbate_poller/example.py
"""Example for the Chaturbate Poller module."""
"""Example for the Chaturbate Poller module.""" # noqa: INP001

import asyncio
import contextlib
Expand All @@ -20,7 +19,7 @@
token = os.getenv("CB_TOKEN", "")


async def handle_event(event: Event) -> None: # noqa: C901, PLR0915, PLR0912
async def handle_event(event: Event) -> None: # noqa: PLR0915, PLR0912, C901
"""Handle different types of events."""
method = event.method
object_data = event.object
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ exclude = [
line-length = 88
indent-width = 4

# Assume Python 3.8
target-version = "py38"
# Assume Python 3.10
target-version = "py310"

[tool.ruff.lint]
select = ["ALL"]
Expand All @@ -91,7 +91,8 @@ ignore = [
"ISC001", # single-line-implicit-string-concatenation
"ANN101", "ANN102", # missing-type-self (redundant)
"D203", # one-blank-line-before-class (incompatable)
"D213" # multi-line-summary-second-line (incompatable)
"D213", # multi-line-summary-second-line (incompatable)
"S101" # disable assert warning for tests
]
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
Expand Down
10 changes: 1 addition & 9 deletions src/chaturbate_poller/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
"""chaturbate_poller package."""

import logging.config

# Read version from installed package
from importlib.metadata import version

from .chaturbate_poller import ChaturbateClient
from .logging_config import LOGGING_CONFIG

__version__ = version("chaturbate_poller")
__author__ = "MountainGod2"
Expand All @@ -18,10 +15,5 @@
__description__ = "A Chaturbate event poller."


# Configure logging
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger(__name__)
"""logging.Logger: The logger for the package."""

__all__ = ["ChaturbateClient", "logger"]
__all__ = ["ChaturbateClient"]
"""List[str]: The package exports."""
54 changes: 0 additions & 54 deletions src/chaturbate_poller/__main__.py

This file was deleted.

63 changes: 25 additions & 38 deletions src/chaturbate_poller/chaturbate_poller.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
"""Chaturbate poller module."""

from __future__ import annotations

import logging
from types import TracebackType # noqa: TCH003
from types import TracebackType

import backoff
import httpx
from httpx import HTTPStatusError, RequestError
from typing_extensions import Self

from .constants import BASE_URL, ERROR_RANGE_END, ERROR_RANGE_START
from .models import EventsAPIResponse
Expand All @@ -20,40 +17,41 @@ class ChaturbateClient:
"""Client for fetching Chaturbate events.
Args:
username: Chaturbate username.
token: Chaturbate token.
timeout: Timeout in seconds for the API request.
Attributes:
base_url: Base URL for the Chaturbate API.
timeout: Timeout in seconds for the API request.
username: Chaturbate username.
token: Chaturbate token.
client: HTTPX AsyncClient instance.
username (str): The Chaturbate username.
token (str): The Chaturbate token.
timeout (int, optional): The timeout for the request. Defaults to None.
Raises:
ValueError: If username or token are not provided.
"""

def __init__(self, username: str, token: str, timeout: int | None = None) -> None:
"""Initialize client.
Args:
username (str): The Chaturbate username.
token (str): The Chaturbate token.
timeout (int, optional): The timeout for the request. Defaults to None.
Raises:
ValueError: If username or token are not provided.
"""
if not username or not token:
error_msg = "Chaturbate username and token are required."
raise ValueError(error_msg)
msg = "Chaturbate username and token are required."
raise ValueError(msg)

self.base_url = BASE_URL
self.timeout = timeout
self.username = username
self.token = token
self.client = httpx.AsyncClient()

async def __aenter__(self) -> Self:
async def __aenter__(self) -> "ChaturbateClient":
"""Enter client.
Returns:
ChaturbateClient: Client instance.
ChaturbateClient: The client.
"""
self.client = httpx.AsyncClient()
logger.debug("Client opened.")
return self

Expand All @@ -63,13 +61,7 @@ async def __aexit__(
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
"""Exit client.
Args:
exc_type: Exception type.
exc_value: Exception value.
traceback: Traceback.
"""
"""Exit client."""
await self.client.aclose()

@backoff.on_exception(
Expand All @@ -78,28 +70,23 @@ async def __aexit__(
max_time=20,
giveup=lambda e: not need_retry(e),
on_backoff=lambda details: logger.info(
"Backoff triggered. Retry: %s, Waiting: %s sec. before retrying.",
"Backoff triggered. Retry: %s, Waiting: %s seconds before retrying.",
details.get("tries", ""),
int(details.get("wait", 0)),
),
on_giveup=lambda details: logger.info(
"Retry stopped: %s after %s attempts.",
details.get("exception", ""),
"Retry stopped after %s attempts.",
details.get("tries", ""),
),
)
async def fetch_events(self, url: str | None = None) -> EventsAPIResponse:
"""Fetch events from the Chaturbate API.
Args:
url: URL to fetch events from.
url (str, optional): The URL to fetch events from. Defaults to None.
Returns:
EventsAPIResponse: Response from the API.
Raises:
HTTPStatusError: If the response status code is in the error range.
RequestError: If an error occurs during the request.
EventsAPIResponse: The events API response.
"""
if url is None:
url = self._construct_url()
Expand All @@ -111,7 +98,7 @@ def _construct_url(self) -> str:
"""Construct URL with username, token, and timeout.
Returns:
str: Constructed URL.
str: The constructed URL.
"""
base_url = self.base_url.format(username=self.username, token=self.token)
if self.timeout:
Expand All @@ -123,10 +110,10 @@ def need_retry(exception: Exception) -> bool:
"""Retries requests on 500 series errors.
Args:
exception: Exception raised by the request.
exception (Exception): The exception raised.
Returns:
bool: True if the request should be retried, False otherwise.
bool: True if the request should be retried.
"""
if isinstance(exception, HTTPStatusError):
status_code = exception.response.status_code
Expand Down
60 changes: 48 additions & 12 deletions src/chaturbate_poller/logging_config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
# chaturbate_poller/src/chaturbate_poller/logging_config.py
"""Logging configuration for the application."""
"""Logging configuration for the Chaturbate poller."""

import datetime
import logging

# Define a custom level for CRITICAL events
CRITICAL_LEVEL = 50
logging.addLevelName(CRITICAL_LEVEL, "CRITICAL")


# Custom formatter for including module and function names
class CustomFormatter(logging.Formatter):
"""Custom formatter for including module and function names."""

def format(
self,
record: logging.LogRecord,
) -> str:
"""Format the log record."""
record.module = record.module.split(".")[-1] # Extract only module name
record.funcName = record.funcName or "" # Avoid NoneType error
return super().format(record)


# Get current timestamp
current_timestamp = datetime.datetime.now(tz=datetime.timezone.utc).strftime(
"%Y-%m-%d_%H-%M-%S"
)

# Logging configuration dictionary
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"},
"standard": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
},
"detailed": {
"()": CustomFormatter,
"format": "%(asctime)s - %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(message)s", # noqa: E501
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"console": {
Expand All @@ -14,38 +49,39 @@
"level": "INFO",
},
"file": {
"class": "logging.FileHandler",
"filename": "app.log",
"formatter": "standard",
"class": "logging.handlers.TimedRotatingFileHandler",
"filename": f"app_{current_timestamp}.log",
"formatter": "detailed",
"level": "DEBUG",
"when": "midnight", # Rotate logs at midnight
"backupCount": 7, # Keep 7 backup log files
},
},
"loggers": {
"": { # root logger
"": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": True,
},
"chaturbate_poller": {
"handlers": ["console", "file"],
"handlers": ["file"], # No need for console output
"level": "INFO",
"propagate": False,
},
"httpx": {
"handlers": ["console", "file"],
"handlers": ["file"], # No need for console output
"level": "ERROR",
"propagate": False,
},
"httpcore": {
"handlers": ["console", "file"],
"handlers": ["file"], # No need for console output
"level": "ERROR",
"propagate": False,
},
"backoff": {
"handlers": ["console", "file"],
"handlers": ["file"], # No need for console output
"level": "ERROR",
"propagate": False,
},
},
}
"""Logging configuration for the application."""
Loading

0 comments on commit bea835a

Please sign in to comment.