Skip to content

Commit 52b0a8b

Browse files
authored
fix: fixed profile pages after blizzard unlocks update (#297)
* fix: fixed profile pages after blizzard unlocks update * fixes after review * fix: updated volumes
1 parent 50eda58 commit 52b0a8b

32 files changed

Lines changed: 157 additions & 513 deletions

app/cache_manager.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,20 +122,6 @@ def update_player_cache(self, player_id: str, value: dict) -> None:
122122
ex=settings.player_cache_timeout,
123123
)
124124

125-
@valkey_connection_handler
126-
def get_unlock_data_cache(self, cache_key: str) -> str | None:
127-
data_cache = self.valkey_server.hget(settings.unlock_data_cache_key, cache_key)
128-
return data_cache.decode("utf-8") if data_cache else None
129-
130-
@valkey_connection_handler
131-
def update_unlock_data_cache(self, unlock_data: dict[str, str]) -> None:
132-
for data_key, data_value in unlock_data.items():
133-
self.valkey_server.hset(
134-
settings.unlock_data_cache_key,
135-
data_key,
136-
data_value,
137-
)
138-
139125
@valkey_connection_handler
140126
def is_being_rate_limited(self) -> bool:
141127
return self.valkey_server.exists(settings.blizzard_rate_limit_key)

app/config.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,22 +122,6 @@ class Settings(BaseSettings):
122122
# Cache TTL for hero stats data (seconds)
123123
hero_stats_cache_timeout: int = 3600
124124

125-
############
126-
# UNLOCKS DATA (AVATARS, NAMECARDS, TITLES)
127-
############
128-
129-
# Cache key for unlock data cache in Valkey.
130-
unlock_data_cache_key: str = "unlock-data-cache"
131-
132-
# URI of the page where unlock data can be retrieved
133-
unlock_data_path: str = "/search/unlocks/"
134-
135-
# Batch size to use when requesting unlock data
136-
unlock_data_batch_size: int = 50
137-
138-
# Keys that should be retrieved for unlocks
139-
unlock_keys: set[str] = {"portrait", "namecard", "title"}
140-
141125
############
142126
# BLIZZARD
143127
############

app/helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Parser Helpers module"""
22

33
import csv
4+
import traceback
45
from functools import cache
56
from pathlib import Path
67

@@ -65,9 +66,10 @@ def overfast_internal_error(url: str, error: Exception) -> HTTPException:
6566

6667
# Log the critical error
6768
logger.critical(
68-
"Internal server error for URL {} : {}",
69+
"Internal server error for URL {} : {}\n{}",
6970
url,
7071
str(error),
72+
traceback.format_stack(),
7173
)
7274

7375
# If we're using a profiler, it means we're debugging, raise the error

app/players/enums.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,6 @@ class CompetitiveDivision(StrEnum):
8888
ULTIMATE = "ultimate"
8989

9090

91-
class UnlockDataType(StrEnum):
92-
NAMECARD = "namecard"
93-
PORTRAIT = "portrait"
94-
TITLE = "title"
95-
96-
# Special value to only retrieve last_updated_at value
97-
LAST_UPDATED_AT = "lastUpdatedAt"
98-
99-
# Special value to retrieve all the player data from search endpoint
100-
SUMMARY = "summary"
101-
102-
10391
class PlayerRegion(StrEnum):
10492
EUROPE = "europe"
10593
AMERICAS = "americas"

app/players/helpers.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,11 @@ def key_to_label(key: str) -> str:
3232
return " ".join(s.capitalize() for s in key.split("_"))
3333

3434

35-
@cache
36-
def get_player_title(title: str | None) -> str | None:
37-
"""Get player title from string extracted from Blizzard page. This is
38-
where we're handling the special "no title" case for which we return None
35+
def get_player_title(title: dict | str) -> str | None:
36+
"""Get player title from string or dict extracted from Blizzard API.
37+
This is where we're handling the special "empty string" case for which we return None
3938
"""
40-
return None if title and title.lower() == "no title" else title
39+
return title["en_US"] if title else None
4140

4241

4342
def get_computed_stat_value(input_str: str) -> str | float | int:

app/players/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ class PlayerShort(BaseModel):
7878
),
7979
examples=[1704209332],
8080
)
81+
is_public: bool | None = Field(
82+
None,
83+
title="Is public",
84+
description="Whether or not the player profile is public",
85+
examples=[True],
86+
)
8187

8288

8389
class PlayerSearchResult(BaseModel):

app/players/parsers/base_player_parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async def parse(self) -> None:
6666

6767
async def __retrieve_player_summary_data(self) -> dict | None:
6868
"""Call Blizzard search page with user name to
69-
check last_updated_at and retrieve unlock values
69+
check last_updated_at and retrieve summary values
7070
"""
7171
player_summary_parser = SearchDataParser(player_id=self.player_id)
7272
await player_summary_parser.parse()

app/players/parsers/player_career_parser.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -163,30 +163,14 @@ def __get_summary(self) -> dict:
163163

164164
return {
165165
"username": summary_div.css_first("h1.Profile-player--name").text(),
166-
"avatar": (
167-
summary_div.css_first("img.Profile-player--portrait").attributes.get(
168-
"src"
169-
)
170-
),
171-
"namecard": self.player_data["summary"]["namecard"],
172-
"title": self.__get_title(profile_div),
166+
"avatar": self.player_data["summary"]["avatar"],
167+
"namecard": self.player_data["summary"].get("namecard"),
168+
"title": get_player_title(self.player_data["summary"]["title"]),
173169
"endorsement": self.__get_endorsement(progression_div),
174170
"competitive": self.__get_competitive_ranks(progression_div),
175171
"last_updated_at": self.player_data["summary"]["lastUpdated"],
176172
}
177173

