Skip to content

Commit

Permalink
add new files
Browse files Browse the repository at this point in the history
  • Loading branch information
extreme4all committed Oct 26, 2024
1 parent 523c0e2 commit 256ae76
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 0 deletions.
Binary file added requirements-test.txt
Binary file not shown.
Empty file added src/async_api/__init__.py
Empty file.
Empty file added src/async_api/osrs/__init__.py
Empty file.
91 changes: 91 additions & 0 deletions src/async_api/osrs/hiscores.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import logging
from enum import Enum

from aiohttp import ClientSession
from pydantic import BaseModel

from src.exceptions import PlayerDoesNotExist, Undefined, UnexpectedRedirection
from src.utils import RateLimiter

logger = logging.getLogger(__name__)


class Mode(str, Enum):
OLDSCHOOL: str = "hiscore_oldschool"
IRONMAN: str = "hiscore_oldschool_ironman"
HARDCORE: str = "hiscore_oldschool_hardcore_ironman"
ULTIMATE: str = "hiscore_oldschool_ultimate"
DEADMAN: str = "hiscore_oldschool_deadman"
SEASONAL: str = "hiscore_oldschool_seasonal"
TOURNAMENT: str = "hiscore_oldschool_tournament"


class Skill(BaseModel):
id: int
name: str
rank: int
level: int
xp: int


class Activity(BaseModel):
id: int
name: str
rank: int
score: int


class PlayerStats(BaseModel):
skills: list[Skill]
activities: list[Activity]


class hiscore:
BASE_URL = "https://secure.runescape.com"

def __init__(
self, proxy: str = "", rate_limiter: RateLimiter = RateLimiter()
) -> None:
self.proxy = proxy
self.rate_limiter = rate_limiter

async def get(self, mode: Mode, player: str, session: ClientSession) -> PlayerStats:
"""
Fetches player stats from the OSRS hiscores API.
Args:
mode (Mode): The hiscore mode.
player (str): The player's username.
session (ClientSession): The HTTP session.
Returns:
PlayerStats: Parsed player statistics.
Raises:
UnexpectedRedirection: If a redirection occurs.
PlayerDoesNotExist: If the player is not found (404 error).
ClientResponseError: For other HTTP errors.
Undefined: For anything else that is not a 200
"""
logger.info(f"Performing hiscores lookup on {player}")
url = f"{self.BASE_URL}/m={mode.value}/index_lite.json"
params = {"player": player}

async with session.get(url, proxy=self.proxy, params=params) as response:
# when the HS are down it will redirect to the main page.
# after redirction it will return a 200, so we must check for redirection first
if response.history and any(r.status == 302 for r in response.history):
error_msg = (
f"Redirection occured: {response.url} - {response.history[0].url}"
)
logger.error(error_msg)
raise UnexpectedRedirection(error_msg)
elif response.status == 404:
logger.error(f"player: {player} does not exist.")
raise PlayerDoesNotExist(f"player: {player} does not exist.")
elif response.status != 200:
# raises ClientResponseError
response.raise_for_status()
raise Undefined()
data = await response.json()
return PlayerStats(**data)
Empty file.
10 changes: 10 additions & 0 deletions src/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class PlayerDoesNotExist(Exception):
pass


class UnexpectedRedirection(Exception):
pass


class Undefined(Exception):
pass
Empty file added src/sync_api/__init__.py
Empty file.
Empty file added src/sync_api/osrs/__init__.py
Empty file.
Empty file.
35 changes: 35 additions & 0 deletions tests/test_async_hiscore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import pytest
from aiohttp import ClientSession

from src.async_api.osrs.hiscores import Mode, PlayerStats, hiscore
from src.exceptions import PlayerDoesNotExist


@pytest.mark.asyncio
async def test_get_valid():
hiscore_instance = hiscore()
async with ClientSession() as session:
player_stats = await hiscore_instance.get(
mode=Mode.OLDSCHOOL,
player="extreme4all",
session=session,
)

# Assertions to confirm the response is correct
assert isinstance(
player_stats, PlayerStats
), "The returned object is not of type PlayerStats"
assert player_stats.skills, "Skills data should not be empty"
assert player_stats.activities, "Activities data should not be empty"


@pytest.mark.asyncio
async def test_get_invalid():
hiscore_instance = hiscore()
async with ClientSession() as session:
with pytest.raises(PlayerDoesNotExist):
_ = await hiscore_instance.get(
mode=Mode.OLDSCHOOL,
player="This_is_not_a_valid_name",
session=session,
)

0 comments on commit 256ae76

Please sign in to comment.