changeset 42:7d9609edcf9c

track calendar feed summary/description text and emit them in graphs
author drewp@bigasterisk.com
date Sun, 18 Feb 2024 12:34:53 -0800
parents ab54c9f65f76
children b5d3d9a8c83d
files gcalendarwatch.py graphconvert.py ingest.py localtypes.py
diffstat 4 files changed, 49 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- a/gcalendarwatch.py	Sun Feb 18 12:33:52 2024 -0800
+++ b/gcalendarwatch.py	Sun Feb 18 12:34:53 2024 -0800
@@ -22,6 +22,7 @@
 from typing import Any, Dict, Iterable, Optional, cast
 
 import background_loop
+import pymongo.collection
 from dateutil.tz import tzlocal
 from patchablegraph import PatchableGraph
 from patchablegraph.handler import GraphEvents, StaticGraph
@@ -29,7 +30,7 @@
 from rdflib import RDF, Graph, Namespace, URIRef
 from starlette.applications import Starlette
 from starlette.requests import Request
-from starlette.responses import (HTMLResponse, JSONResponse, PlainTextResponse, Response)
+from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse, Response
 from starlette.routing import Route
 from starlette_exporter import PrometheusMiddleware, handle_metrics
 
@@ -105,7 +106,7 @@
 
 
 def starred(graph: Graph, ev: URIRef) -> Optional[str]:
-    title = graph.value(ev, EV['title'])
+    title = str(graph.value(ev, EV['title']))
     m = re.search(r'(.*)\*\s*$', title)
     if m:
         return m.group(1)
@@ -116,7 +117,7 @@
 class ReadMongoEvents(object):
     """read events from mongodb"""
 
-    def __init__(self, collection):
+    def __init__(self, collection: pymongo.collection.Collection):
         self.collection = collection
 
     def getEvents(self, t1: datetime.datetime, t2: datetime.datetime) -> Iterable[Record]:
@@ -146,14 +147,14 @@
 
 
 # who uses this? pimscreen, frontdoor? they should use the GraphEvents version
-def EventsPage(read: ReadMongoEvents, req: Request) -> Response:
+def EventsPage(conf: Conf, read: ReadMongoEvents, req: Request) -> Response:
     t1, t2 = dayRange(int(req.query_params.get('days', default='2')))
     if req.query_params.get('t1', default=None):
         t1 = parse(req.query_params.get('t1', ''), tzinfo=tzlocal())
     if req.query_params.get('t2', default=None):
         t2 = parse(req.query_params.get('t2', ''), tzinfo=tzlocal())
     log.info(f'get /events local t1={t1} t2={t2}')
-    return PlainTextResponse(media_type="text/n3", content=asGraph(read.getEvents(t1, t2)).serialize(format='n3'))
+    return PlainTextResponse(media_type="text/n3", content=asGraph(conf, cals=[], events=read.getEvents(t1, t2)).serialize(format='n3'))
 
 
 def Countdowns(countdownGraph, req: Request) -> Response:
@@ -229,7 +230,7 @@
                         Route('/graph/calendar/countdown', StaticGraph(countdownGraph)),
                         Route('/graph/calendar/countdown/events', GraphEvents(countdownGraph)),
                         Route('/countdowns.json', functools.partial(Countdowns, countdownGraph)),
-                        Route('/events', functools.partial(EventsPage, read)),
+                        Route('/events', functools.partial(EventsPage, conf, read)),
                         Route('/pollNow', functools.partial(PollNow, loop), methods=['POST'])
                     ])
 
--- a/graphconvert.py	Sun Feb 18 12:33:52 2024 -0800
+++ b/graphconvert.py	Sun Feb 18 12:34:53 2024 -0800
@@ -1,15 +1,26 @@
-from rdflib import RDF, ConjunctiveGraph, Literal, Namespace, URIRef
+from localtypes import Conf, feedFromCalId
+from rdflib import RDF, RDFS, ConjunctiveGraph, Literal, Namespace, URIRef
 
 EV = Namespace("http://bigasterisk.com/event#")
 
 
-def asGraph(events, extraClasses=[], ctx=EV['gcalendar']):
+def asGraph(conf: Conf, cals, events, extraClasses=[], ctxUri:URIRef=EV['gcalendar']):
+    ctx = ConjunctiveGraph(identifier=ctxUri)
     graph = ConjunctiveGraph()
     graph.namespace_manager.bind('ev', EV)
+
+    for doc in cals:
+        feed = URIRef(feedFromCalId(conf, doc['_id']))
+        graph.add((feed, RDF.type, EV['CalendarFeed'], ctx))
+        if doc['summary']:
+            graph.add((feed, RDFS.label, Literal(doc['summary'].strip()), ctx))
+        if doc['description']:
+            graph.add((feed, EV['description'], Literal(doc['description'].strip()), ctx))
+
     for ev in events:
         uri = URIRef(ev['uri'])
 
-        def add(p, o):
+        def add(p: URIRef, o: URIRef|Literal):
             return graph.add((uri, p, o, ctx))
 
         add(RDF.type, EV['Event'])
