Skip to content

Commit

Permalink
fix: Fixed issues after Blizzard update and mostly update competitive…
Browse files Browse the repository at this point in the history
… informations (#93)
  • Loading branch information
TeKrop authored Feb 13, 2024
1 parent cf2bfcb commit 24a321f
Show file tree
Hide file tree
Showing 56 changed files with 129,696 additions and 49,771 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ options:
```

### Code Quality
The code quality is checked using the `ruff` command. I'm also using the `isort` utility for imports ordering, and `black` to enforce PEP-8 convention on my code. To check the quality of the code, you just have to run the following command :
The code quality is checked using the `ruff` command. I'm also using `ruff format` for imports ordering and code formatting, enforcing PEP-8 convention on my code. To check the quality of the code, you just have to run the following command :

```
ruff .
Expand All @@ -139,9 +139,7 @@ python -m pytest --cov=app --cov-report html
The project is using [pre-commit](https://pre-commit.com/) framework to ensure code quality before making any commit on the repository. After installing the project dependencies, you can install the pre-commit by using the `pre-commit install` command.

The configuration can be found in the `.pre-commit-config.yaml` file. It consists in launching 3 processes on modified files before making any commit :
- `isort` for imports sorting in a clean way
- `black` for formatting the code in a uniform and PEP8-compliant format
- `ruff` for code quality checks and some fixes if possible
- `ruff` for linting and code formatting (with `ruff format`)
- `sourcery` for more code quality checks and a lot of simplifications

## 🛠️ Cache System
Expand Down
4 changes: 3 additions & 1 deletion app/commands/update_test_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ async def main():
for route, filepath in route_file_mapping.items():
logger.info("Updating {}{}...", test_data_path, filepath)
logger.info("GET {}/{}{}...", settings.blizzard_host, locale, route)
response = await client.get(f"{settings.blizzard_host}/{locale}{route}")
response = await client.get(
f"{settings.blizzard_host}/{locale}{route}", follow_redirects=True
)
logger.debug(
"HTTP {} / Time : {}",
response.status_code,
Expand Down
20 changes: 13 additions & 7 deletions app/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ class Role(StrEnum):
TANK = "tank"


# Dynamically create the CompetitiveRole enum by using the existing
# Role enum and just adding the "open" option for Open Queue
CompetitiveRole = StrEnum(
"CompetitiveRole",
{
**{role.name: role.value for role in Role},
"OPEN": "open",
},
)
CompetitiveRole.__doc__ = "Competitive roles for ranks in stats summary"


class PlayerGamemode(StrEnum):
"""Gamemodes associated with players statistics"""

Expand All @@ -95,13 +107,6 @@ class PlayerPlatform(StrEnum):
PC = "pc"


class PlayerPrivacy(StrEnum):
"""Players career privacy"""

PUBLIC = "public"
PRIVATE = "private"


class CompetitiveDivision(StrEnum):
"""Competitive division of a rank"""

Expand All @@ -112,6 +117,7 @@ class CompetitiveDivision(StrEnum):
DIAMOND = "diamond"
MASTER = "master"
GRANDMASTER = "grandmaster"
CHAMPION = "champion"


class Locale(StrEnum):
Expand Down
2 changes: 1 addition & 1 deletion app/common/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"Dekk-2677", # Classic profile without rank
"KIRIKO-21253", # Profile with rank on only two roles
"Player-1112937", # Console player
"Player-137712", # Private profile
"quibble-11594", # Profile without endorsement
"TeKrop-2217", # Classic profile
"Unknown-1234", # No player
"JohnV1-1190", # Player without any title ingame
Expand Down
20 changes: 0 additions & 20 deletions app/handlers/search_players_request_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

class SearchPlayersRequestHandler(ApiRequestMixin):
"""Search Players Request Handler used in order to find an Overwatch player
using some filters : career privacy, etc.
The APIRequestHandler class is not used here, as this is a very specific request,
depending on a Blizzard endpoint returning JSON Data. Some parsers are used,
Expand Down Expand Up @@ -56,10 +55,6 @@ async def process_request(self, **kwargs) -> dict:

players = req.json()

# Filter results using kwargs
logger.info("Applying filters..")
players = self.apply_filters(players, **kwargs)

# Transform into PlayerSearchResult format
logger.info("Applying transformation..")
players = self.apply_transformations(players)
Expand All @@ -84,20 +79,6 @@ async def process_request(self, **kwargs) -> dict:
logger.info("Done ! Returning players list...")
return players_list

@staticmethod
def apply_filters(players: list[dict], **kwargs) -> Iterable[dict]:
"""Apply query params filters on a list of players (only career
privacy for now), and return the results accordingly as an iterable.
"""

def filter_privacy(player: dict) -> bool:
return not kwargs.get("privacy") or player["isPublic"] == (
kwargs.get("privacy") == "public"
)

filters = [filter_privacy]
return filter(lambda x: all(f(x) for f in filters), players)

def apply_transformations(self, players: Iterable[dict]) -> list[dict]:
"""Apply transformations to found players in order to return the data
in the OverFast API format. We'll also retrieve some data from parsers.
Expand All @@ -112,7 +93,6 @@ def apply_transformations(self, players: Iterable[dict]) -> list[dict]:
"avatar": self.get_avatar_url(player, player_id),
"namecard": self.get_namecard_url(player, player_id),
"title": self.get_title(player, player_id),
"privacy": "public" if player["isPublic"] else "private",
"career_url": f"{settings.app_base_url}/players/{player_id}",
},
)
Expand Down
44 changes: 17 additions & 27 deletions app/models/players.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
CareerStatCategory,
CompetitiveDivision,
HeroKey,
PlayerPrivacy,
)
from app.common.helpers import get_hero_name, key_to_label