178-
@staticmethod
179-
def __get_title(profile_div: LexborNode) -> str | None:
180-
# We return None is there isn't any player title div
181-
if not (title_tag := profile_div.css_first("h2.Profile-player--title")):
182-
return None
183-
184-
# Retrieve the title text
185-
title = title_tag.text() or None
186-
187-
# Special case : the "no title" means there is no title
188-
return get_player_title(title)
189-
190174
@staticmethod
191175
def __get_endorsement(progression_div: LexborNode) -> dict | None:
192176
endorsement_span = progression_div.css_first(

app/players/parsers/player_search_parser.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from app.config import settings
66
from app.overfast_logger import logger
77
from app.parsers import JSONParser
8-
from app.unlocks_manager import UnlocksManager
98

109
from ..helpers import get_player_title
1110

@@ -21,7 +20,6 @@ def __init__(self, **kwargs):
2120
self.order_by = kwargs.get("order_by")
2221
self.offset = kwargs.get("offset")
2322
self.limit = kwargs.get("limit")
24-
self.unlocks_manager = UnlocksManager()
2523

2624
def get_blizzard_url(self, **kwargs) -> str:
2725
"""URL used when requesting data to Blizzard."""
@@ -34,7 +32,7 @@ async def parse_data(self) -> dict:
3432

3533
# Transform into PlayerSearchResult format
3634
logger.info("Applying transformation..")
37-
players = await self.apply_transformations(players)
35+
players = self.apply_transformations(players)
3836

3937
# Apply ordering
4038
logger.info("Applying ordering..")
@@ -58,31 +56,26 @@ def filter_players(self) -> list[dict]:
5856
battletag = self.search_nickname.replace("-", "#")
5957
return [player for player in self.json_data if player["battleTag"] == battletag]
6058

61-
async def apply_transformations(self, players: Iterable[dict]) -> list[dict]:
59+
def apply_transformations(self, players: Iterable[dict]) -> list[dict]:
6260
"""Apply transformations to found players in order to return the data
6361
in the OverFast API format. We'll also retrieve some data from parsers.
6462
"""
6563
transformed_players = []
6664

67-
# Retrieve and cache Unlock IDs
68-
unlock_ids = self.__retrieve_unlock_ids(players)
69-
await self.unlocks_manager.cache_values(unlock_ids)
70-
7165
for player in players:
7266
player_id = player["battleTag"].replace("#", "-")
7367

7468
transformed_players.append(
7569
{
7670
"player_id": player_id,
7771
"name": player["battleTag"],
78-
"avatar": self.unlocks_manager.get(player["portrait"]),
79-
"namecard": self.unlocks_manager.get(player["namecard"]),
80-
"title": get_player_title(
81-
self.unlocks_manager.get(player["title"])
82-
),
72+
"avatar": player["avatar"],
73+
"namecard": player.get("namecard"),
74+
"title": get_player_title(player["title"]),
8375
"career_url": f"{settings.app_base_url}/players/{player_id}",
8476
"blizzard_id": player["url"],
8577
"last_updated_at": player["lastUpdated"],
78+
"is_public": player["isPublic"],
8679
},
8780
)
8881
return transformed_players
@@ -95,6 +88,3 @@ def apply_ordering(self, players: list[dict]) -> list[dict]:
9588
reverse=order_arrangement == "desc",
9689
)
9790
return players
98-
99-
def __retrieve_unlock_ids(self, players: list[dict]) -> set[str]:
100-
return {player[key] for player in players for key in settings.unlock_keys}

app/players/parsers/search_data_parser.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from app.config import settings
44
from app.overfast_logger import logger
55
from app.parsers import JSONParser
6-
from app.unlocks_manager import UnlocksManager
76

87

98
class SearchDataParser(JSONParser):
@@ -14,7 +13,6 @@ class SearchDataParser(JSONParser):
1413
def __init__(self, **kwargs):
1514
super().__init__(**kwargs)
1615
self.player_id = kwargs.get("player_id")
17-
self.unlocks_manager = UnlocksManager()
1816

1917
async def parse_data(self) -> dict:
2018
# We'll use the battletag for searching
@@ -25,7 +23,10 @@ async def parse_data(self) -> dict:
2523
player_data = next(
2624
player
2725
for player in self.json_data
28-
if player["battleTag"] == player_battletag
26+
if (
27+
player["battleTag"] == player_battletag
28+
and player["isPublic"] is True
29+
)
2930
)
3031
except StopIteration:
3132
# We didn't find the player, return nothing
@@ -35,32 +36,9 @@ async def parse_data(self) -> dict:
3536
)
3637
return {}
3738

38-
# Once we found the player, add unlock values in data (avatar, namecard, title)
39-
return await self._enrich_with_unlock_values(player_data)
39+
return player_data
4040

4141
def get_blizzard_url(self, **kwargs) -> str:
4242
# Replace dash by encoded number sign (#) for search
4343
player_name = kwargs.get("player_id").split("-", 1)[0]
4444
return f"{super().get_blizzard_url(**kwargs)}/{player_name}/"
45-
46-
async def _enrich_with_unlock_values(self, player_data: dict) -> dict:
47-
"""Enrich player data with unlock values"""
48-
49-
# First cache unlock data if not already done
50-
unlock_ids = {
51-
player_data[key]
52-
for key in settings.unlock_keys
53-
if player_data[key] is not None
54-
}
55-
56-
await self.unlocks_manager.cache_values(unlock_ids)
57-
58-
# Then return values with existing unlock keys replaced by their respective values
59-
return {
60-
key: (
61-
self.unlocks_manager.get(value)
62-
if key in settings.unlock_keys
63-
else value
64-
)
65-
for key, value in player_data.items()
66-
}

0 commit comments

Comments
 (0)