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  |