annotate patchablegraph_handler.py @ 25:e11d407c46f8

rewrite for asyncio and starlette
author drewp@bigasterisk.com
date Sat, 23 Apr 2022 23:58:41 -0700
parents
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
25
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
1 import html
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
2 import logging
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
3 from typing import Callable, Tuple
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
4
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
5 from prometheus_client import Summary
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
6 from rdflib.namespace import NamespaceManager
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
7 from sse_starlette import ServerSentEvent
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
8 from sse_starlette.sse import EventSourceResponse
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
9 from starlette.requests import Request
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
10 from starlette.responses import Response
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
11
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
12 from patchablegraph import PatchableGraph
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
13
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
14 log = logging.getLogger('patchablegraph')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
15
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
16 SEND_SIMPLE_GRAPH = Summary('send_simple_graph', 'calls to _writeGraphResponse')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
17 SEND_FULL_GRAPH = Summary('send_full_graph', 'fullGraph SSE events')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
18 SEND_PATCH = Summary('send_patch', 'patch SSE events')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
19
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
20
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
21 def StaticGraph(masterGraph: PatchableGraph) -> Callable[[Request], Response]:
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
22 """
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
23 e.g.
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
24 Route('/graph/environment', StaticGraph(masterGraph)),
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
25 """
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
26
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
27 @SEND_SIMPLE_GRAPH.time()
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
28 def handle(request: Request) -> Response:
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
29 ctype, content = _writeGraphResponse(masterGraph, request.headers.get('accept', default=''))
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
30 r = Response(content=content)
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
31 r.media_type = ctype
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
32 return r
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
33
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
34 return handle
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
35
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
36
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
37 def _writeGraphResponse(masterGraph: PatchableGraph, accept: str) -> Tuple[str, bytes]:
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
38 if accept == 'application/nquads':
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
39 return 'application/nquads', masterGraph.serialize(format='nquads')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
40 elif accept == 'application/ld+json':
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
41 return 'application/ld+json', masterGraph.serialize(format='json-ld', indent=2)
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
42 else:
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
43 if accept.startswith('text/html'):
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
44 return _writeGraphForBrowser(masterGraph)
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
45 return 'application/x-trig', masterGraph.serialize(format='trig')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
46
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
47
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
48 def _writeGraphForBrowser(masterGraph: PatchableGraph) -> Tuple[str, bytes]:
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
49 # We think this is a browser, so respond with a live graph view
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
50 # (todo)
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
51 out = (b'''
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
52 <html><body><pre>''')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
53
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
54 ns = NamespaceManager(masterGraph._graph)
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
55 # maybe these could be on the PatchableGraph instance
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
56 ns.bind('ex', 'http://example.com/')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
57 ns.bind('', 'http://projects.bigasterisk.com/room/')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
58 ns.bind("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
59 ns.bind("xsd", "http://www.w3.org/2001/XMLSchema#")
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
60
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
61 for s, p, o, g in sorted(masterGraph._graph.quads()):
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
62 g = g.identifier
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
63 nquadLine = f'{s.n3(ns)} {p.n3(ns)} {o.n3(ns)} {g.n3(ns)} .\n'
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
64 out += html.escape(nquadLine).encode('utf8')
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
65
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
66 out += b'''
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
67 </pre>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
68 <p>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
69 <a href="#">[refresh]</a>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
70 <label><input type="checkbox"> Auto-refresh</label>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
71 </p>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
72 <script>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
73
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
74 if (new URL(window.location).searchParams.get('autorefresh') == 'on') {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
75 document.querySelector("input").checked = true;
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
76 setTimeout(() => {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
77 requestAnimationFrame(() => {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
78 window.location.replace(window.location.href);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
79 });
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
80 }, 2000);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
81 }
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
82
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
83 document.querySelector("a").addEventListener("click", (ev) => {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
84 ev.preventDefault();
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
85 window.location.replace(window.location.href);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
86
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
87 });
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
88 document.querySelector("input").addEventListener("change", (ev) => {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
89 if (document.querySelector("input").checked) {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
90 const u = new URL(window.location);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
91 u.searchParams.set('autorefresh', 'on');
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
92 window.location.replace(u.href);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
93 } else {
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
94 const u = new URL(window.location);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
95 u.searchParams.delete('autorefresh');
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
96 window.location.replace(u.href);
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
97 }
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
98 });
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
99
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
100 </script>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
101 </body></html>
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
102 '''
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
103 return 'text/html', out
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
104
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
105
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
106 def GraphEvents(masterGraph: PatchableGraph):
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
107 """
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
108 e.g.
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
109 Route('/graph/environment/events', GraphEvents(masterGraph)),
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
110 """
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
111
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
112 async def generateEvents():
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
113 events = masterGraph.subscribeToPatches()
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
114 while True: # we'll get cancelled by EventSourceResponse when the conn drops
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
115 etype, data = await events.get()
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
116 # Are there more to get? We might throttle and combine patches here- ideally we could see how
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
117 # long the latency to the client is to make a better rate choice
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
118 metric = {'graph': SEND_FULL_GRAPH, 'patch': SEND_PATCH}[etype]
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
119 with metric.time():
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
120 yield ServerSentEvent(event=etype, data=data)
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
121
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
122 async def handle(request: Request):
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
123 """
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
124 One session with one client.
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
125
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
126 returns current graph plus future patches to keep remote version
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
127 in sync with ours.
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
128
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
129 instead of turning off buffering all over, it may work for this
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
130 response to send 'x-accel-buffering: no', per
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
131 http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
132 """
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
133 return EventSourceResponse(generateEvents())
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
134
e11d407c46f8 rewrite for asyncio and starlette
drewp@bigasterisk.com
parents:
diff changeset
135 return handle