diff options
| author | 2021-03-13 17:58:25 +0100 | |
|---|---|---|
| committer | 2021-03-13 18:41:17 +0100 | |
| commit | 81e48983c7408e6a8dd4c6131eb5633be7c53825 (patch) | |
| tree | 2cddaca593a6727de34735fd56a32dd4e6c0b507 | |
| parent | Branding: extract duration string in helper function (diff) | |
Branding: cache all available events
This is a prequel to adding a calendar command. To avoid re-querying
the branding repo on command invocation, event information will be
cached whenever we make requests. The command can then simply get an
up-to-date event schedule from the cache, with the option of forcing
an update via the 'populate_cache_events' function.
Since we cannot easily serialize entire 'Event' instances, we simply
store what's needed - the event name, and its duration.
The author has verified that the cache maintains order; in this case
chronological order based on event start date.
| -rw-r--r-- | bot/exts/backend/branding/_cog.py | 49 | ||||
| -rw-r--r-- | bot/exts/backend/branding/_repository.py | 18 |
2 files changed, 56 insertions, 11 deletions
diff --git a/bot/exts/backend/branding/_cog.py b/bot/exts/backend/branding/_cog.py index 332d4ad58..50ae11b11 100644 --- a/bot/exts/backend/branding/_cog.py +++ b/bot/exts/backend/branding/_cog.py @@ -62,6 +62,18 @@ def extract_event_duration(event: Event) -> str: return f"{start_date} - {end_date}" +def extract_event_name(event: Event) -> str: + """ + Extract title-cased event name from the path of `event`. + + An event with a path of 'events/black_history_month' will resolve to 'Black History Month'. + """ + name = event.path.split("/")[-1] # Inner-most directory name + words = name.split("_") # Words from snake case + + return " ".join(word.title() for word in words) + + class Branding(commands.Cog): """Guild branding management.""" @@ -80,6 +92,10 @@ class Branding(commands.Cog): # corresponding to the amount of times each icon has been used in the current rotation cache_icons = RedisCache() + # Cache holding all available event names & their durations; this is cached by the daemon and read by + # the calendar command with the intention of preventing API spam; doesn't contain the fallback event + cache_events = RedisCache() + def __init__(self, bot: Bot) -> None: """Instantiate repository abstraction & allow daemon to start.""" self.bot = bot @@ -271,12 +287,35 @@ class Branding(commands.Cog): """ log.debug("Synchronise: fetching current event") - event = await self.repository.get_current_event() + current_event, available_events = await self.repository.get_current_event() - if event is None: + await self.populate_cache_events(available_events) + + if current_event is None: log.error("Failed to fetch event ~ cannot synchronise!") else: - await self.enter_event(event) + await self.enter_event(current_event) + + async def populate_cache_events(self, events: t.List[Event]) -> None: + """ + Clear `cache_events` and re-populate with names and durations of `events`. + + For each event, we store its name and duration string. This is the information presented to users in the + calendar command. If a format change is needed, it has to be done here. + + The cache does not store the fallback event, as it is not shown in the calendar. + """ + log.debug(f"Populating events cache with {len(events)} events") + + await self.cache_events.clear() + + no_fallback = [event for event in events if not event.meta.is_fallback] + chronological_events = sorted(no_fallback, key=lambda event_: event_.meta.start_date) + + await self.cache_events.update({ + extract_event_name(event): extract_event_duration(event) + for event in chronological_events + }) # endregion # region: Daemon @@ -322,7 +361,9 @@ class Branding(commands.Cog): """ log.debug("Daemon awakens: checking current event") - new_event = await self.repository.get_current_event() + new_event, available_events = await self.repository.get_current_event() + + await self.populate_cache_events(available_events) if new_event is None: log.warning("Failed to get current event from the branding repository, daemon will do nothing!") diff --git a/bot/exts/backend/branding/_repository.py b/bot/exts/backend/branding/_repository.py index ef292619e..b1368c59e 100644 --- a/bot/exts/backend/branding/_repository.py +++ b/bot/exts/backend/branding/_repository.py @@ -189,11 +189,14 @@ class BrandingRepository: log.trace(f"Found {len(instances)} correctly configured events") return instances - async def get_current_event(self) -> t.Optional[Event]: + async def get_current_event(self) -> t.Tuple[t.Optional[Event], t.List[Event]]: """ Get the currently active event, or the fallback event. - Returns None in the case that no event is active, and no fallback event is found. + The second return value is a list of all available events. The caller may discard it, if not needed. + Returning all events alongside the current one prevents having to query the API twice in some cases. + + The current event may be None in the case that no event is active, and no fallback event is found. """ utc_now = datetime.utcnow() log.debug(f"Finding active event for: {utc_now}") @@ -201,17 +204,18 @@ class BrandingRepository: # As all events exist in the arbitrary year, we construct a separate object for the purposes of comparison lookup_now = date(year=ARBITRARY_YEAR, month=utc_now.month, day=utc_now.day) - events = await self.get_events() + available_events = await self.get_events() - for event in events: + for event in available_events: meta = event.meta if not meta.is_fallback and (meta.start_date <= lookup_now <= meta.end_date): - return event + return event, available_events log.debug("No active event found, looking for fallback") - for event in events: + for event in available_events: if event.meta.is_fallback: - return event + return event, available_events log.warning("No event is currently active and no fallback event was found!") + return None, available_events |