Move towards a more object-oriented API
This commit is contained in:
		@@ -10,7 +10,7 @@ import recurring_ical_events
 | 
			
		||||
import yaml
 | 
			
		||||
 | 
			
		||||
from asyncache import cached
 | 
			
		||||
from cachetools import TTLCache
 | 
			
		||||
from cachetools import TTLCache, LRUCache
 | 
			
		||||
from icalendar import Calendar
 | 
			
		||||
 | 
			
		||||
from fabcal.models import CalendarEvent
 | 
			
		||||
@@ -22,8 +22,20 @@ class ConfiguredCalendar(NamedTuple):
 | 
			
		||||
    default_color: str = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# this is not ideal, since it will read the file (at most) every 2 minutes, but there is no good way to cache the
 | 
			
		||||
# settings, e.g., by storing it in the FastAPI object
 | 
			
		||||
class CalendarClient:
 | 
			
		||||
    def __init__(self, url: str):
 | 
			
		||||
        self.url = url
 | 
			
		||||
 | 
			
		||||
    async def get(self, session):
 | 
			
		||||
        async with session.get(self.url) as response:
 | 
			
		||||
            response.raise_for_status()
 | 
			
		||||
            assert response.content_type.lower() == "text/calendar"
 | 
			
		||||
 | 
			
		||||
            return await response.text()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# this will be cached permanently, i.e., the server process needs to be restarted to apply config changes
 | 
			
		||||
@cached(LRUCache(100))
 | 
			
		||||
def read_calendars_from_config_file():
 | 
			
		||||
    with open("config.yml") as f:
 | 
			
		||||
        data = yaml.safe_load(f)
 | 
			
		||||
@@ -32,25 +44,21 @@ def read_calendars_from_config_file():
 | 
			
		||||
            yield ConfiguredCalendar(**calendar)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cached(TTLCache(maxsize=100, ttl=int(os.environ.get("FABCAL_CACHE_EXPIRE", 120))))
 | 
			
		||||
async def get_data() -> Dict[ConfiguredCalendar, str]:
 | 
			
		||||
    calendars = list(read_calendars_from_config_file())
 | 
			
		||||
 | 
			
		||||
class CombinedCalendarClient:
 | 
			
		||||
    def __init__(self, configured_calendars: List[ConfiguredCalendar]):
 | 
			
		||||
        # make sure it's a list since read_calendars_from_config_file() returns an iterator
 | 
			
		||||
        self.configured_calendars = list(configured_calendars)
 | 
			
		||||
 | 
			
		||||
    async def fetch_calendars(self) -> Dict[ConfiguredCalendar, str]:
 | 
			
		||||
        async with aiohttp.ClientSession() as session:
 | 
			
		||||
            calendar_clients = [CalendarClient(calendar.url) for calendar in self.configured_calendars]
 | 
			
		||||
            responses = await asyncio.gather(*[calendar.get(session) for calendar in calendar_clients])
 | 
			
		||||
 | 
			
		||||
        async def get(url: str) -> str:
 | 
			
		||||
            async with session.get(url) as response:
 | 
			
		||||
                response.raise_for_status()
 | 
			
		||||
                assert response.content_type.lower() == "text/calendar"
 | 
			
		||||
        return dict(zip(self.configured_calendars, responses))
 | 
			
		||||
 | 
			
		||||
                return await response.text()
 | 
			
		||||
 | 
			
		||||
        responses = await asyncio.gather(*[get(calendar.url) for calendar in calendars])
 | 
			
		||||
 | 
			
		||||
    return dict(zip(calendars, responses))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def combined_calendar(data: Dict[ConfiguredCalendar, str]) -> Calendar:
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def combine_calendars(data: Dict[ConfiguredCalendar, str]) -> Calendar:
 | 
			
		||||
        combined_calendar = Calendar()
 | 
			
		||||
 | 
			
		||||
        combined_calendar.add("prodid", "-//FabCal//NONSGML//EN")
 | 
			
		||||
@@ -83,12 +91,22 @@ def combined_calendar(data: Dict[ConfiguredCalendar, str]) -> Calendar:
 | 
			
		||||
 | 
			
		||||
        return combined_calendar
 | 
			
		||||
 | 
			
		||||
    async def fetch_and_combine_calendars(self) -> Calendar:
 | 
			
		||||
        return self.combine_calendars(await self.fetch_calendars())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@cached(TTLCache(maxsize=100, ttl=int(os.environ.get("FABCAL_CACHE_EXPIRE", 120))))
 | 
			
		||||
async def get_data() -> Calendar:
 | 
			
		||||
    client = CombinedCalendarClient(read_calendars_from_config_file())
 | 
			
		||||
    combined_calendar = await client.fetch_and_combine_calendars()
 | 
			
		||||
    return combined_calendar
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_tzinfo():
 | 
			
		||||
    return timezone(timedelta(hours=1))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_events_from_calendar_string(cal: Calendar) -> List[CalendarEvent]:
 | 
			
		||||
def get_events_from_calendar(cal: Calendar) -> List[CalendarEvent]:
 | 
			
		||||
    """
 | 
			
		||||
    Generate list of events from calendar vevents.
 | 
			
		||||
    Expands recurring events for +- one year.
 | 
			
		||||
@@ -147,9 +165,7 @@ def get_events_from_calendar_string(cal: Calendar) -> List[CalendarEvent]:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_future_events():
 | 
			
		||||
    cal = combined_calendar(await get_data())
 | 
			
		||||
 | 
			
		||||
    events = get_events_from_calendar_string(cal)
 | 
			
		||||
    events = get_events_from_calendar(await get_data())
 | 
			
		||||
 | 
			
		||||
    today = datetime.today().date()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
from fastapi import APIRouter
 | 
			
		||||
from starlette.responses import Response
 | 
			
		||||
 | 
			
		||||
from fabcal.calendar_client import combined_calendar, get_data
 | 
			
		||||
from fabcal.calendar_client import get_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
@@ -10,7 +10,7 @@ router = APIRouter()
 | 
			
		||||
@router.get("/events.ics")
 | 
			
		||||
async def events_ics():
 | 
			
		||||
    return Response(
 | 
			
		||||
        combined_calendar(await get_data()).to_ical(True),
 | 
			
		||||
        (await get_data()).to_ical(True),
 | 
			
		||||
        headers={
 | 
			
		||||
            "content-type": "text/calendar",
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user