Skip to content

Commit 5f2eebf

Browse files
authored
Merge pull request #1795 from Numerlor/doc-repetitive-outdated-warn
Prevent repetitive stale inventory warnings from erroneously generated inventories
2 parents 77e83e0 + 662c9a6 commit 5f2eebf

File tree

3 files changed

+47
-19
lines changed

3 files changed

+47
-19
lines changed

bot/exts/info/doc/_batch_parser.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@
1717

1818
from . import _cog, doc_cache
1919
from ._parsing import get_symbol_markdown
20+
from ._redis_cache import StaleItemCounter
2021

2122
log = get_logger(__name__)
2223

2324

2425
class StaleInventoryNotifier:
2526
"""Handle sending notifications about stale inventories through `DocItem`s to dev log."""
2627

28+
symbol_counter = StaleItemCounter()
29+
2730
def __init__(self):
2831
self._init_task = scheduling.create_task(
2932
self._init_channel(),
@@ -40,13 +43,16 @@ async def _init_channel(self) -> None:
4043
async def send_warning(self, doc_item: _cog.DocItem) -> None:
4144
"""Send a warning to dev log if one wasn't already sent for `item`'s url."""
4245
if doc_item.url not in self._warned_urls:
43-
self._warned_urls.add(doc_item.url)
44-
await self._init_task
45-
embed = discord.Embed(
46-
description=f"Doc item `{doc_item.symbol_id=}` present in loaded documentation inventories "
47-
f"not found on [site]({doc_item.url}), inventories may need to be refreshed."
48-
)
49-
await self._dev_log.send(embed=embed)
46+
# Only warn if the item got less than 3 warnings
47+
# or if it has been more than 3 weeks since the last warning
48+
if await self.symbol_counter.increment_for(doc_item) < 3:
49+
self._warned_urls.add(doc_item.url)
50+
await self._init_task
51+
embed = discord.Embed(
52+
description=f"Doc item `{doc_item.symbol_id=}` present in loaded documentation inventories "
53+
f"not found on [site]({doc_item.url}), inventories may need to be refreshed."
54+
)
55+
await self._dev_log.send(embed=embed)
5056

5157

5258
class QueueItem(NamedTuple):
@@ -103,7 +109,7 @@ async def get_markdown(self, doc_item: _cog.DocItem) -> Optional[str]:
103109
if doc_item not in self._item_futures and doc_item not in self._queue:
104110
self._item_futures[doc_item].user_requested = True
105111

106-
async with bot.instance.http_session.get(doc_item.url) as response:
112+
async with bot.instance.http_session.get(doc_item.url, raise_for_status=True) as response:
107113
soup = await bot.instance.loop.run_in_executor(
108114
None,
109115
BeautifulSoup,

bot/exts/info/doc/_cog.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ async def clear_cache_command(
464464
) -> None:
465465
"""Clear the persistent redis cache for `package`."""
466466
if await doc_cache.delete(package_name):
467+
await self.item_fetcher.stale_inventory_notifier.symbol_counter.delete()
467468
await ctx.send(f"Successfully cleared the cache for `{package_name}`.")
468469
else:
469470
await ctx.send("No keys matching the package found.")

bot/exts/info/doc/_redis_cache.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ async def set(self, item: DocItem, value: str) -> None:
2525
2626
All keys from a single page are stored together, expiring a week after the first set.
2727
"""
28-
url_key = remove_suffix(item.relative_url_path, ".html")
29-
redis_key = f"{self.namespace}:{item.package}:{url_key}"
28+
redis_key = f"{self.namespace}:{item_key(item)}"
3029
needs_expire = False
3130

3231
with await self._get_pool_connection() as connection:
@@ -44,10 +43,36 @@ async def set(self, item: DocItem, value: str) -> None:
4443
@namespace_lock
4544
async def get(self, item: DocItem) -> Optional[str]:
4645
"""Return the Markdown content of the symbol `item` if it exists."""
47-
url_key = remove_suffix(item.relative_url_path, ".html")
46+
with await self._get_pool_connection() as connection:
47+
return await connection.hget(f"{self.namespace}:{item_key(item)}", item.symbol_id, encoding="utf8")
4848

49+
@namespace_lock
50+
async def delete(self, package: str) -> bool:
51+
"""Remove all values for `package`; return True if at least one key was deleted, False otherwise."""
52+
with await self._get_pool_connection() as connection:
53+
package_keys = [
54+
package_key async for package_key in connection.iscan(match=f"{self.namespace}:{package}:*")
55+
]
56+
if package_keys:
57+
await connection.delete(*package_keys)
58+
return True
59+
return False
60+
61+
62+
class StaleItemCounter(RedisObject):
63+
"""Manage increment counters for stale `DocItem`s."""
64+
65+
@namespace_lock
66+
async def increment_for(self, item: DocItem) -> int:
67+
"""
68+
Increment the counter for `item` by 1, set it to expire in 3 weeks and return the new value.
69+
70+
If the counter didn't exist, initialize it with 1.
71+
"""
72+
key = f"{self.namespace}:{item_key(item)}:{item.symbol_id}"
4973
with await self._get_pool_connection() as connection:
50-
return await connection.hget(f"{self.namespace}:{item.package}:{url_key}", item.symbol_id, encoding="utf8")
74+
await connection.expire(key, WEEK_SECONDS * 3)
75+
return int(await connection.incr(key))
5176

5277
@namespace_lock
5378
async def delete(self, package: str) -> bool:
@@ -62,10 +87,6 @@ async def delete(self, package: str) -> bool:
6287
return False
6388

6489

65-
def remove_suffix(string: str, suffix: str) -> str:
66-
"""Remove `suffix` from end of `string`."""
67-
# TODO replace usages with str.removesuffix on 3.9
68-
if string.endswith(suffix):
69-
return string[:-len(suffix)]
70-
else:
71-
return string
90+
def item_key(item: DocItem) -> str:
91+
"""Get the redis redis key string from `item`."""
92+
return f"{item.package}:{item.relative_url_path.removesuffix('.html')}"

0 commit comments

Comments
 (0)