--- a/ingest.py	Sun Feb 18 12:33:52 2024 -0800
+++ b/ingest.py	Sun Feb 18 12:34:53 2024 -0800
@@ -13,19 +13,17 @@
 from calendar_connection import getCalendarService
 from datetimemath import dayRange, limitDays, parse
 from graphconvert import asGraph
-from localtypes import Conf, Record
+from localtypes import Conf, Record, feedFromCalId
 
 log = logging.getLogger()
 EV = Namespace("http://bigasterisk.com/event#")
 
 
-def feedFromCalId(conf: Conf, calId: str) -> str:
-    return conf['event_uri_ns'] + 'feed/' + calId
 
 
-def getFirstPageOfCalendars(service: Resource):
+def getFirstPageOfCalendars(service: Resource) -> Iterable[tuple[str, str | None, str | None]]:
     for row in service.calendarList().list().execute()['items']:
-        yield row['id']
+        yield row['id'], row.get('summary'), row.get('description')
 
 
 def recordFromEv(conf: Conf, calId: str, ev: Dict) -> Record:
@@ -35,7 +33,7 @@
             return d['date']
         return d['dateTime']
 
-    rec = {
+    rec= {
         'uri': conf['event_uri_ns'] + ev['id'],
         'feed': feedFromCalId(conf, calId),
         'title': ev.get('summary', '?'),
@@ -68,14 +66,14 @@
 
 
 class SyncToMongo(object):
-    """reads gcal, writes to mongodb"""
+    """reads gcal, writes to mongodb and graphs"""
     collection: pymongo.collection.Collection
 
-    def __init__(self, conf: Conf, collection: pymongo.collection.Collection, agendaGraph: PatchableGraph,
-                 countdownGraph: PatchableGraph):
+    def __init__(self, conf: Conf, collection: pymongo.collection.Collection, agendaGraph: PatchableGraph, countdownGraph: PatchableGraph):
         self.conf = conf
         self.service = getCalendarService()
         self.collection = collection
+        self.calendarsCollection = collection.database.get_collection('gcalendar_cals')
         self.agendaGraph = agendaGraph
         self.countdownGraph = countdownGraph
 
@@ -85,7 +83,6 @@
 
         totalNew, currentRecords = self.gatherNewEventsInRange(cal, start, end, idsFormerlyInRange)
 
-
         self.updateGraphs(currentRecords)
         return totalNew
 
@@ -93,18 +90,22 @@
         totalNew = 0
         currentRecords = []
         try:
-            calIds = getFirstPageOfCalendars(self.service)
+            cals = getFirstPageOfCalendars(self.service)
         except HttpError:
             log.error('on getFirstPageOfCalendars')
             os.abort()
-        for calId in calIds:
+        for calId, summary, description in cals:
+            self.calendarsCollection.update_one({'_id': calId}, {'$set': {
+                'summary': summary,
+                'description': description,
+            }}, upsert=True)
             if cal and calId != cal:
                 continue
             try:
                 self.updateOneCal(start, end, idsFormerlyInRange, totalNew, currentRecords, calId)
             except HttpError:
                 log.error(f"on cal {calId}")
-        return totalNew,currentRecords
+        return totalNew, currentRecords
 
     def clearByStartTime(self, cal, start, end):
         spec: Dict[str, Any] = {"startTime": {"$gte": start, "$lte": end}}
@@ -118,13 +119,13 @@
     def updateOneCal(self, start, end, idsFormerlyInRange, totalNew, currentRecords, calId):
         print('read %s' % calId)
         events = self.service.events().list(
-                calendarId=calId,
-                singleEvents=True,
-                timeMin=start.isoformat(),
-                timeMax=end.isoformat(),
-                showDeleted=False,
-                maxResults=1000,
-            ).execute()
+            calendarId=calId,
+            singleEvents=True,
+            timeMin=start.isoformat(),
+            timeMax=end.isoformat(),
+            showDeleted=False,
+            maxResults=1000,
+        ).execute()
 
         for ev in events['items']:
             rec = recordFromEv(self.conf, calId, ev)
@@ -147,5 +148,6 @@
 
     def updateGraphs(self, currentRecords: Iterable[Record]):
         currentRecords = list(currentRecords)
-        self.agendaGraph.setToGraph(asGraph(limitDays(currentRecords, days=2)))
-        self.countdownGraph.setToGraph(asGraph(filterStarred(currentRecords, maxCount=15), extraClasses=[EV['CountdownEvent']]))
+        cals = list(self.calendarsCollection.find())
+        self.agendaGraph.setToGraph(asGraph(self.conf, cals, limitDays(currentRecords, days=2)))
+        self.countdownGraph.setToGraph(asGraph(self.conf, cals, filterStarred(currentRecords, maxCount=15), extraClasses=[EV['CountdownEvent']]))
--- a/localtypes.py	Sun Feb 18 12:33:52 2024 -0800
+++ b/localtypes.py	Sun Feb 18 12:34:53 2024 -0800
@@ -28,3 +28,7 @@
     endTimeUnspecified: bool
     htmlLink: str
     creatorEmail: str
+
+
+def feedFromCalId(conf: Conf, calId: str) -> str:
+    return conf['event_uri_ns'] + 'feed/' + calId