From ed0e183341dcf372bdad5a900485793ede9a7daa Mon Sep 17 00:00:00 2001 From: Weather Date: Sun, 8 Mar 2026 05:45:42 -0400 Subject: [PATCH 1/8] New CSH Calendar Logic --- .env.template | 1 + src/api/endpoints.py | 5 +- src/config.py | 1 + src/core/cshcalendar.py | 308 +++++++++++++++++++++++++++------------- 4 files changed, 217 insertions(+), 98 deletions(-) diff --git a/.env.template b/.env.template index 183571d..b2cd711 100644 --- a/.env.template +++ b/.env.template @@ -1,6 +1,7 @@ CALENDAR_URL= CALENDAR_OUTLOOK_DAYS= CALENDAR_EVENT_MAXIMUM= +CALENDAR_CACHE_REFRESH= CALENDAR_TIMEZONE= CALENDAR_API_KEY= WATCHED_CHANNELS= diff --git a/src/api/endpoints.py b/src/api/endpoints.py index 151e980..8c204ed 100644 --- a/src/api/endpoints.py +++ b/src/api/endpoints.py @@ -4,6 +4,7 @@ import httpx import random import textwrap +import asyncio from fastapi import APIRouter, Request, Form from fastapi.responses import JSONResponse @@ -23,8 +24,8 @@ def get_calendar() -> JSONResponse: JSONResponse: A JSON response containing the calendar data. """ - get_future_events_ical: list[cshcalendar.CalendarInfo] = ( - cshcalendar.get_future_events_ical() + get_future_events_ical: tuple[cshcalendar.CalendarInfo] = ( + asyncio.run(cshcalendar.get_future_events()) ) formatted_events: dict = cshcalendar.format_events(get_future_events_ical) diff --git a/src/config.py b/src/config.py index 9a02244..b517731 100644 --- a/src/config.py +++ b/src/config.py @@ -17,6 +17,7 @@ CALENDAR_EVENT_MAXIMUM: int = int(os.getenv("CALENDAR_EVENT_MAXIMUM", "10")) CALENDAR_TIMEZONE: str = os.getenv("CALENDAR_TIMEZONE", "America/New_York") CALENDAR_API_KEY: str = os.getenv("CALENDAR_API_KEY", None) +CALENDAR_CACHE_REFRESH: int = int(os.getenv("CALENDAR_CACHE_REFRESH", "10")) if SLACK_API_TOKEN in (None, ""): raise Exception("Missing SLACK_API_TOKEN") diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index 8e82498..9781d11 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -1,129 +1,212 @@ from logging import getLogger, Logger -from datetime import datetime -from datetime import date -from datetime import timedelta +from datetime import datetime,date,timedelta from zoneinfo import ZoneInfo -import time - -from googleapiclient.discovery import build from icalendar import Calendar + +import time import requests import recurring_ical_events import arrow -import config +from config import CALENDAR_CACHE_REFRESH, CALENDAR_EVENT_MAXIMUM, CALENDAR_OUTLOOK_DAYS, CALENDAR_TIMEZONE, CALENDAR_URL +import asyncio logger: Logger = getLogger(__name__) operation_start_time = time.perf_counter() logger.info("Starting up the calendar service!") -try: + +'''try: calendar_service = build("calendar", "v3", developerKey=config.CALENDAR_API_KEY) except: logger.warning( "Failed to build the calendar service, check your API key and internet connection!" ) - +''' # Automatically format all info into the class class CalendarInfo: - """ - Class that represents standardized calendar info. This is here so when pulling from different things, we can establish it as this class to only update - certain parts of the codebass - """ - - def __init__(self, name: str, date_time: date): - self.name: str = name - self.date: arrow.arrow = arrow.get(date_time) # Arrow has way cooler stuff - - + """ + Class that represents standardized calendar info. This is here so when pulling from different things, we can establish it as this class to only update + certain parts of the codebass + """ + + def __init__(self, name: str, date_time: date,loc = None): + self.name: str = name + self.date: arrow.arrow = arrow.get(date_time) # Arrow has way cooler stuff + self.location : str | None = loc + + def __eq__(self,other): + if not isinstance(other,CalendarInfo): + return False + return (self.name == other.name) + def __hash__(self): + return hash(self.name) # Might be stupid only hashing off name, but as of right now I think the implementation works + def report_timing(display_tag: str) -> None: - """ - Helper function to report how long an operation took since the lastly established operation start time. + """ + Helper function to report how long an operation took since the lastly established operation start time. - Args: - displayTag: The tag to be printed into the terminal. - """ - - operation_timestamp = time.perf_counter() - operation_start_time - logger.info(f"{operation_timestamp}:: {display_tag}") + Args: + displayTag: The tag to be printed into the terminal. + """ + operation_timestamp = time.perf_counter() - operation_start_time + logger.info(f"{operation_timestamp}:: {display_tag}") def format_events(events: list[CalendarInfo]) -> dict: - """ - Formats a parsed list of CalendarInfos, and returns the HTML required for front end - - Args: - events: The list of CalendarInfos to be formatted - - Returns: - dict: Returns a dictionary with the "data" key mapping to the HTML data. - """ - - current_date: date = datetime.now(ZoneInfo(config.CALENDAR_TIMEZONE)) - final_events = "
" - - if not events: - print("No upcoming events found.") - - for event in events: - formatted = ( - event.date.humanize() if event.date > current_date else "Happening Now!" - ) - event.date = formatted - final_events += ( - """
""" - + formatted - + """
""" - ) - final_events += ( - "" - + "".join(event.name) - + "
" - ) - final_events += "
" - return {"data": final_events} - - -def get_future_events_google_api() -> list[CalendarInfo]: - """ - Fetches the first ten events using the google api client. - Requires an API key to be estbalished as a env variable - - Returns: - list: A list of CalendarInfo objects3 - """ - # pylint: disable=no-member - events_result = ( - calendar_service.events() - .list( - calendarId="rti648k5hv7j3ae3a3rum8potk@group.calendar.google.com", - timeMin=datetime.now(ZoneInfo(config.CALENDAR_TIMEZONE)).isoformat(), - maxResults=10, - singleEvents=True, - orderBy="startTime", - ) - .execute() - ) - - events = events_result.get("items", []) - formatted_events: list[CalendarInfo] = [] - - for event in events: - start = event["start"].get("dateTime") or event["start"].get("date") - new_event = CalendarInfo(event["summary"], datetime.fromisoformat(start)) - formatted_events.append(new_event) - - return formatted_events - - -def get_future_events_ical() -> list[CalendarInfo]: + """ + Formats a parsed list of CalendarInfos, and returns the HTML required for front end + + Args: + events: The list of CalendarInfos to be formatted + + Returns: + dict: Returns a dictionary with the "data" key mapping to the HTML data. + """ + + current_date: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) + final_events = "
" + + if not events: + print("No upcoming events found.") + else: + for event in events: + formatted = "" + if event.date < current_date: + if event.location: + formatted = f"Happening in {event.location}" + else: + formatted = "Happening Now!" + else: + formatted = event.date.humanize() + + final_events += ( + """
""" + + formatted + + """
""" + ) + final_events += ( + "" + + "".join(event.name) + + "
" + ) + final_events += "
" + return {"data": final_events} + +calendar_cache : tuple[CalendarInfo] = () +cal_last_update = None +header_none_match = None +header_last_modified = None +currently_rebuilding = False + +async def rebuildCalendar(): + global calendar_cache,cal_last_update + + def blocking_section(): # this is what i found as the solution, i dont know if its good or not but here goes bullshit! + found_events: set[CalendarInfo] = set() + try: + response = requests.get(CALENDAR_URL, timeout=10) + report_timing("Fetched the calendar from google") + + cal = Calendar.from_ical(response.content) + report_timing("Converted the calendar info") + + current_time = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) + + fetched_daily_events: list = recurring_ical_events.of(cal).between( + current_time, current_time + timedelta(days=CALENDAR_OUTLOOK_DAYS) + ) + + for event in fetched_daily_events: + if len(found_events) >= CALENDAR_EVENT_MAXIMUM: + break + else: + new_event = CalendarInfo( + event.get("SUMMARY"), event.get("DTSTART").dt, event.get("LOCATION") + ) + if not new_event in found_events: + found_events.add(new_event) + except Exception as e: + logger.warning("Failed to rebuild calendar cache! Error:") + logger.warning(e) + + return sorted(found_events, key=lambda x: x.date) + + cal_last_update = datetime.now() + sorted_events = await asyncio.to_thread(blocking_section) + calendar_cache = sorted_events + + + +async def get_future_events(): + """ + Returns the first events up to event maximum within the the calendar outlook day amount + custom object has name, date and the location + + Returns: + list: A list of CalendarInfo objects + """ + global calendar_cache,cal_last_update,header_last_modified,header_none_match, currently_rebuilding + + if currently_rebuilding: + logger.info("Calendar is currently rebuilding, waiting for result!") + while currently_rebuilding: + time.sleep(1) + return calendar_cache + + cal_correct_length = len(calendar_cache) == CALENDAR_EVENT_MAXIMUM + if cal_last_update and cal_correct_length and cal_last_update > (datetime.now() - timedelta(minutes=CALENDAR_CACHE_REFRESH)): + logger.info("Pulling from CSH calendar cache!") + return calendar_cache + else: + logger.info("Checking to rebuild CSH calendar....") + + try: + headers = {} + if header_none_match: + headers["If-None-Match"] = header_none_match + if header_last_modified: + headers["If-Modified-Since"] = header_last_modified + + response = requests.get(CALENDAR_URL, timeout=10,headers=headers) + + if response.status_code == 304 and cal_correct_length: #Nothing changed status code + logger.info("CSH calendar not updated, refreshing last update!") + cal_last_update = datetime.now() + return calendar_cache + response.raise_for_status() + + header_none_match = response.headers.get("ETag") + header_last_modified = response.headers.get("Last-Modified") + + if currently_rebuilding: + logger.info("Calendar is currently rebuilding, waiting for result!") + while currently_rebuilding: + time.sleep(1) + return calendar_cache + + if cal_correct_length: + logger.info("Calendar cache is full length, rebuilding async!") + asyncio.create_task(rebuildCalendar()) #Calendar is correct length, we can just run this in the background + else: + currently_rebuilding = True + logger.info("Calendar cache is NOT full length, yielding rebuild!") + await rebuildCalendar() + currently_rebuilding = False + return calendar_cache + except Exception as e: + logger.warning("Failed to fetch the Calendar!") + logger.warning(e) + + +'''def get_future_events_ical() -> list[CalendarInfo]: """ Fetches the first ten events using the Ical library, loops through the first 7 days of the current time. Returns: - list: A list of CalendarInfo objects3 + list: A list of CalendarInfo objects """ found_events: list[CalendarInfo] = [] try: @@ -161,3 +244,36 @@ def get_future_events_ical() -> list[CalendarInfo]: sorted_events = sorted(found_events, key=lambda x: x.date) return sorted_events +''' + +'''def get_future_events_google_api() -> list[CalendarInfo]: + """ + Fetches the first ten events using the google api client. + Requires an API key to be estbalished as a env variable + + Returns: + list: A list of CalendarInfo objects3 + """ + # pylint: disable=no-member + events_result = ( + calendar_service.events() + .list( + calendarId="rti648k5hv7j3ae3a3rum8potk@group.calendar.google.com", + timeMin=datetime.now(ZoneInfo(config.CALENDAR_TIMEZONE)).isoformat(), + maxResults=10, + singleEvents=True, + orderBy="startTime", + ) + .execute() + ) + + events = events_result.get("items", []) + formatted_events: list[CalendarInfo] = [] + + for event in events: + start = event["start"].get("dateTime") or event["start"].get("date") + new_event = CalendarInfo(event["summary"], datetime.fromisoformat(start)) + formatted_events.append(new_event) + + return formatted_events +''' From 3a20c448b349f9a6b0a59301dcd1b820906c3aed Mon Sep 17 00:00:00 2001 From: Weather Date: Mon, 9 Mar 2026 01:20:43 -0400 Subject: [PATCH 2/8] Fixed up CSH text --- src/core/cshcalendar.py | 3 ++- src/templates/index.html | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index 9781d11..ad5c9ff 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -72,7 +72,7 @@ def format_events(events: list[CalendarInfo]) -> dict: print("No upcoming events found.") else: for event in events: - formatted = "" + formatted : str = "" if event.date < current_date: if event.location: formatted = f"Happening in {event.location}" @@ -80,6 +80,7 @@ def format_events(events: list[CalendarInfo]) -> dict: formatted = "Happening Now!" else: formatted = event.date.humanize() + formatted = formatted.title() final_events += ( """
""" diff --git a/src/templates/index.html b/src/templates/index.html index fbfe873..1f23266 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -29,8 +29,29 @@
+
From c49f369ceece7701b278fc094206ad16a1d9e258 Mon Sep 17 00:00:00 2001 From: Weather Date: Mon, 9 Mar 2026 19:44:40 -0400 Subject: [PATCH 3/8] Convert to httpx --- src/api/endpoints.py | 4 +- src/core/cshcalendar.py | 391 ++++++++++++++++++++++------------------ 2 files changed, 220 insertions(+), 175 deletions(-) diff --git a/src/api/endpoints.py b/src/api/endpoints.py index 8c204ed..11b4af7 100644 --- a/src/api/endpoints.py +++ b/src/api/endpoints.py @@ -24,8 +24,8 @@ def get_calendar() -> JSONResponse: JSONResponse: A JSON response containing the calendar data. """ - get_future_events_ical: tuple[cshcalendar.CalendarInfo] = ( - asyncio.run(cshcalendar.get_future_events()) + get_future_events_ical: tuple[cshcalendar.CalendarInfo] = asyncio.run( + cshcalendar.get_future_events() ) formatted_events: dict = cshcalendar.format_events(get_future_events_ical) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index ad5c9ff..909199e 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -1,204 +1,249 @@ from logging import getLogger, Logger -from datetime import datetime,date,timedelta +from datetime import datetime, date, timedelta from zoneinfo import ZoneInfo from icalendar import Calendar import time -import requests +import httpx import recurring_ical_events import arrow -from config import CALENDAR_CACHE_REFRESH, CALENDAR_EVENT_MAXIMUM, CALENDAR_OUTLOOK_DAYS, CALENDAR_TIMEZONE, CALENDAR_URL +from config import ( + CALENDAR_CACHE_REFRESH, + CALENDAR_EVENT_MAXIMUM, + CALENDAR_OUTLOOK_DAYS, + CALENDAR_TIMEZONE, + CALENDAR_URL, +) import asyncio +calendar_cache: tuple[CalendarInfo] = () +cal_last_update = None +header_none_match = None +header_last_modified = None +cal_currently_rebuilding = False + logger: Logger = getLogger(__name__) operation_start_time = time.perf_counter() - logger.info("Starting up the calendar service!") -'''try: +client = httpx.AsyncClient() + +"""try: calendar_service = build("calendar", "v3", developerKey=config.CALENDAR_API_KEY) except: logger.warning( "Failed to build the calendar service, check your API key and internet connection!" ) -''' +""" + + # Automatically format all info into the class class CalendarInfo: - """ - Class that represents standardized calendar info. This is here so when pulling from different things, we can establish it as this class to only update - certain parts of the codebass - """ - - def __init__(self, name: str, date_time: date,loc = None): - self.name: str = name - self.date: arrow.arrow = arrow.get(date_time) # Arrow has way cooler stuff - self.location : str | None = loc - - def __eq__(self,other): - if not isinstance(other,CalendarInfo): - return False - return (self.name == other.name) - def __hash__(self): - return hash(self.name) # Might be stupid only hashing off name, but as of right now I think the implementation works - + """ + Class that represents standardized calendar info. This is here so when pulling from different things, we can establish it as this class to only update + certain parts of the codebass + """ + + def __init__(self, name: str, date_time: date, loc=None): + self.name: str = name + self.date: arrow.arrow = arrow.get(date_time) # Arrow has way cooler stuff + self.location: str | None = loc + + def __eq__(self, other): + if not isinstance(other, CalendarInfo): + return False + return self.name == other.name + + def __hash__(self): + return hash( + self.name + ) # Might be stupid only hashing off name, but as of right now I think the implementation works + + def report_timing(display_tag: str) -> None: - """ - Helper function to report how long an operation took since the lastly established operation start time. + """ + Helper function to report how long an operation took since the lastly established operation start time. + + Args: + displayTag: The tag to be printed into the terminal. + """ - Args: - displayTag: The tag to be printed into the terminal. - """ + operation_timestamp = time.perf_counter() - operation_start_time + logger.info(f"{operation_timestamp}:: {display_tag}") - operation_timestamp = time.perf_counter() - operation_start_time - logger.info(f"{operation_timestamp}:: {display_tag}") def format_events(events: list[CalendarInfo]) -> dict: - """ - Formats a parsed list of CalendarInfos, and returns the HTML required for front end - - Args: - events: The list of CalendarInfos to be formatted - - Returns: - dict: Returns a dictionary with the "data" key mapping to the HTML data. - """ - - current_date: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) - final_events = "
" - - if not events: - print("No upcoming events found.") - else: - for event in events: - formatted : str = "" - if event.date < current_date: - if event.location: - formatted = f"Happening in {event.location}" - else: - formatted = "Happening Now!" - else: - formatted = event.date.humanize() - formatted = formatted.title() - - final_events += ( - """
""" - + formatted - + """
""" - ) - final_events += ( - "" - + "".join(event.name) - + "
" - ) - final_events += "
" - return {"data": final_events} - -calendar_cache : tuple[CalendarInfo] = () -cal_last_update = None -header_none_match = None -header_last_modified = None -currently_rebuilding = False - -async def rebuildCalendar(): - global calendar_cache,cal_last_update - - def blocking_section(): # this is what i found as the solution, i dont know if its good or not but here goes bullshit! - found_events: set[CalendarInfo] = set() - try: - response = requests.get(CALENDAR_URL, timeout=10) - report_timing("Fetched the calendar from google") - - cal = Calendar.from_ical(response.content) - report_timing("Converted the calendar info") - - current_time = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) - - fetched_daily_events: list = recurring_ical_events.of(cal).between( - current_time, current_time + timedelta(days=CALENDAR_OUTLOOK_DAYS) - ) - - for event in fetched_daily_events: - if len(found_events) >= CALENDAR_EVENT_MAXIMUM: - break - else: - new_event = CalendarInfo( - event.get("SUMMARY"), event.get("DTSTART").dt, event.get("LOCATION") - ) - if not new_event in found_events: - found_events.add(new_event) - except Exception as e: - logger.warning("Failed to rebuild calendar cache! Error:") - logger.warning(e) - - return sorted(found_events, key=lambda x: x.date) - - cal_last_update = datetime.now() - sorted_events = await asyncio.to_thread(blocking_section) - calendar_cache = sorted_events - - - + """ + Formats a parsed list of CalendarInfos, and returns the HTML required for front end + + Args: + events: The list of CalendarInfos to be formatted + + Returns: + dict: Returns a dictionary with the "data" key mapping to the HTML data. + """ + + current_date: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) + final_events = "
" + + if not events: + final_events += "
" + return {"data": final_events} + + for event in events: + formatted: str = "" + if event.date < current_date: + formatted = ( + f"Happening in {event.location}!" + if event.location + else "Happening Now!" + ) + else: + formatted = event.date.humanize() + formatted = formatted.title() + + final_events += ( + """
""" + + formatted + + """
""" + ) + final_events += ( + "" + + "".join(event.name) + + "
" + ) + final_events += "
" + return {"data": final_events} + + +async def rebuild_calendar(): + """ + Fetches and rebuilds the global calendar cache. This does NOT return a new cache, but changes the global calendar cache + """ + global calendar_cache, cal_last_update + + found_events: set[CalendarInfo] = set() + try: + response = await client.get(CALENDAR_URL, timeout=10) + response.raise_for_status() + report_timing("Fetched the calendar from google") + + cal = Calendar.from_ical(response.content) + report_timing("Converted the calendar info") + + current_time = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) + + fetched_daily_events: list = recurring_ical_events.of(cal).between( + current_time, current_time + timedelta(days=CALENDAR_OUTLOOK_DAYS) + ) + + for event in fetched_daily_events: + if len(found_events) >= CALENDAR_EVENT_MAXIMUM: + break + + new_event = CalendarInfo( + event.get("SUMMARY"), + event.get("DTSTART").dt, + event.get("LOCATION"), + ) + if not new_event in found_events: + found_events.add(new_event) + except Exception as e: + logger.warning("Failed to rebuild calendar cache! Error:") + logger.warning(e) + + cal_last_update = datetime.now() + sorted_events = sorted(found_events, key=lambda x: x.date) + calendar_cache = sorted_events + + +async def wait_for_rebuild(): + """ + Simple yielding function for waiting to return the freshly made calendar cache, rather than proceeding with the obtain + + Returns: + list: A list of CalendarInfo objects + """ + global cal_currently_rebuilding + while cal_currently_rebuilding: + asyncio.sleep(1) + + return calendar_cache + + async def get_future_events(): - """ - Returns the first events up to event maximum within the the calendar outlook day amount - custom object has name, date and the location - - Returns: - list: A list of CalendarInfo objects - """ - global calendar_cache,cal_last_update,header_last_modified,header_none_match, currently_rebuilding - - if currently_rebuilding: - logger.info("Calendar is currently rebuilding, waiting for result!") - while currently_rebuilding: - time.sleep(1) - return calendar_cache - - cal_correct_length = len(calendar_cache) == CALENDAR_EVENT_MAXIMUM - if cal_last_update and cal_correct_length and cal_last_update > (datetime.now() - timedelta(minutes=CALENDAR_CACHE_REFRESH)): - logger.info("Pulling from CSH calendar cache!") - return calendar_cache - else: - logger.info("Checking to rebuild CSH calendar....") - - try: - headers = {} - if header_none_match: - headers["If-None-Match"] = header_none_match - if header_last_modified: - headers["If-Modified-Since"] = header_last_modified - - response = requests.get(CALENDAR_URL, timeout=10,headers=headers) - - if response.status_code == 304 and cal_correct_length: #Nothing changed status code - logger.info("CSH calendar not updated, refreshing last update!") - cal_last_update = datetime.now() - return calendar_cache - response.raise_for_status() - - header_none_match = response.headers.get("ETag") - header_last_modified = response.headers.get("Last-Modified") - - if currently_rebuilding: - logger.info("Calendar is currently rebuilding, waiting for result!") - while currently_rebuilding: - time.sleep(1) - return calendar_cache - - if cal_correct_length: - logger.info("Calendar cache is full length, rebuilding async!") - asyncio.create_task(rebuildCalendar()) #Calendar is correct length, we can just run this in the background - else: - currently_rebuilding = True - logger.info("Calendar cache is NOT full length, yielding rebuild!") - await rebuildCalendar() - currently_rebuilding = False - return calendar_cache - except Exception as e: - logger.warning("Failed to fetch the Calendar!") - logger.warning(e) + """ + Returns the first events up to event maximum within the the calendar outlook day amount + custom object has name, date and the location + + Returns: + list: A list of CalendarInfo objects + """ + global \ + calendar_cache, \ + cal_last_update, \ + header_last_modified, \ + header_none_match, \ + cal_currently_rebuilding + + if cal_currently_rebuilding: + return await wait_for_rebuild() + + cal_correct_length = len(calendar_cache) == CALENDAR_EVENT_MAXIMUM + if ( + cal_last_update + and cal_correct_length + and cal_last_update + > (datetime.now() - timedelta(minutes=CALENDAR_CACHE_REFRESH)) + ): + logger.info("Pulling from CSH calendar cache!") + return calendar_cache + + logger.info("Checking to rebuild CSH Calendar...") + try: + headers = {} + if header_none_match: + headers["If-None-Match"] = header_none_match + if header_last_modified: + headers["If-Modified-Since"] = header_last_modified + + response = await client.get(CALENDAR_URL, timeout=10, headers=headers) + response.raise_for_status() + + if ( # Nothing changed status code + response.status_code == 304 and cal_correct_length + ): + logger.info("CSH calendar not updated, refreshing last update!") + cal_last_update = datetime.now() + return calendar_cache + + header_none_match = response.headers.get("ETag") + header_last_modified = response.headers.get("Last-Modified") + + if cal_currently_rebuilding: + return await wait_for_rebuild() + + if cal_correct_length: + logger.info("Calendar cache is full length, rebuilding async!") + asyncio.create_task( + rebuild_calendar() + ) # Calendar is correct length, we can just run this in the background + else: + logger.info("Calendar cache is NOT full length, yielding rebuild!") + cal_currently_rebuilding = True + await rebuild_calendar() + cal_currently_rebuilding = False + return calendar_cache + except Exception as e: + logger.warning("Failed to fetch the Calendar!") + logger.warning(e) + + +def closeClient(): + global client + client.aclose() '''def get_future_events_ical() -> list[CalendarInfo]: From bef252594f41311eaec921a0dc8d3ecd5507eff4 Mon Sep 17 00:00:00 2001 From: Weather Date: Mon, 9 Mar 2026 19:47:31 -0400 Subject: [PATCH 4/8] Added no events to HTML display --- src/core/cshcalendar.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index 909199e..628ddf9 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -90,6 +90,16 @@ def format_events(events: list[CalendarInfo]) -> dict: if not events: final_events += "
" + final_events += ( + """
""" + + " " + + """
""" + ) + final_events += ( + "" + + "No Events on the Calendar!" + + "
" + ) return {"data": final_events} for event in events: From e093dc8f06c115f05779a9508fa1fd6f38fd3d9b Mon Sep 17 00:00:00 2001 From: Weather Date: Mon, 9 Mar 2026 20:55:53 -0400 Subject: [PATCH 5/8] Cleanup --- src/core/cshcalendar.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index 628ddf9..fdbf87d 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -17,14 +17,20 @@ ) import asyncio -calendar_cache: tuple[CalendarInfo] = () -cal_last_update = None -header_none_match = None -header_last_modified = None -cal_currently_rebuilding = False +calendar_cache: tuple[CalendarInfo] = () # The current cache of the calendar +cal_last_update: date = ( + None # The last time the calendar was fetched and updated the cache +) +header_none_match: str = ( + None # Used for the httpx.get to see if the current calendar matches +) +header_last_modified: str = ( + None # Used for the httpx.get to see when the calndar was last modified +) +cal_currently_rebuilding: bool = False # Tells if the calendar is being rebuilt logger: Logger = getLogger(__name__) -operation_start_time = time.perf_counter() +operation_start_time: float = time.perf_counter() logger.info("Starting up the calendar service!") client = httpx.AsyncClient() @@ -46,10 +52,10 @@ class CalendarInfo: certain parts of the codebass """ - def __init__(self, name: str, date_time: date, loc=None): + def __init__(self, name: str, date_time: date, location: str | None = None): self.name: str = name self.date: arrow.arrow = arrow.get(date_time) # Arrow has way cooler stuff - self.location: str | None = loc + self.location: str | None = location def __eq__(self, other): if not isinstance(other, CalendarInfo): @@ -74,7 +80,7 @@ def report_timing(display_tag: str) -> None: logger.info(f"{operation_timestamp}:: {display_tag}") -def format_events(events: list[CalendarInfo]) -> dict: +def format_events(events: tuple[CalendarInfo]) -> dict: """ Formats a parsed list of CalendarInfos, and returns the HTML required for front end @@ -86,7 +92,7 @@ def format_events(events: list[CalendarInfo]) -> dict: """ current_date: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) - final_events = "
" + final_events: str = "
" if not events: final_events += "
" @@ -158,15 +164,13 @@ async def rebuild_calendar(): event.get("DTSTART").dt, event.get("LOCATION"), ) - if not new_event in found_events: - found_events.add(new_event) + found_events.add(new_event) except Exception as e: logger.warning("Failed to rebuild calendar cache! Error:") logger.warning(e) cal_last_update = datetime.now() - sorted_events = sorted(found_events, key=lambda x: x.date) - calendar_cache = sorted_events + calendar_cache = sorted(found_events, key=lambda x: x.date) async def wait_for_rebuild(): @@ -251,7 +255,7 @@ async def get_future_events(): logger.warning(e) -def closeClient(): +def close_client(): global client client.aclose() From c6b97d0b18f08325fcfbf0a5d6919c6bbec0410e Mon Sep 17 00:00:00 2001 From: Weather Date: Mon, 9 Mar 2026 21:21:11 -0400 Subject: [PATCH 6/8] Added lifespan async to FastAPI --- src/core/cshcalendar.py | 17 ++++++++++------- src/main.py | 14 +++++++++++--- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index fdbf87d..eaef48d 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -32,8 +32,7 @@ logger: Logger = getLogger(__name__) operation_start_time: float = time.perf_counter() logger.info("Starting up the calendar service!") - -client = httpx.AsyncClient() +cshcal_client = httpx.AsyncClient() """try: calendar_service = build("calendar", "v3", developerKey=config.CALENDAR_API_KEY) @@ -142,7 +141,7 @@ async def rebuild_calendar(): found_events: set[CalendarInfo] = set() try: - response = await client.get(CALENDAR_URL, timeout=10) + response = await cshcal_client.get(CALENDAR_URL, timeout=10) response.raise_for_status() report_timing("Fetched the calendar from google") @@ -223,7 +222,7 @@ async def get_future_events(): if header_last_modified: headers["If-Modified-Since"] = header_last_modified - response = await client.get(CALENDAR_URL, timeout=10, headers=headers) + response = await cshcal_client.get(CALENDAR_URL, timeout=10, headers=headers) response.raise_for_status() if ( # Nothing changed status code @@ -255,9 +254,13 @@ async def get_future_events(): logger.warning(e) -def close_client(): - global client - client.aclose() +async def close_cal_client(): + global cshcal_client + try: + await cshcal_client.aclose() + except RuntimeError as e: + logger.warning("EVENT LOOP HAS ALREADY CLOSED, FAILED TO CLOSE csh_cal") + return '''def get_future_events_ical() -> list[CalendarInfo]: diff --git a/src/main.py b/src/main.py index 420ded8..1313585 100644 --- a/src/main.py +++ b/src/main.py @@ -14,15 +14,23 @@ from fastapi.templating import Jinja2Templates from fastapi.responses import RedirectResponse, HTMLResponse +from contextlib import asynccontextmanager + from config import BASE_DIR from api import endpoints - +from core.cshcalendar import close_cal_client logger: Logger = getLogger(__name__) +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("Starting up the Jumpstart application!") + yield + logger.info("Shutting down the Jumpstart application!") + await close_cal_client() + logger.info("Succesfully shut down the Jumpstart application!") -logger.info("Starting up the Jumpstart application!") -app: FastAPI = FastAPI(docs_url="/swag") +app: FastAPI = FastAPI(docs_url="/swag",lifespan=lifespan) logger.info("Mounting static files and templates!") app.mount( From e94175700370a88e1ba47467ef4f211f0af0e65a Mon Sep 17 00:00:00 2001 From: Weather Date: Tue, 10 Mar 2026 14:50:05 -0400 Subject: [PATCH 7/8] Fixes + Cleanup --- .env.template | 1 - src/config.py | 4 - src/core/cshcalendar.py | 159 ++++++++++------------------------------ src/main.py | 5 +- 4 files changed, 43 insertions(+), 126 deletions(-) diff --git a/.env.template b/.env.template index b2cd711..78ff702 100644 --- a/.env.template +++ b/.env.template @@ -3,7 +3,6 @@ CALENDAR_OUTLOOK_DAYS= CALENDAR_EVENT_MAXIMUM= CALENDAR_CACHE_REFRESH= CALENDAR_TIMEZONE= -CALENDAR_API_KEY= WATCHED_CHANNELS= SLACK_API_TOKEN= SLACK_JUMPSTART_MESSAGE= \ No newline at end of file diff --git a/src/config.py b/src/config.py index b517731..7c45f0b 100644 --- a/src/config.py +++ b/src/config.py @@ -16,14 +16,10 @@ CALENDAR_OUTLOOK_DAYS: int = int(os.getenv("CALENDAR_OUTLOOK_DAYS", "7")) CALENDAR_EVENT_MAXIMUM: int = int(os.getenv("CALENDAR_EVENT_MAXIMUM", "10")) CALENDAR_TIMEZONE: str = os.getenv("CALENDAR_TIMEZONE", "America/New_York") -CALENDAR_API_KEY: str = os.getenv("CALENDAR_API_KEY", None) CALENDAR_CACHE_REFRESH: int = int(os.getenv("CALENDAR_CACHE_REFRESH", "10")) if SLACK_API_TOKEN in (None, ""): raise Exception("Missing SLACK_API_TOKEN") -if CALENDAR_API_KEY in (None, "") and CALENDAR_URL in (None, ""): - raise Exception("Missing CALENDAR_API_KEY or CALENDAR_URL") - with open(os.path.join(BASE_DIR, "static", "slack", "dm_request_template.json")) as f: SLACK_DM_TEMPLATE = json.load(f) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index eaef48d..a690b95 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -1,8 +1,9 @@ from logging import getLogger, Logger from datetime import datetime, date, timedelta from zoneinfo import ZoneInfo -from icalendar import Calendar +# from icalendar import Calendar +from icalendar.cal import Event, Calendar import time import httpx import recurring_ical_events @@ -18,13 +19,13 @@ import asyncio calendar_cache: tuple[CalendarInfo] = () # The current cache of the calendar -cal_last_update: date = ( +cal_last_update: date | None = ( None # The last time the calendar was fetched and updated the cache ) -header_none_match: str = ( +header_none_match: str | None = ( None # Used for the httpx.get to see if the current calendar matches ) -header_last_modified: str = ( +header_last_modified: str | None = ( None # Used for the httpx.get to see when the calndar was last modified ) cal_currently_rebuilding: bool = False # Tells if the calendar is being rebuilt @@ -34,15 +35,6 @@ logger.info("Starting up the calendar service!") cshcal_client = httpx.AsyncClient() -"""try: - calendar_service = build("calendar", "v3", developerKey=config.CALENDAR_API_KEY) -except: - logger.warning( - "Failed to build the calendar service, check your API key and internet connection!" - ) - -""" - # Automatically format all info into the class class CalendarInfo: @@ -79,15 +71,15 @@ def report_timing(display_tag: str) -> None: logger.info(f"{operation_timestamp}:: {display_tag}") -def format_events(events: tuple[CalendarInfo]) -> dict: +def format_events(events: tuple[CalendarInfo]) -> dict[str, str]: """ Formats a parsed list of CalendarInfos, and returns the HTML required for front end Args: - events: The list of CalendarInfos to be formatted + events: The list of CalendarInfos to be formatted Returns: - dict: Returns a dictionary with the "data" key mapping to the HTML data. + dict: Returns a dictionary with the "data" key mapping to the HTML data. """ current_date: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) @@ -116,8 +108,7 @@ def format_events(events: tuple[CalendarInfo]) -> dict: else "Happening Now!" ) else: - formatted = event.date.humanize() - formatted = formatted.title() + formatted = event.date.humanize().title() final_events += ( """
""" @@ -133,24 +124,26 @@ def format_events(events: tuple[CalendarInfo]) -> dict: return {"data": final_events} -async def rebuild_calendar(): +async def rebuild_calendar() -> None: """ Fetches and rebuilds the global calendar cache. This does NOT return a new cache, but changes the global calendar cache """ - global calendar_cache, cal_last_update + global calendar_cache, cal_last_update, cal_currently_rebuilding + + cal_currently_rebuilding = True found_events: set[CalendarInfo] = set() try: - response = await cshcal_client.get(CALENDAR_URL, timeout=10) + response: httpx.Response = await cshcal_client.get(CALENDAR_URL, timeout=10) response.raise_for_status() report_timing("Fetched the calendar from google") - cal = Calendar.from_ical(response.content) + cal: Calendar = Calendar.from_ical(response.content) report_timing("Converted the calendar info") - current_time = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) + current_time: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) - fetched_daily_events: list = recurring_ical_events.of(cal).between( + fetched_daily_events: list[Event] = recurring_ical_events.of(cal).between( current_time, current_time + timedelta(days=CALENDAR_OUTLOOK_DAYS) ) @@ -158,7 +151,7 @@ async def rebuild_calendar(): if len(found_events) >= CALENDAR_EVENT_MAXIMUM: break - new_event = CalendarInfo( + new_event: CalendarInfo = CalendarInfo( event.get("SUMMARY"), event.get("DTSTART").dt, event.get("LOCATION"), @@ -168,16 +161,17 @@ async def rebuild_calendar(): logger.warning("Failed to rebuild calendar cache! Error:") logger.warning(e) - cal_last_update = datetime.now() + cal_last_update = current_time calendar_cache = sorted(found_events, key=lambda x: x.date) + cal_currently_rebuilding = False -async def wait_for_rebuild(): +async def wait_for_rebuild() -> tuple[CalendarInfo]: """ Simple yielding function for waiting to return the freshly made calendar cache, rather than proceeding with the obtain - Returns: - list: A list of CalendarInfo objects + Returns: + list: A list of CalendarInfo objects """ global cal_currently_rebuilding while cal_currently_rebuilding: @@ -186,13 +180,13 @@ async def wait_for_rebuild(): return calendar_cache -async def get_future_events(): +async def get_future_events() -> tuple[CalendarInfo]: """ Returns the first events up to event maximum within the the calendar outlook day amount custom object has name, date and the location - Returns: - list: A list of CalendarInfo objects + Returns: + list: A list of CalendarInfo objects """ global \ calendar_cache, \ @@ -204,7 +198,7 @@ async def get_future_events(): if cal_currently_rebuilding: return await wait_for_rebuild() - cal_correct_length = len(calendar_cache) == CALENDAR_EVENT_MAXIMUM + cal_correct_length: bool = len(calendar_cache) == CALENDAR_EVENT_MAXIMUM if ( cal_last_update and cal_correct_length @@ -216,13 +210,15 @@ async def get_future_events(): logger.info("Checking to rebuild CSH Calendar...") try: - headers = {} + headers: dict[str, str | None] = {} if header_none_match: headers["If-None-Match"] = header_none_match if header_last_modified: headers["If-Modified-Since"] = header_last_modified - response = await cshcal_client.get(CALENDAR_URL, timeout=10, headers=headers) + response: httpx.Response = await cshcal_client.get( + CALENDAR_URL, timeout=10, headers=headers + ) response.raise_for_status() if ( # Nothing changed status code @@ -245,98 +241,21 @@ async def get_future_events(): ) # Calendar is correct length, we can just run this in the background else: logger.info("Calendar cache is NOT full length, yielding rebuild!") - cal_currently_rebuilding = True await rebuild_calendar() - cal_currently_rebuilding = False return calendar_cache except Exception as e: logger.warning("Failed to fetch the Calendar!") logger.warning(e) -async def close_cal_client(): - global cshcal_client - try: - await cshcal_client.aclose() - except RuntimeError as e: - logger.warning("EVENT LOOP HAS ALREADY CLOSED, FAILED TO CLOSE csh_cal") - return - - -'''def get_future_events_ical() -> list[CalendarInfo]: +async def close_cal_client() -> None: """ - Fetches the first ten events using the Ical library, - loops through the first 7 days of the current time. - - Returns: - list: A list of CalendarInfo objects + Closes the calendar's HTTPX client, logs if the event loops has been + closed prior to the function being called """ - found_events: list[CalendarInfo] = [] + global cshcal_client try: - response = requests.get(config.CALENDAR_URL, timeout=10) - report_timing("Fetched the calendar from google") - - cal = Calendar.from_ical(response.content) - report_timing("Converted the calendar info") - - current_day = 1 - current_time = datetime.now(ZoneInfo(config.CALENDAR_TIMEZONE)) - - while (current_day < config.CALENDAR_OUTLOOK_DAYS) and ( - len(found_events) < config.CALENDAR_EVENT_MAXIMUM - ): - fetched_daily_events: list = recurring_ical_events.of(cal).between( - current_time, current_time + timedelta(days=1) - ) - report_timing("Sorted events on day " + str(current_day)) - - for event in fetched_daily_events: - if len(found_events) >= config.CALENDAR_EVENT_MAXIMUM: - break - else: - new_event = CalendarInfo( - event.get("SUMMARY"), event.get("DTSTART").dt - ) - found_events.append(new_event) - - current_time += timedelta(days=1) - current_day += 1 - except Exception as e: - logger.warning("Failed to fetch the Calendar! Failed with error:") - logger.warning(e) - - sorted_events = sorted(found_events, key=lambda x: x.date) - return sorted_events -''' - -'''def get_future_events_google_api() -> list[CalendarInfo]: - """ - Fetches the first ten events using the google api client. - Requires an API key to be estbalished as a env variable - - Returns: - list: A list of CalendarInfo objects3 - """ - # pylint: disable=no-member - events_result = ( - calendar_service.events() - .list( - calendarId="rti648k5hv7j3ae3a3rum8potk@group.calendar.google.com", - timeMin=datetime.now(ZoneInfo(config.CALENDAR_TIMEZONE)).isoformat(), - maxResults=10, - singleEvents=True, - orderBy="startTime", - ) - .execute() - ) - - events = events_result.get("items", []) - formatted_events: list[CalendarInfo] = [] - - for event in events: - start = event["start"].get("dateTime") or event["start"].get("date") - new_event = CalendarInfo(event["summary"], datetime.fromisoformat(start)) - formatted_events.append(new_event) - - return formatted_events -''' + await cshcal_client.aclose() + except RuntimeError as e: + logger.warning("EVENT LOOP HAS ALREADY CLOSED, FAILED TO CLOSE csh_cal") + return diff --git a/src/main.py b/src/main.py index 1313585..8f39778 100644 --- a/src/main.py +++ b/src/main.py @@ -20,8 +20,10 @@ from api import endpoints from core.cshcalendar import close_cal_client + logger: Logger = getLogger(__name__) + @asynccontextmanager async def lifespan(app: FastAPI): logger.info("Starting up the Jumpstart application!") @@ -30,7 +32,8 @@ async def lifespan(app: FastAPI): await close_cal_client() logger.info("Succesfully shut down the Jumpstart application!") -app: FastAPI = FastAPI(docs_url="/swag",lifespan=lifespan) + +app: FastAPI = FastAPI(docs_url="/swag", lifespan=lifespan) logger.info("Mounting static files and templates!") app.mount( From 7037df9e8c4dd71734bfef39722205b447984948 Mon Sep 17 00:00:00 2001 From: Weather Date: Thu, 12 Mar 2026 03:03:41 -0500 Subject: [PATCH 8/8] Fixed Issues --- src/core/cshcalendar.py | 49 +++++++++++++++++------------------------ src/main.py | 4 ++-- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/core/cshcalendar.py b/src/core/cshcalendar.py index a690b95..cd4c365 100644 --- a/src/core/cshcalendar.py +++ b/src/core/cshcalendar.py @@ -1,10 +1,9 @@ from logging import getLogger, Logger -from datetime import datetime, date, timedelta +from datetime import datetime, date, timedelta, time from zoneinfo import ZoneInfo # from icalendar import Calendar from icalendar.cal import Event, Calendar -import time import httpx import recurring_ical_events import arrow @@ -31,7 +30,6 @@ cal_currently_rebuilding: bool = False # Tells if the calendar is being rebuilt logger: Logger = getLogger(__name__) -operation_start_time: float = time.perf_counter() logger.info("Starting up the calendar service!") cshcal_client = httpx.AsyncClient() @@ -45,32 +43,27 @@ class CalendarInfo: def __init__(self, name: str, date_time: date, location: str | None = None): self.name: str = name + + if isinstance(date_time, date) and not isinstance(date_time, datetime): + date_time = arrow.combine( + date_time, time.min, tzinfo=ZoneInfo(CALENDAR_TIMEZONE) + ) + elif not date_time.tzinfo: + date_time = date_time.replace(tzinfo=ZoneInfo(CALENDAR_TIMEZONE)) self.date: arrow.arrow = arrow.get(date_time) # Arrow has way cooler stuff self.location: str | None = location def __eq__(self, other): if not isinstance(other, CalendarInfo): return False - return self.name == other.name + return (self.name == other.name) and (self.date == self.date) def __hash__(self): - return hash( - self.name + return ( + hash(self.name) + hash(self.date) ) # Might be stupid only hashing off name, but as of right now I think the implementation works -def report_timing(display_tag: str) -> None: - """ - Helper function to report how long an operation took since the lastly established operation start time. - - Args: - displayTag: The tag to be printed into the terminal. - """ - - operation_timestamp = time.perf_counter() - operation_start_time - logger.info(f"{operation_timestamp}:: {display_tag}") - - def format_events(events: tuple[CalendarInfo]) -> dict[str, str]: """ Formats a parsed list of CalendarInfos, and returns the HTML required for front end @@ -132,14 +125,12 @@ async def rebuild_calendar() -> None: cal_currently_rebuilding = True - found_events: set[CalendarInfo] = set() try: + found_events: set[CalendarInfo] = set() response: httpx.Response = await cshcal_client.get(CALENDAR_URL, timeout=10) response.raise_for_status() - report_timing("Fetched the calendar from google") cal: Calendar = Calendar.from_ical(response.content) - report_timing("Converted the calendar info") current_time: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) @@ -148,9 +139,6 @@ async def rebuild_calendar() -> None: ) for event in fetched_daily_events: - if len(found_events) >= CALENDAR_EVENT_MAXIMUM: - break - new_event: CalendarInfo = CalendarInfo( event.get("SUMMARY"), event.get("DTSTART").dt, @@ -162,7 +150,9 @@ async def rebuild_calendar() -> None: logger.warning(e) cal_last_update = current_time - calendar_cache = sorted(found_events, key=lambda x: x.date) + calendar_cache = sorted(found_events, key=lambda x: x.date)[ + :CALENDAR_EVENT_MAXIMUM + ] # Only cache the first elements of this list cal_currently_rebuilding = False @@ -175,7 +165,7 @@ async def wait_for_rebuild() -> tuple[CalendarInfo]: """ global cal_currently_rebuilding while cal_currently_rebuilding: - asyncio.sleep(1) + await asyncio.sleep(1) return calendar_cache @@ -198,12 +188,12 @@ async def get_future_events() -> tuple[CalendarInfo]: if cal_currently_rebuilding: return await wait_for_rebuild() + cur_time: date = datetime.now(ZoneInfo(CALENDAR_TIMEZONE)) cal_correct_length: bool = len(calendar_cache) == CALENDAR_EVENT_MAXIMUM if ( cal_last_update and cal_correct_length - and cal_last_update - > (datetime.now() - timedelta(minutes=CALENDAR_CACHE_REFRESH)) + and (cal_last_update > (cur_time - timedelta(minutes=CALENDAR_CACHE_REFRESH))) ): logger.info("Pulling from CSH calendar cache!") return calendar_cache @@ -211,6 +201,7 @@ async def get_future_events() -> tuple[CalendarInfo]: logger.info("Checking to rebuild CSH Calendar...") try: headers: dict[str, str | None] = {} + if header_none_match: headers["If-None-Match"] = header_none_match if header_last_modified: @@ -225,7 +216,7 @@ async def get_future_events() -> tuple[CalendarInfo]: response.status_code == 304 and cal_correct_length ): logger.info("CSH calendar not updated, refreshing last update!") - cal_last_update = datetime.now() + cal_last_update = cur_time return calendar_cache header_none_match = response.headers.get("ETag") diff --git a/src/main.py b/src/main.py index 8f39778..8fb77a1 100644 --- a/src/main.py +++ b/src/main.py @@ -19,7 +19,7 @@ from config import BASE_DIR from api import endpoints -from core.cshcalendar import close_cal_client +from core import cshcalendar logger: Logger = getLogger(__name__) @@ -29,7 +29,7 @@ async def lifespan(app: FastAPI): logger.info("Starting up the Jumpstart application!") yield logger.info("Shutting down the Jumpstart application!") - await close_cal_client() + await cshcalendar.close_cal_client() logger.info("Succesfully shut down the Jumpstart application!")