Expand Down Expand Up @@ -57,15 +56,6 @@ class PlayerShort(BaseModel):
description="Title of the player if any",
examples=["Bytefixer"],
)
privacy: PlayerPrivacy = Field(
...,
title="Privacy",
description=(
"Privacy of the player career. If private, only some basic informations "
"are available on player details endpoint (avatar, endorsement)"
),
examples=["public"],
)
career_url: AnyHttpUrl = Field(
...,
title="Career URL",
Expand Down Expand Up @@ -102,9 +92,16 @@ class PlayerCompetitiveRank(BaseModel):
)
rank_icon: HttpUrl = Field(
...,
description="URL of the rank icon associated with the player rank (division + tier)",
description="URL of the division icon associated with the player rank",
examples=[
"https://static.playoverwatch.com/img/pages/career/icons/rank/Rank_MasterTier-7d3b85ba0d.png",
],
)
tier_icon: HttpUrl = Field(
...,
description="URL of the tier icon associated with the player rank",
examples=[
"https://static.playoverwatch.com/img/pages/career/icons/rank/GrandmasterTier-3-e55e61f68f.png",
"https://static.playoverwatch.com/img/pages/career/icons/rank/TierDivision_3-1de89374e2.png",
],
)

Expand All @@ -122,23 +119,25 @@ class PlatformCompetitiveRanksContainer(BaseModel):
tank: PlayerCompetitiveRank | None = Field(..., description="Tank role details")
damage: PlayerCompetitiveRank | None = Field(..., description="Damage role details")
support: PlayerCompetitiveRank | None = Field(
...,
description="Support role details",
..., description="Support role details"
)
open: PlayerCompetitiveRank | None = Field(
..., description="Open Queue role details"
)


class PlayerCompetitiveRanksContainer(BaseModel):
pc: PlatformCompetitiveRanksContainer | None = Field(
...,
description=(
"Role Queue competitive ranks for PC and last season played on it. "
"Competitive ranks for PC and last season played on it. "
"If the player doesn't play on this platform, it's null."
),
)
console: PlatformCompetitiveRanksContainer | None = Field(
...,
description=(
"Role Queue competitive ranks for console and last season played on it. "
"Competitive ranks for console and last season played on it. "
"If the player doesn't play on this platform, it's null."
),
)
Expand Down Expand Up @@ -209,27 +208,18 @@ class PlayerSummary(BaseModel):
description="Title of the player if any",
examples=["Bytefixer"],
)
endorsement: PlayerEndorsement = Field(
endorsement: PlayerEndorsement | None = Field(
...,
description="Player endorsement details",
)
competitive: PlayerCompetitiveRanksContainer | None = Field(
...,
description=(
"Role Queue competitive ranking in the last season played by the player "
"Competitive ranking in the last season played by the player "
"in different roles depending on the platform. If the career is private "
"or if the player doesn't play competitive at all, it's null."
),
)
privacy: PlayerPrivacy = Field(
...,
title="Privacy",
description=(
"Privacy of the player career. If private, only some basic informations "
"are available (avatar, endorsement)"
),
examples=["public"],
)


class HeroesComparisons(BaseModel):
Expand Down
18 changes: 11 additions & 7 deletions app/parsers/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import unicodedata
from functools import cache

from app.common.enums import CompetitiveDivision, HeroKey, Role
from app.common.enums import CompetitiveDivision, CompetitiveRole, HeroKey, Role
from app.common.helpers import read_csv_data_file
from app.config import settings

Expand Down Expand Up @@ -38,8 +38,8 @@ def get_computed_stat_value(input_str: str) -> str | float | int:
return 0 if input_str == "--" else input_str


def get_division_from_rank_icon(rank_url: str) -> CompetitiveDivision:
division_name = rank_url.split("/")[-1].split("-")[0]
def get_division_from_icon(rank_url: str) -> CompetitiveDivision:
division_name = rank_url.split("/")[-1].split("-")[0].split("_")[-1]
return CompetitiveDivision(division_name[:-4].lower())


Expand All @@ -65,10 +65,14 @@ def get_hero_keyname(input_str: str) -> str:
return string_to_snakecase(input_str).replace("_", "-")


def get_role_key_from_icon(icon_url: str) -> Role:
def get_role_key_from_icon(icon_url: str) -> CompetitiveRole:
"""Extract role key from the role icon."""
icon_role_key = icon_url.split("/")[-1].split("-")[0]
return Role.DAMAGE if icon_role_key == "offense" else Role(icon_role_key)
return (
CompetitiveRole.DAMAGE
if icon_role_key == "offense"
else CompetitiveRole(icon_role_key)
)


def get_stats_hero_class(hero_classes: list[str]) -> str:
Expand All @@ -78,10 +82,10 @@ def get_stats_hero_class(hero_classes: list[str]) -> str:
)


def get_tier_from_rank_icon(rank_url: str) -> int:
def get_tier_from_icon(tier_url: str) -> int:
"""Extracts the rank tier from the rank URL. 0 if not found."""
try:
return int(rank_url.split("/")[-1].split("-")[1])
return int(tier_url.split("/")[-1].split("-")[0].split("_")[-1])
except (IndexError, ValueError):
return 0

Expand Down
36 changes: 16 additions & 20 deletions app/parsers/player_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@

from app.common.enums import (
CareerHeroesComparisonsCategory,
CompetitiveRole,
PlayerGamemode,
PlayerPlatform,
PlayerPrivacy,
Role,
)
from app.common.exceptions import ParserBlizzardError
from app.common.helpers import get_player_title
Expand All @@ -18,14 +17,14 @@
from .generics.api_parser import APIParser
from .helpers import (
get_computed_stat_value,
get_division_from_rank_icon,
get_division_from_icon,
get_endorsement_value_from_frame,
get_hero_keyname,
get_plural_stat_key,
get_real_category_name,
get_role_key_from_icon,
get_stats_hero_class,
get_tier_from_rank_icon,
get_tier_from_icon,
string_to_snakecase,
)

Expand Down Expand Up @@ -138,7 +137,6 @@ def __get_summary(self) -> dict:
"title": self.__get_title(profile_div),
"endorsement": self.__get_endorsement(progression_div),
"competitive": self.__get_competitive_ranks(progression_div),
"privacy": self.__get_privacy(profile_div),
}

@staticmethod
Expand All @@ -160,12 +158,15 @@ def __get_title(profile_div: Tag) -> str | None:
return get_player_title(title)

@staticmethod
def __get_endorsement(progression_div: Tag) -> dict:
def __get_endorsement(progression_div: Tag) -> dict | None:
endorsement_span = progression_div.find(
"span",
class_="Profile-player--endorsementWrapper",
recursive=False,
)
if not endorsement_span:
return None

endorsement_frame_url = endorsement_span.find(
"img",
class_="Profile-playerSummary--endorsement",
Expand Down Expand Up @@ -210,19 +211,22 @@ def __get_platform_competitive_ranks(

for role_wrapper in role_wrappers:
role_icon = self.__get_role_icon(role_wrapper)
rank_icon = role_wrapper.find("img", class_="Profile-playerSummary--rank")[
"src"
]
role_key = get_role_key_from_icon(role_icon).value

rank_tier_icons = role_wrapper.find_all(
"img", class_="Profile-playerSummary--rank"
)
rank_icon, tier_icon = rank_tier_icons[0]["src"], rank_tier_icons[1]["src"]

competitive_ranks[role_key] = {
"division": get_division_from_rank_icon(rank_icon).value,
"tier": get_tier_from_rank_icon(rank_icon),
"division": get_division_from_icon(rank_icon).value,
"tier": get_tier_from_icon(tier_icon),
"role_icon": role_icon,
"rank_icon": rank_icon,
"tier_icon": tier_icon,
}

for role in Role:
for role in CompetitiveRole:
if role.value not in competitive_ranks:
competitive_ranks[role.value] = None

Expand Down Expand Up @@ -258,14 +262,6 @@ def __get_role_icon(role_wrapper: Tag) -> str:
role_svg = role_wrapper.find("svg", class_="Profile-playerSummary--role")
return role_svg.find("use")["xlink:href"]

@staticmethod
def __get_privacy(profile_div: Tag) -> str:
return (
PlayerPrivacy.PRIVATE
if profile_div.find("div", class_="Profile-player--private")
else PlayerPrivacy.PUBLIC
).value

def get_stats(self) -> dict | None:
stats = {
platform.value: self.__get_platform_stats(platform_class)
Expand Down
Loading

0 comments on commit 24a321f

Please sign in to comment.