changeset 13:bfd95926be6e default tip

initial port to starlette. missing some disconnect & cleanup functionality
author drewp@bigasterisk.com
date Sat, 26 Nov 2022 14:13:51 -0800
parents 032e59be8fe9
children
files collector.py deploy.yaml merge.py patchsink.py patchsource.py pdm.lock pyproject.toml src/CollectorState.ts vite.config.ts
diffstat 9 files changed, 283 insertions(+), 373 deletions(-) [+]
line wrap: on
line diff
--- a/collector.py	Fri Nov 25 20:58:08 2022 -0800
+++ b/collector.py	Sat Nov 26 14:13:51 2022 -0800
@@ -7,48 +7,29 @@
 - filter out unneeded stmts from the sources
 - give a time resolution and concatenate any patches that come faster than that res
 """
+import asyncio
 import json
 import logging
 import time
 from typing import Dict, List, Optional, Set, Union
 
-import cyclone.sse
-import cyclone.web
-from docopt import docopt
 from patchablegraph.patchablegraph import jsonFromPatch
-from patchablegraph.patchsource import PatchSource, ReconnectingPatchSource
 from prometheus_client import Summary
-from prometheus_client.exposition import generate_latest
-from prometheus_client.registry import REGISTRY
 from rdfdb.patch import Patch
 from rdflib import Namespace, URIRef
-from standardservice.logsetup import enableTwistedLog, log
-from twisted.internet import defer, reactor
+
+from starlette.applications import Starlette
+from starlette.requests import Request
+from starlette.responses import JSONResponse
+from starlette.routing import Route
+from starlette_exporter import PrometheusMiddleware, handle_metrics
 
 from collector_config import config
 from merge import SourceUri, ActiveStatements, LocalStatements
-from patchsink import PatchSink
-
-import cyclone.sse
-def py3_sendEvent(self, message, event=None, eid=None, retry=None):
-
-    if isinstance(message, dict):
-        message = cyclone.sse.escape.json_encode(message)
-    if isinstance(message, str):
-        message = message.encode("utf-8")
-    assert isinstance(message, bytes)
-    if eid:
-        self.transport.write(b"id: %s\n" % eid)
-    if event:
-        self.transport.write(b"event: %s\n" % event)
-    if retry:
-        self.transport.write(b"retry: %s\n" % retry)
-    self.transport.write(b"data: %s\n\n" % message)
-
-
-cyclone.sse.SSEHandler.sendEvent = py3_sendEvent
-
-
+from patchsink import PatchSink, PatchSinkResponse
+from patchsource import PatchSource
+logging.basicConfig(level=logging.DEBUG)
+log=logging.getLogger()
 ROOM = Namespace("http://projects.bigasterisk.com/room/")
 COLLECTOR = SourceUri(URIRef('http://bigasterisk.com/sse_collector/'))
 
@@ -57,14 +38,6 @@
 SEND_UPDATE_PATCH_CALLS = Summary("send_update_patch_calls", 'calls')
 
 
-class Metrics(cyclone.web.RequestHandler):
-
-    def get(self):
-        self.add_header('content-type', 'text/plain')
-        self.write(generate_latest(REGISTRY))
-
-
-
 class GraphClients(object):
     """
     All the active PatchSources and SSEHandlers
@@ -76,20 +49,20 @@
     """
 
     def __init__(self):
-        self.clients: Dict[SourceUri, Union[PatchSource, ReconnectingPatchSource]] = {}  # (COLLECTOR is not listed)
-        self.handlers: Set[PatchSink] = set()
+        self.clients: Dict[SourceUri, PatchSource] = {}  # (COLLECTOR is not listed)
+        self.handlers: Set[PatchSinkResponse] = set()
         self.statements: ActiveStatements = ActiveStatements()
 
         self._localStatements = LocalStatements(self._onPatch)
 
     def state(self) -> Dict:
         return {
-            'clients': sorted([ps.state() for ps in self.clients.values()], key=lambda r: r['reconnectedPatchSource']['url']),
+            'clients': sorted([ps.state() for ps in self.clients.values()], key=lambda r: r['url']),
             'sseHandlers': sorted([h.state() for h in self.handlers], key=lambda r: (r['streamId'], r['created'])),
             'statements': self.statements.state(),
         }
 
-    def _sourcesForHandler(self, handler: PatchSink) -> List[SourceUri]:
+    def _sourcesForHandler(self, handler: PatchSinkResponse) -> List[SourceUri]:
         streamId = handler.streamId
         matches = [s for s in config['streams'] if s['id'] == streamId]
         if len(matches) != 1:
@@ -107,14 +80,14 @@
 
         self._sendUpdatePatch()
 
-        if log.isEnabledFor(logging.DEBUG):
+        if 0 and log.isEnabledFor(logging.DEBUG):
             self.statements.pprintTable()
 
         if source != COLLECTOR:
             self._localStatements.setSourceState(source, ROOM['fullGraphReceived'] if fullGraph else ROOM['patchesReceived'])
 
     @SEND_UPDATE_PATCH_CALLS.time()
-    def _sendUpdatePatch(self, handler: Optional[PatchSink] = None):
+    def _sendUpdatePatch(self, handler: Optional[PatchSinkResponse] = None):
         """
         send a patch event out this handler to bring it up to date with
         self.statements
@@ -129,7 +102,7 @@
         # reduce loops here- prepare all patches at once
         for h in selected:
             period = .9
-            if 'Raspbian' in h.request.headers.get('user-agent', ''):
+            if 'Raspbian' in h.user_agent:
                 period = 5
             if h.lastPatchSentTime > now - period:
                 continue
@@ -142,12 +115,12 @@
                 # it up into multiple sends, although there's no
                 # guarantee at all since any single stmt could be any
                 # length.
-                h.sendEvent(message=jsonFromPatch(p), event=b'patch')
+                h.sendEvent(message=jsonFromPatch(p), event='patch')
                 h.lastPatchSentTime = now
             else:
                 log.debug('nothing to send to %s', h)
 
-    def addSseHandler(self, handler: PatchSink):
+    def addSseHandler(self, handler: PatchSinkResponse):
         log.info('addSseHandler %r %r', handler, handler.streamId)
 
         # fail early if id doesn't match
@@ -159,14 +132,14 @@
             if source not in self.clients and source != COLLECTOR:
                 log.debug('connect to patch source %s', source)
                 self._localStatements.setSourceState(source, ROOM['connect'])
-                self.clients[source] = ReconnectingPatchSource(source,
-                                                               listener=lambda p, fullGraph, source=source: self._onPatch(source, p, fullGraph),
-                                                               reconnectSecs=10)
+                self.clients[source] = PatchSource(source,
+                                                   listener=lambda p, fullGraph, source=source: self._onPatch(source, p, fullGraph),
+                                                   reconnectSecs=10)
         log.debug('bring new client up to date')
 
         self._sendUpdatePatch(handler)
 
-    def removeSseHandler(self, handler: PatchSink):
+    def removeSseHandler(self, handler: PatchSinkResponse):
         log.info('removeSseHandler %r', handler)
         self.statements.discardHandler(handler)
         for source in self._sourcesForHandler(handler):
@@ -204,50 +177,32 @@
                     garbage.add(stmt)
 
 
-class State(cyclone.web.RequestHandler):
-
-    @GET_STATE_CALLS.time()
-    def get(self) -> None:
-        try:
-            state = self.settings.graphClients.state()
-            msg = json.dumps({'graphClients': state}, indent=2, default=lambda obj: '<unserializable>')
-            log.info(msg)
-            self.write(msg)
-        except Exception:
-            import traceback
-            traceback.print_exc()
-            raise
-
-
-class GraphList(cyclone.web.RequestHandler):
-
-    def get(self) -> None:
-        self.write(json.dumps(config['streams']))
+@GET_STATE_CALLS.time()
+def State(request: Request) -> JSONResponse:
+    state = request.app.state.graphClients.state()
+    msg = json.dumps({'graphClients': state}, indent=2, default=lambda obj: '<unserializable>')
+    log.info(msg)
+    return JSONResponse({'graphClients': state})
 
 
-if __name__ == '__main__':
-    arg = docopt("""
-    Usage: sse_collector.py [options]
+def GraphList(request: Request) -> JSONResponse:
+    return JSONResponse(config['streams'])
 
-    -v   Verbose
-    -i  Info level only
-    """)
-
-    if True:
-        enableTwistedLog()
-        log.setLevel(logging.DEBUG if arg['-v'] else logging.INFO)
-        defer.setDebugging(True)
-
+def main():
     graphClients = GraphClients()
 
-    reactor.listenTCP(
-        9072,
-        cyclone.web.Application(  #
-            handlers=[
-                (r'/state', State),
-                (r'/graph/', GraphList),
-                (r'/graph/(.+)', PatchSink),
-                (r'/metrics', Metrics),
-            ], graphClients=graphClients),
-        interface='::')
-    reactor.run()
+    app = Starlette(
+        debug=True,
+        routes=[
+            Route('/state', State),
+            Route('/graph/', GraphList),
+            Route('/graph/{stream_id:str}', PatchSink),
+        ])
+    app.state.graphClients = graphClients
+
+    app.add_middleware(PrometheusMiddleware, app_name='collector')
+    app.add_route("/metrics", handle_metrics)
+    return app
+
+
+app = main()
\ No newline at end of file
--- a/deploy.yaml	Fri Nov 25 20:58:08 2022 -0800
+++ b/deploy.yaml	Sat Nov 26 14:13:51 2022 -0800
@@ -15,10 +15,10 @@
       proxy_buffering off;
       server {
         access_log /dev/stderr main;
-        location = /collector/state      { proxy_pass http://127.0.0.1:9072/state; }
-        location   /collector/graph/     { proxy_pass http://127.0.0.1:9072/graph/; }
-        location = /collector/metrics    { proxy_pass http://127.0.0.1:9072/metrics; }
-        location = /metrics              { proxy_pass http://127.0.0.1:9072/metrics; }
+        location = /collector/state      { proxy_pass http://127.0.0.1:8001/state; }
+        location   /collector/graph/     { proxy_pass http://127.0.0.1:8001/graph/; }
+        location = /collector/metrics    { proxy_pass http://127.0.0.1:8001/metrics; }
+        location = /metrics              { proxy_pass http://127.0.0.1:8001/metrics; }
 
         location = /collector/vite-ws { 
           proxy_pass http://127.0.0.1:8002/collector/vite-ws;
@@ -61,11 +61,13 @@
           command:
             - pdm
             - run
-            - python
-            - collector.py
-            - "-v"
+            - uvicorn
+            - --port=8001   
+            - --host=0.0.0.0
+            - collector:app
+            - --reload
           ports:
-            - containerPort: 9072
+            - containerPort: 8001
 
         - name: view
           image: bang5:5000/collector_image
@@ -73,8 +75,7 @@
             - pnpx
             - vite
             - --mode=dev
-            - --logLevel=info
-            - --debug
+            # - --debug
           ports:
             - containerPort: 8002
 ---
--- a/merge.py	Fri Nov 25 20:58:08 2022 -0800
+++ b/merge.py	Sat Nov 26 14:13:51 2022 -0800
@@ -1,3 +1,4 @@
+import logging
 import collections
 from typing import Callable, Dict, List, Optional, Sequence, Set, Tuple, Union, NewType
 from prometheus_client import Summary
@@ -5,16 +6,19 @@
 from rdfdb.patch import Patch
 from rdflib import Namespace, URIRef
 from rdflib.term import Node
-from standardservice.logsetup import enableTwistedLog, log
-from patchsink import PatchSink
+log = logging.getLogger()
 LOCAL_STATEMENTS_PATCH_CALLS = Summary("local_statements_patch_calls", 'calls')
 MAKE_SYNC_PATCH_CALLS = Summary("make_sync_patch_calls", 'calls')
 REPLACE_SOURCE_STATEMENTS_CALLS = Summary("replace_source_statements_calls", 'calls')
 
+# we deal with PatchSourceResponse in here, but only as objs to __contains__ and compare.
+from patchsink import PatchSinkResponse
+OutputHandler = Union[PatchSinkResponse, type(None)] #  A patchsink.PatchSinkResponse, but make type error if we try to look in it
+
 SourceUri = NewType('SourceUri', URIRef)
 
 Statement = Tuple[Node, Node, Node, Node]
-StatementTable = Dict[Statement, Tuple[Set[SourceUri], Set[PatchSink]]]
+StatementTable = Dict[Statement, Tuple[Set[SourceUri], Set[OutputHandler]]]
 
 ROOM = Namespace("http://projects.bigasterisk.com/room/")
 COLLECTOR = SourceUri(URIRef('http://bigasterisk.com/sse_collector/'))
@@ -111,7 +115,7 @@
             print("%03d. %-80s from %s to %s" % (i, abbrevStmt(stmt), [abbrevTerm(s) for s in sources], handlers))
 
     @MAKE_SYNC_PATCH_CALLS.time()
-    def makeSyncPatch(self, handler: PatchSink, sources: Set[SourceUri]):
+    def makeSyncPatch(self, handler: OutputHandler, sources: Set[SourceUri]):
         # todo: this could run all handlers at once, which is how we
         # use it anyway
         adds = []
@@ -172,7 +176,7 @@
 
         self.applySourcePatch(source, Patch(addQuads=newStmts, delQuads=[]))
 
-    def discardHandler(self, handler: PatchSink):
+    def discardHandler(self, handler: OutputHandler):
         with self.postDeleteStatements() as garbage:
             for stmt, (sources, handlers) in self.table.items():
                 handlers.discard(handler)
--- a/patchsink.py	Fri Nov 25 20:58:08 2022 -0800
+++ b/patchsink.py	Sat Nov 26 14:13:51 2022 -0800
@@ -1,43 +1,82 @@
+"""sends patches out an SSE response to a collector client who wants a collection of graphs"""
 import time
-from typing import Dict
+from typing import Dict, Any, Optional
+import asyncio
+from starlette.requests import Request
+from sse_starlette.sse import EventSourceResponse
+import logging
+import queue
+log = logging.getLogger('writer')
 
-import cyclone.sse
-import cyclone.web
+async def iq(q):
+    while True:
+        elem = await q.get()
+        yield elem
 
-class PatchSink(cyclone.sse.SSEHandler):
-    _handlerSerial = 0
+class PatchSinkResponse(EventSourceResponse):
 
-    def __init__(self, application: cyclone.web.Application, request):
-        cyclone.sse.SSEHandler.__init__(self, application, request)
-        self.bound = False
+    def __init__(
+        self,
+        
+        status_code: int = 200,
+        headers: Optional[Dict] = None,
+        ping: Optional[int] = None,
+        user_agent="",
+        stream_id: Optional[str] = None,
+    ) -> None:
+        self.q = asyncio.Queue()
+        EventSourceResponse.__init__(self, iq(self.q), status_code, headers, ping=ping)
         self.created = time.time()
-        self.graphClients = self.settings.graphClients
-
-        self._serial = PatchSink._handlerSerial
-        PatchSink._handlerSerial += 1
-        self.lastPatchSentTime: float = 0.0
-
-    def __repr__(self) -> str:
-        return '<Handler #%s>' % self._serial
+        self.user_agent = user_agent
+        self.lastPatchSentTime = 0.0
+        self.streamId = stream_id
+        
+    def sendEvent(self, message: str, event: str):
+        self.q.put_nowait(dict(data=message, event=event))
 
     def state(self) -> Dict:
         return {
             'created': round(self.created, 2),
             'ageHours': round((time.time() - self.created) / 3600, 2),
             'streamId': self.streamId,
-            'remoteIp': self.request.remote_ip,  # wrong, need some forwarded-for thing
-            'foafAgent': self.request.headers.get('X-Foaf-Agent'),
-            'userAgent': self.request.headers.get('user-agent'),
+            # 'remoteIp': self.request.remote_ip,  # wrong, need some forwarded-for thing
+            # 'foafAgent': self.request.headers.get('X-Foaf-Agent'),
+            # 'userAgent': self.request.headers.get('user-agent'),
         }
 
-    def bind(self, *args, **kwargs):
-        self.streamId = args[0]
+
+async def PatchSink(request: Request) -> PatchSinkResponse:
+    log.debug(f"PatchSink for {request.path_params['stream_id']=}")
+
+    ret= PatchSinkResponse( ping=30, user_agent=request.headers['user-agent'], stream_id=request.path_params['stream_id'])
+    request.app.state.graphClients.addSseHandler(ret)
+    return ret
+
+
+# class PatchSink_old:
+#     _handlerSerial = 0
+
+#     def __init__(self, application: cyclone.web.Application, request):
+#         cyclone.sse.SSEHandler.__init__(self, application, request)
+#         self.bound = False
+#         self.created = time.time()
+#         self.graphClients = self.settings.graphClients
 
-        self.graphClients.addSseHandler(self)
-        # If something goes wrong with addSseHandler, I don't want to
-        # try removeSseHandler.
-        self.bound = True
+#         self._serial = PatchSink._handlerSerial
+#         PatchSink._handlerSerial += 1
+#         self.lastPatchSentTime: float = 0.0
+
+#     def __repr__(self) -> str:
+#         return '<Handler #%s>' % self._serial
 
-    def unbind(self) -> None:
-        if self.bound:
-            self.graphClients.removeSseHandler(self)
+#     def bind(self, *args, **kwargs):
+#         self.streamId = args[0]
+
+#         self.graphClients.addSseHandler(self)
+#         # If something goes wrong with addSseHandler, I don't want to
+#         # try removeSseHandler.
+#         self.bound = True
+
+#     def unbind(self) -> None:
+#         if self.bound:
+#             self.graphClients.removeSseHandler(self)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/patchsource.py	Sat Nov 26 14:13:51 2022 -0800
@@ -0,0 +1,82 @@
+"""consumes another server's SSE stream of graph patches"""
+import time
+import asyncio
+from aiohttp_sse_client import client as sse_client
+from typing import Protocol, Dict, cast
+from rdflib import ConjunctiveGraph
+from rdflib.parser import StringInputSource
+from rdfdb.patch import Patch
+import logging
+
+log = logging.getLogger('reader')
+from patchablegraph.patchablegraph import patchFromJson, JsonSerializedPatch
+
+
+class _Listener(Protocol):
+
+    def __call__(
+            self,
+            p: Patch,
+            fullGraph: bool,  # True if the patch is the initial full graph.
+    ) -> None:
+        ...
+
+
+class PatchSource:
+
+    def __init__(self, url: str, listener: _Listener, reconnectSecs=60, agent='unset'):
+
+        self.url = url
+
+        self._listener = listener
+        self.reconnectSecs = reconnectSecs
+        self.agent = agent
+
+        self._reconnnect()
+
+    def _reconnnect(self):
+        self._fullGraphReceived = None
+        self._patchesReceived = 0
+        log.debug(f'sse_client.EventSource {self.url=}')
+        self._eventSource = sse_client.EventSource(self.url, on_error=self._onError)
+        self.task = asyncio.create_task(self._spin())
+
+    async def _spin(self):
+        log.info(f'_spin {self.url}')
+        async with self._eventSource as es:
+            async for ev in es:
+                log.info(f'spin got ev {str(ev)[:100]}')
+                if ev.type == 'patch':
+                    p = patchFromJson(cast(JsonSerializedPatch, ev.data))
+                    self._listener(p, fullGraph=False)
+                    self._latestPatchTime = time.time()
+                    self._patchesReceived += 1
+                elif ev.type == 'fullGraph':
+
+                    g = ConjunctiveGraph()
+                    g.parse(StringInputSource(ev.data), format='json-ld')
+                    p = Patch(addGraph=g)
+                    self._listener(p, fullGraph=True)
+
+                    self._fullGraphReceived = True
+                    self._fullGraphTime = time.time()
+                    self._patchesReceived += 1
+
+    def _onError(self, *args):
+        raise ValueError(repr(args))
+
+    def state(self) -> Dict:
+        return {
+            'url': self.url,
+            'fullGraphReceived': self._fullGraphReceived,
+            'patchesReceived': self._patchesReceived,
+            'time': {
+                'open': getattr(self, '_startReadTime', None),
+                'fullGraph': getattr(self, '_fullGraphTime', None),
+                'latestPatch': getattr(self, '_latestPatchTime', None),
+            },
+            'closed': self._eventSource is None,
+        }
+
+    def stop(self):
+        print('stop ps pls')
--- a/pdm.lock	Fri Nov 25 20:58:08 2022 -0800
+++ b/pdm.lock	Sat Nov 26 14:13:51 2022 -0800
@@ -14,6 +14,17 @@
 ]
 
 [[package]]
+name = "aiohttp-sse-client"
+version = "0.2.1"
+summary = "A Server-Sent Event python client base on aiohttp"
+dependencies = [
+    "aiohttp>=3",
+    "attrs",
+    "multidict",
+    "yarl",
+]
+
+[[package]]
 name = "aiosignal"
 version = "1.3.1"
 requires_python = ">=3.7"
@@ -62,15 +73,6 @@
 ]
 
 [[package]]
-name = "automat"
-version = "22.10.0"
-summary = "Self-service finite-state machines for the programmer on the go."
-dependencies = [
-    "attrs>=19.2.0",
-    "six",
-]
-
-[[package]]
 name = "cffi"
 version = "1.15.1"
 summary = "Foreign Function Interface for Python calling C code."
@@ -85,9 +87,19 @@
 summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 
 [[package]]
-name = "constantly"
-version = "15.1.0"
-summary = "Symbolic constants in Python"
+name = "click"
+version = "8.1.3"
+requires_python = ">=3.7"
+summary = "Composable command line interface toolkit"
+dependencies = [
+    "colorama; platform_system == \"Windows\"",
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+summary = "Cross-platform colored terminal text."
 
 [[package]]
 name = "cryptography"
@@ -99,26 +111,18 @@
 ]
 
 [[package]]
-name = "cyclone"
-version = "1.3"
-summary = "Non-blocking web server for Python. Tornado API as a Twisted protocol."
-dependencies = [
-    "pyOpenSSL==19.0.0",
-    "twisted==19.2.1",
-]
-
-[[package]]
-name = "docopt"
-version = "0.6.2"
-summary = "Pythonic argument parser, that will make you smile"
-
-[[package]]
 name = "frozenlist"
 version = "1.3.3"
 requires_python = ">=3.7"
 summary = "A list-like structure which implements collections.abc.MutableSequence"
 
 [[package]]
+name = "h11"
+version = "0.14.0"
+requires_python = ">=3.7"
+summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+
+[[package]]
 name = "hyperlink"
 version = "21.0.0"
 requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
@@ -134,11 +138,6 @@
 summary = "Internationalized Domain Names in Applications (IDNA)"
 
 [[package]]
-name = "incremental"
-version = "22.10.0"
-summary = "\"A small library that versions your Python projects.\""
-
-[[package]]
 name = "isodate"
 version = "0.6.1"
 summary = "An ISO 8601 date/time/duration parser and formatter"
@@ -172,46 +171,12 @@
 summary = "Python client for the Prometheus monitoring system."
 
 [[package]]
-name = "psutil"
-version = "5.9.4"
-requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-summary = "Cross-platform lib for process and system monitoring in Python."
-
-[[package]]
-name = "pyasn1"
-version = "0.4.8"
-summary = "ASN.1 types and codecs"
-
-[[package]]
-name = "pyasn1-modules"
-version = "0.2.8"
-summary = "A collection of ASN.1-based protocols modules."
-dependencies = [
-    "pyasn1<0.5.0,>=0.4.6",
-]
-
-[[package]]
 name = "pycparser"
 version = "2.21"
 requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 summary = "C parser in Python"
 
 [[package]]
-name = "pyhamcrest"
-version = "2.0.4"
-requires_python = ">=3.6"
-summary = "Hamcrest framework for matcher objects"
-
-[[package]]
-name = "pyopenssl"
-version = "22.1.0"
-requires_python = ">=3.6"
-summary = "Python wrapper module around the OpenSSL library"
-dependencies = [
-    "cryptography<39,>=38.0.0",
-]
-
-[[package]]
 name = "pyparsing"
 version = "3.0.9"
 requires_python = ">=3.6.8"
@@ -251,26 +216,6 @@
 ]
 
 [[package]]
-name = "scales"
-version = "1.0.9"
-summary = "Stats for Python processes"
-dependencies = [
-    "six",
-]
-
-[[package]]
-name = "service-identity"
-version = "21.1.0"
-summary = "Service identity verification for pyOpenSSL & cryptography."
-dependencies = [
-    "attrs>=19.1.0",
-    "cryptography",
-    "pyasn1",
-    "pyasn1-modules",
-    "six",
-]
-
-[[package]]
 name = "setuptools"
 version = "65.6.3"
 requires_python = ">=3.7"
@@ -298,16 +243,6 @@
 ]
 
 [[package]]
-name = "standardservice"
-version = "0.6.0"
-summary = ""
-dependencies = [
-    "psutil",
-    "scales",
-    "twisted",
-]
-
-[[package]]
 name = "starlette"
 version = "0.22.0"
 requires_python = ">=3.7"
@@ -326,31 +261,22 @@
 ]
 
 [[package]]
-name = "twisted"
-version = "19.2.1"
-summary = "An asynchronous networking framework written in Python"
-dependencies = [
-    "Automat>=0.3.0",
-    "PyHamcrest>=1.9.0",
-    "attrs>=17.4.0",
-    "constantly>=15.1",
-    "hyperlink>=17.1.1",
-    "incremental>=16.10.1",
-    "zope.interface>=4.4.2",
-]
-
-[[package]]
-name = "twisted-sse"
-version = "0.3.0"
-summary = ""
-
-[[package]]
 name = "txaio"
 version = "22.2.1"
 requires_python = ">=3.6"
 summary = "Compatibility API between asyncio/Twisted/Trollius"
 
 [[package]]
+name = "uvicorn"
+version = "0.20.0"
+requires_python = ">=3.7"
+summary = "The lightning-fast ASGI server."
+dependencies = [
+    "click>=7.0",
+    "h11>=0.8",
+]
+
+[[package]]
 name = "yarl"
 version = "1.8.1"
 requires_python = ">=3.7"
@@ -360,18 +286,9 @@
     "multidict>=4.0",
 ]
 
-[[package]]
-name = "zope.interface"
-version = "5.5.2"
-requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-summary = "Interfaces for Python"
-dependencies = [
-    "setuptools",
-]
-
 [metadata]
 lock_version = "4.0"
-content_hash = "sha256:7071c9c49f9bcacd4c21a3d47b9779515fb2338cc1a392f73d5402c18131badc"
+content_hash = "sha256:8067acc73a57fb7b85629040185ecbba1e4ebd28c00c13effd2a0d13f4849da1"
 
 [metadata.files]
 "aiohttp 3.8.3" = [
@@ -463,6 +380,10 @@
     {url = "https://files.pythonhosted.org/packages/fe/e1/401b3fac5bfaa8cef6d7fd541c8a573907565c662f2d5891cc9a0c0124b7/aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"},
     {url = "https://files.pythonhosted.org/packages/ff/4f/62d9859b7d4e6dc32feda67815c5f5ab4421e6909e48cbc970b6a40d60b7/aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"},
 ]
+"aiohttp-sse-client 0.2.1" = [
+    {url = "https://files.pythonhosted.org/packages/25/c9/ad514e70a549db22e118f0366c0bb38c5a90cb407978cb6c3d1482760889/aiohttp_sse_client-0.2.1-py2.py3-none-any.whl", hash = "sha256:42c81ee9213e9fc8bc412b063bac3a813e02e75250c4c8049222234d41c9b024"},
+    {url = "https://files.pythonhosted.org/packages/71/c3/4825c5f37909a70c8018924b3d521847dd7acf1fce7e1054574bafed2271/aiohttp-sse-client-0.2.1.tar.gz", hash = "sha256:5004e29271624af586158dc7166cb0687a7a5997aab5b808f4b53400e1b72e3b"},
+]
 "aiosignal 1.3.1" = [
     {url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
     {url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
@@ -486,10 +407,6 @@
 "autobahn 22.7.1" = [
     {url = "https://files.pythonhosted.org/packages/c5/b5/c92d6640fd55cbbdd97c05800ab534d84197f7b485d89a9df981ab67cce3/autobahn-22.7.1.tar.gz", hash = "sha256:8b462ea2e6aad6b4dc0ed45fb800b6cbfeb0325e7fe6983907f122f2be4a1fe9"},
 ]
-"automat 22.10.0" = [
-    {url = "https://files.pythonhosted.org/packages/29/90/64aabce6c1b820395452cc5472b8f11cd98320f40941795b8069aef4e0e0/Automat-22.10.0-py2.py3-none-any.whl", hash = "sha256:c3164f8742b9dc440f3682482d32aaff7bb53f71740dd018533f9de286b64180"},
-    {url = "https://files.pythonhosted.org/packages/7a/7b/9c3d26d8a0416eefbc0428f168241b32657ca260fb7ef507596ff5c2f6c4/Automat-22.10.0.tar.gz", hash = "sha256:e56beb84edad19dcc11d30e8d9b895f75deeb5ef5e96b84a467066b3b84bb04e"},
-]
 "cffi 1.15.1" = [
     {url = "https://files.pythonhosted.org/packages/00/05/23a265a3db411b0bfb721bf7a116c7cecaf3eb37ebd48a6ea4dfb0a3244d/cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
     {url = "https://files.pythonhosted.org/packages/03/7b/259d6e01a6083acef9d3c8c88990c97d313632bb28fa84d6ab2bb201140a/cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
@@ -560,9 +477,13 @@
     {url = "https://files.pythonhosted.org/packages/a1/34/44964211e5410b051e4b8d2869c470ae8a68ae274953b1c7de6d98bbcf94/charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
     {url = "https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
 ]
-"constantly 15.1.0" = [
-    {url = "https://files.pythonhosted.org/packages/95/f1/207a0a478c4bb34b1b49d5915e2db574cadc415c9ac3a7ef17e29b2e8951/constantly-15.1.0.tar.gz", hash = "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35"},
-    {url = "https://files.pythonhosted.org/packages/b9/65/48c1909d0c0aeae6c10213340ce682db01b48ea900a7d9fce7a7910ff318/constantly-15.1.0-py2.py3-none-any.whl", hash = "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d"},
+"click 8.1.3" = [
+    {url = "https://files.pythonhosted.org/packages/59/87/84326af34517fca8c58418d148f2403df25303e02736832403587318e9e8/click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+    {url = "https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+]
+"colorama 0.4.6" = [
+    {url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+    {url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
 ]
 "cryptography 38.0.3" = [
     {url = "https://files.pythonhosted.org/packages/13/dd/a9608b7aebe5d2dc0c98a4b2090a6b815628efa46cc1c046b89d8cd25f4c/cryptography-38.0.3.tar.gz", hash = "sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd"},
@@ -592,13 +513,6 @@
     {url = "https://files.pythonhosted.org/packages/ee/a7/dfa6e33efa9f7448554560d4e7debf5cefbbdc9bc677c13824a0bc777d78/cryptography-38.0.3-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36"},
     {url = "https://files.pythonhosted.org/packages/fe/44/e5f4e5040491130f58d3ffbc3d21e917cced3e13faa126530109c18dee2e/cryptography-38.0.3-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320"},
 ]
-"cyclone 1.3" = [
-    {url = "https://files.pythonhosted.org/packages/0a/2d/d3a939924f41c36c91996365f3f9bb8a3da2449a76cfe97a2183611d04b1/cyclone-1.3.tar.gz", hash = "sha256:6e5ec576c719240180d56e6e8f83bb474ab52ca3e56ed6417a90f60fd0bf562b"},
-    {url = "https://files.pythonhosted.org/packages/8e/78/fd66d26885eca6c1f0b40c1e5386563d5a7bfdb41d2aa6158707dd926b7f/cyclone-1.3-py3-none-any.whl", hash = "sha256:adc1c8736bafc668f1f55236d1919cfb96c1f377831361deb73d633f0c1db779"},
-]
-"docopt 0.6.2" = [
-    {url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
-]
 "frozenlist 1.3.3" = [
     {url = "https://files.pythonhosted.org/packages/01/a3/a3c18bfd7bd2a56831b985140f98eb6dda684a2d8b2a54b1077b45c7f9d9/frozenlist-1.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8"},
     {url = "https://files.pythonhosted.org/packages/03/00/febbfd2ec244a0f91707bd879afe6aa278e337dc41cd9d0d25260e6da38e/frozenlist-1.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef"},
@@ -675,6 +589,10 @@
     {url = "https://files.pythonhosted.org/packages/fa/a1/d0822eb2f827f209c8fcf19ff1c9eb30ae08e25b710cf432b1013ea23429/frozenlist-1.3.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91"},
     {url = "https://files.pythonhosted.org/packages/fd/b8/9ed4ed37b2c3269660a86a10a09b2fe49dbbd6973ac684804ece7b51b63f/frozenlist-1.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816"},
 ]
+"h11 0.14.0" = [
+    {url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+    {url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
 "hyperlink 21.0.0" = [
     {url = "https://files.pythonhosted.org/packages/3a/51/1947bd81d75af87e3bb9e34593a4cf118115a8feb451ce7a69044ef1412e/hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"},
     {url = "https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"},
@@ -683,10 +601,6 @@
     {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
     {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
 ]
-"incremental 22.10.0" = [
-    {url = "https://files.pythonhosted.org/packages/77/51/8073577012492fcd15628e811db585f447c500fa407e944ab3a18ec55fb7/incremental-22.10.0-py2.py3-none-any.whl", hash = "sha256:b864a1f30885ee72c5ac2835a761b8fe8aa9c28b9395cacf27286602688d3e51"},
-    {url = "https://files.pythonhosted.org/packages/86/42/9e87f04fa2cd40e3016f27a4b4572290e95899c6dce317e2cdb580f3ff09/incremental-22.10.0.tar.gz", hash = "sha256:912feeb5e0f7e0188e6f42241d2f450002e11bbc0937c65865045854c24c0bd0"},
-]
 "isodate 0.6.1" = [
     {url = "https://files.pythonhosted.org/packages/b6/85/7882d311924cbcfc70b1890780763e36ff0b140c7e51c110fc59a532f087/isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"},
     {url = "https://files.pythonhosted.org/packages/db/7a/c0a56c7d56c7fa723988f122fa1f1ccf8c5c4ccc48efad0d214b49e5b1af/isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"},
@@ -759,42 +673,10 @@
     {url = "https://files.pythonhosted.org/packages/2e/5e/4225463cdac1098aac718b1d8adf8f9dc3d6aaea55f4f85a2f7d572b4f7c/prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"},
     {url = "https://files.pythonhosted.org/packages/ae/ae/04b27ea04f67f91f10b1379d8fd6729c41ac0bcefbb0af603850b3fa32e0/prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"},
 ]
-"psutil 5.9.4" = [
-    {url = "https://files.pythonhosted.org/packages/1d/80/e1502ba4ff65390bd17b4612010762075f64f5a0e7c28e889c4820bd95a9/psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"},
-    {url = "https://files.pythonhosted.org/packages/25/6e/ba97809175c90cbdcd33b470e466ebf0854d15d1506e605cc0ddd284d5b6/psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"},
-    {url = "https://files.pythonhosted.org/packages/3d/7d/d05864a69e452f003c0d77e728e155a89a2a26b09e64860ddd70ad64fb26/psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"},
-    {url = "https://files.pythonhosted.org/packages/3e/af/fe14b984e8b0f778d502d387b789d846cb2fcc3989f63be942741266d8c8/psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"},
-    {url = "https://files.pythonhosted.org/packages/53/ae/536719016fe9399187dbf52cdc65aef942f82b75924495918a2f701bcb77/psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"},
-    {url = "https://files.pythonhosted.org/packages/5a/37/ef88eed265d93bc28c681316f68762c5e04167519e5627a0187c8878b409/psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"},
-    {url = "https://files.pythonhosted.org/packages/60/f8/b92fecd5297edcecda825a04dfde7cb0a2ecd178eb976cb5a7956e375c6a/psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"},
-    {url = "https://files.pythonhosted.org/packages/6e/c8/784968329c1c67c28cce91991ef9af8a8913aa5a3399a6a8954b1380572f/psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"},
-    {url = "https://files.pythonhosted.org/packages/79/26/f026804298b933b11640cc2d15155a545805df732e5ead3a2ad7cf45a38b/psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"},
-    {url = "https://files.pythonhosted.org/packages/89/a8/dd2f0866a7e87de751fb5f7c6eca99cbb953c81be76e1814ab3c8c3b0908/psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"},
-    {url = "https://files.pythonhosted.org/packages/8e/6b/9a3a5471b74d92dc85bfd71a7f7a55e013b258d86b4c3826ace9d49f7b8c/psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"},
-    {url = "https://files.pythonhosted.org/packages/99/9c/7a5761f9d2e79e6f781db5b25eeb9e74c2dc533bc52ee4749cb055a32ce9/psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"},
-    {url = "https://files.pythonhosted.org/packages/a5/73/35cea01aad1baf901c915dc95ea33a2f271c8ff8cf2f1c73b7f591f1bdf1/psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"},
-    {url = "https://files.pythonhosted.org/packages/ec/be/b8df2071eda861e65a1b2cec35770bb1f4523737e84a10aa41c53e39e9bc/psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"},
-]
-"pyasn1 0.4.8" = [
-    {url = "https://files.pythonhosted.org/packages/62/1e/a94a8d635fa3ce4cfc7f506003548d0a2447ae76fd5ca53932970fe3053f/pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d"},
-    {url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"},
-]
-"pyasn1-modules 0.2.8" = [
-    {url = "https://files.pythonhosted.org/packages/88/87/72eb9ccf8a58021c542de2588a867dbefc7556e14b2866d1e40e9e2b587e/pyasn1-modules-0.2.8.tar.gz", hash = "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e"},
-    {url = "https://files.pythonhosted.org/packages/95/de/214830a981892a3e286c3794f41ae67a4495df1108c3da8a9f62159b9a9d/pyasn1_modules-0.2.8-py2.py3-none-any.whl", hash = "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"},
-]
 "pycparser 2.21" = [
     {url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
     {url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
 ]
-"pyhamcrest 2.0.4" = [
-    {url = "https://files.pythonhosted.org/packages/b1/9a/588f086b64ace8d2e9843d8551e9068b2570c3c51b06cb49a107303f8700/pyhamcrest-2.0.4.tar.gz", hash = "sha256:b5d9ce6b977696286cf232ce2adf8969b4d0b045975b0936ac9005e84e67e9c1"},
-    {url = "https://files.pythonhosted.org/packages/fe/58/96afda21aa47a5b4de8f050a90c8e0f394c142a0edae251199fe873d2970/pyhamcrest-2.0.4-py3-none-any.whl", hash = "sha256:60a41d4783b9d56c9ec8586635d2301db5072b3ea8a51c32dd03c408ae2b0f79"},
-]
-"pyopenssl 22.1.0" = [
-    {url = "https://files.pythonhosted.org/packages/00/3f/ea5cfb789dddb327e6d2cf9377c36d9d8607af85530af0e7001165587ae7/pyOpenSSL-22.1.0-py3-none-any.whl", hash = "sha256:b28437c9773bb6c6958628cf9c3bebe585de661dba6f63df17111966363dd15e"},
-    {url = "https://files.pythonhosted.org/packages/e7/2f/c6d89edac75482f11e231b644e365d31d5479b7b727734e6a8f3d00decd5/pyOpenSSL-22.1.0.tar.gz", hash = "sha256:7a83b7b272dd595222d672f5ce29aa030f1fb837630ef229f62e72e395ce8968"},
-]
 "pyparsing 3.0.9" = [
     {url = "https://files.pythonhosted.org/packages/6c/10/a7d0fa5baea8fe7b50f448ab742f26f52b80bfca85ac2be9d35cdd9a3246/pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
     {url = "https://files.pythonhosted.org/packages/71/22/207523d16464c40a0310d2d4d8926daffa00ac1f5b1576170a32db749636/pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
@@ -810,13 +692,6 @@
     {url = "https://files.pythonhosted.org/packages/29/92/da92898b2aab0da78207afc9c035a71bedef3544966374c44e9627d761c5/rdflib_jsonld-0.6.2-py2.py3-none-any.whl", hash = "sha256:011afe67672353ca9978ab9a4bee964dff91f14042f2d8a28c22a573779d2f8b"},
     {url = "https://files.pythonhosted.org/packages/cd/1a/627de985dffc11b486eb07be86dc9a16c25b4877905f5f6a0be3633addb0/rdflib-jsonld-0.6.2.tar.gz", hash = "sha256:107cd3019d41354c31687e64af5e3fd3c3e3fa5052ce635f5ce595fd31853a63"},
 ]
-"scales 1.0.9" = [
-    {url = "https://files.pythonhosted.org/packages/08/85/b4a3933f227889b536a76c7ed5a0708ae5f63fe20f81d09a725228349e81/scales-1.0.9.tar.gz", hash = "sha256:8b6930f7d4bf115192290b44c757af5e254e3fcfcb75ff9a51f5c96a404e2753"},
-]
-"service-identity 21.1.0" = [
-    {url = "https://files.pythonhosted.org/packages/09/2e/26ade69944773df4748c19d3053e025b282f48de02aad84906d34a29d28b/service-identity-21.1.0.tar.gz", hash = "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34"},
-    {url = "https://files.pythonhosted.org/packages/93/5a/5e93f280ec7be676b5a57f305350f439d31ced168bca04e6ffa64b575664/service_identity-21.1.0-py2.py3-none-any.whl", hash = "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"},
-]
 "setuptools 65.6.3" = [
     {url = "https://files.pythonhosted.org/packages/b6/21/cb9a8d0b2c8597c83fce8e9c02884bce3d4951e41e807fc35791c6b23d9a/setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"},
     {url = "https://files.pythonhosted.org/packages/ef/e3/29d6e1a07e8d90ace4a522d9689d03e833b67b50d1588e693eec15f26251/setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},
@@ -833,9 +708,6 @@
     {url = "https://files.pythonhosted.org/packages/3f/98/538914a5ec084ba4f8c1327266925b0d37a344b0840769e506a17a8ab47e/sse-starlette-1.2.1.tar.gz", hash = "sha256:52760a16ed6380be53a2acca9173de17442f4165716f93679182e82ddd5c8a01"},
     {url = "https://files.pythonhosted.org/packages/c0/2f/d7dc1386c2a3fb9729bfc7117c753659457a094c9c17d524dd5e584742a6/sse_starlette-1.2.1-py3-none-any.whl", hash = "sha256:22116035906cb332ed3c5cb2d495af58fd55350de20866ecdc247db7fd69f2ef"},
 ]
-"standardservice 0.6.0" = [
-    {url = "https://projects.bigasterisk.com/standardservice/standardservice-0.6.0.tar.gz", hash = "sha256:0f1926ac396287a9aedfa964bd4a8ca651f18e18796ed3f1eba140270ffa4b83"},
-]
 "starlette 0.22.0" = [
     {url = "https://files.pythonhosted.org/packages/1d/4e/30eda84159d5b3ad7fe663c40c49b16dd17436abe838f10a56c34bee44e8/starlette-0.22.0-py3-none-any.whl", hash = "sha256:b5eda991ad5f0ee5d8ce4c4540202a573bb6691ecd0c712262d0bc85cf8f2c50"},
     {url = "https://files.pythonhosted.org/packages/f7/ff/64bd3362f80e81402f21e0f1ba55f8801cd899ad3f2a1bcc4a94d284c786/starlette-0.22.0.tar.gz", hash = "sha256:b092cbc365bea34dd6840b42861bdabb2f507f8671e642e8272d2442e08ea4ff"},
@@ -844,16 +716,14 @@
     {url = "https://files.pythonhosted.org/packages/3a/7b/2a88fc15c7314dccfb5addb019ed84700e7ede87d328b3f6386c37f27024/starlette_exporter-0.14.0.tar.gz", hash = "sha256:8e64d7b13ad072b1e916458510c68ff9c5eaafaf5b8b5a61c929eaee16c410c4"},
     {url = "https://files.pythonhosted.org/packages/50/55/59c632e6b2180bdf84d4e86dd0ad5171b646ae605e28b5aa789d09058a1d/starlette_exporter-0.14.0-py3-none-any.whl", hash = "sha256:69443e326e3b306f6fbec3da9dfc9c5ab555d4e0e0adc3372b879fbc409994e6"},
 ]
-"twisted 19.2.1" = [
-    {url = "https://files.pythonhosted.org/packages/79/59/035de19362320e632301ed7bbde23e4c8cd6fc5e2f1cf8d354cdba857854/Twisted-19.2.1.tar.bz2", hash = "sha256:fa2c04c2d68a9be7fc3975ba4947f653a57a656776f24be58ff0fe4b9aaf3e52"},
-]
-"twisted-sse 0.3.0" = [
-    {url = "https://projects.bigasterisk.com/twisted-sse/twisted_sse-0.3.0.tar.gz", hash = "sha256:4a112f1157901463666b24f5f38adaef83d7fbd58aa67eabcdd7ca0156624484"},
-]
 "txaio 22.2.1" = [
     {url = "https://files.pythonhosted.org/packages/6d/4b/28313388dfb2bdedb71b35b900459c56ba08ccb7ad2885487df037808c06/txaio-22.2.1.tar.gz", hash = "sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01"},
     {url = "https://files.pythonhosted.org/packages/77/97/001a2b89b07d0a1b17137e0ef33277ad16747a7b8391146bfc880890322b/txaio-22.2.1-py2.py3-none-any.whl", hash = "sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5"},
 ]
+"uvicorn 0.20.0" = [
+    {url = "https://files.pythonhosted.org/packages/95/3c/9f4650ff609370456f796bb96a355dcddef1ded67e05d1b4eb3481088329/uvicorn-0.20.0.tar.gz", hash = "sha256:a4e12017b940247f836bc90b72e725d7dfd0c8ed1c51eb365f5ba30d9f5127d8"},
+    {url = "https://files.pythonhosted.org/packages/96/f3/f39ac8ac3bdf356b4934b8f7e56173e96681f67ef0cd92bd33a5059fae9e/uvicorn-0.20.0-py3-none-any.whl", hash = "sha256:c3ed1598a5668208723f2bb49336f4509424ad198d6ab2615b7783db58d919fd"},
+]
 "yarl 1.8.1" = [
     {url = "https://files.pythonhosted.org/packages/02/83/af04c6a34e9011e1620ae918e223869adcc5de855ae2e89fd77fabfee626/yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"},
     {url = "https://files.pythonhosted.org/packages/02/df/0d3ae329942cb26c96ac5a4e9164a1d440ffd067331c6886b5ce0729b17a/yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"},
@@ -915,41 +785,3 @@
     {url = "https://files.pythonhosted.org/packages/ff/44/52161d5de9fc136e78e2e0663f2b386807f128b246f397817f4d4ae2e4f2/yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"},
     {url = "https://files.pythonhosted.org/packages/ff/c0/ddcd9c883887a67c2d241fc807111fc2be840dcc5513067ddd59abfed933/yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"},
 ]
-"zope.interface 5.5.2" = [
-    {url = "https://files.pythonhosted.org/packages/01/3a/8e57724eeb9b75d366f0c11b24080c69e12f0b3bfe4dc771336f21271c99/zope.interface-5.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d514c269d1f9f5cd05ddfed15298d6c418129f3f064765295659798349c43e6f"},
-    {url = "https://files.pythonhosted.org/packages/01/45/9ff2b9281597da5fcf84995ca18cde71abf248c98bfc7c6bdee60af87dbb/zope.interface-5.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5334e2ef60d3d9439c08baedaf8b84dc9bb9522d0dacbc10572ef5609ef8db6d"},
-    {url = "https://files.pythonhosted.org/packages/05/0b/ff478275c63e1b910c0605b0fdbb004cdf6730d17169fffb2ebe6a10fc44/zope.interface-5.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:65c3c06afee96c654e590e046c4a24559e65b0a87dbff256cd4bd6f77e1a33f9"},
-    {url = "https://files.pythonhosted.org/packages/05/2a/c863bd1e146b66795067677d7ba51290824c19e53f2c99bd0da36d545c43/zope.interface-5.5.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4087e253bd3bbbc3e615ecd0b6dd03c4e6a1e46d152d3be6d2ad08fbad742dcc"},
-    {url = "https://files.pythonhosted.org/packages/06/cf/57513c4915d6288854694f2323d2d15d3fd39e7c7b5c744ebe81b62fcb77/zope.interface-5.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8a2ffadefd0e7206adc86e492ccc60395f7edb5680adedf17a7ee4205c530df4"},
-    {url = "https://files.pythonhosted.org/packages/0f/9f/9e08ab4398974406a431be5a9ae4f83603d6d4e208ba276840f6192ec132/zope.interface-5.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7579960be23d1fddecb53898035a0d112ac858c3554018ce615cefc03024e46d"},
-    {url = "https://files.pythonhosted.org/packages/12/78/83d8b9893d1a3933d772b2b2a542146f5d1465fc770c7618efb4bc1e265e/zope.interface-5.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fb68d212efd057596dee9e6582daded9f8ef776538afdf5feceb3059df2d2e7b"},
-    {url = "https://files.pythonhosted.org/packages/1a/ba/ca524f2f7184346e93bae317580c4906bc2e81bdac6e3b68b64c632a7df0/zope.interface-5.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:765d703096ca47aa5d93044bf701b00bbce4d903a95b41fff7c3796e747b1f1d"},
-    {url = "https://files.pythonhosted.org/packages/2b/26/2dd8687863272cab8c34908b18c98c32f8cb6d3da6e23896622c1a8a4a2e/zope.interface-5.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:404d1e284eda9e233c90128697c71acffd55e183d70628aa0bbb0e7a3084ed8b"},
-    {url = "https://files.pythonhosted.org/packages/2e/d3/3be31d3433e4df8901857e1a62e87cc69d17f91116fbc45e2b3662ca8c27/zope.interface-5.5.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:008b0b65c05993bb08912f644d140530e775cf1c62a072bf9340c2249e613c32"},
-    {url = "https://files.pythonhosted.org/packages/2f/2d/fa6e7f296f9580feff6c2003e70601f111318e032f37f7266bc0a56abc48/zope.interface-5.5.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:17ebf6e0b1d07ed009738016abf0d0a0f80388e009d0ac6e0ead26fc162b3b9c"},
-    {url = "https://files.pythonhosted.org/packages/31/01/297bd775394c46e051d3201c6600adfaf72c346d6201062bb61227615d64/zope.interface-5.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dbaeb9cf0ea0b3bc4b36fae54a016933d64c6d52a94810a63c00f440ecb37dd7"},
-    {url = "https://files.pythonhosted.org/packages/38/6f/fbfb7dde38be7e5644bb342c4c7cdc444cd5e2ffbd70d091263b3858a8cb/zope.interface-5.5.2.tar.gz", hash = "sha256:bfee1f3ff62143819499e348f5b8a7f3aa0259f9aca5e0ddae7391d059dce671"},
-    {url = "https://files.pythonhosted.org/packages/48/fa/25d98f89f07e4524e465d4d5ca4164a443628eae0548f1ec085ea0ed2889/zope.interface-5.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f98d4bd7bbb15ca701d19b93263cc5edfd480c3475d163f137385f49e5b3a3a7"},
-    {url = "https://files.pythonhosted.org/packages/4e/61/e873d7006cd71df28319b2a2826fe1863b761d112598344920e383c58dcb/zope.interface-5.5.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:a2ad597c8c9e038a5912ac3cf166f82926feff2f6e0dabdab956768de0a258f5"},
-    {url = "https://files.pythonhosted.org/packages/51/af/60b486a1b09497343c08924cf43577f759532e0d8d85ff578c32233b10e5/zope.interface-5.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:0fb497c6b088818e3395e302e426850f8236d8d9f4ef5b2836feae812a8f699c"},
-    {url = "https://files.pythonhosted.org/packages/5a/0d/37e87563031b0f35e8002428f7fd27275660c64ff4ebceca0fca8996afc9/zope.interface-5.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:7e66f60b0067a10dd289b29dceabd3d0e6d68be1504fc9d0bc209cf07f56d189"},
-    {url = "https://files.pythonhosted.org/packages/5e/2e/1b0e9120ada342584ab82a4889a0c5dd21bd8846c356c2d5ccd2eb46db0f/zope.interface-5.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:959697ef2757406bff71467a09d940ca364e724c534efbf3786e86eee8591452"},
-    {url = "https://files.pythonhosted.org/packages/84/5d/88c886d8d6f738d7b291ac3e4162be94c960e7b0fb74ba2886685eff4762/zope.interface-5.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:a16025df73d24795a0bde05504911d306307c24a64187752685ff6ea23897cb0"},
-    {url = "https://files.pythonhosted.org/packages/8a/76/a58e11281d2a014fd9b42e7b5fc71e083f686cc9a55d0300af8b3819b5c2/zope.interface-5.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0980d44b8aded808bec5059018d64692f0127f10510eca71f2f0ace8fb11188"},
-    {url = "https://files.pythonhosted.org/packages/9b/c9/5bf7b7f4c26f1b92bd836a5a764337689d06dfdd8852204e6b396029369d/zope.interface-5.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d692374b578360d36568dd05efb8a5a67ab6d1878c29c582e37ddba80e66c396"},
-    {url = "https://files.pythonhosted.org/packages/9c/8f/cbe5432a50a4cf0492574c1e9328c39f034d36fc86e4d3040443c3287e1c/zope.interface-5.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e74a578172525c20d7223eac5f8ad187f10940dac06e40113d62f14f3adb1e8f"},
-    {url = "https://files.pythonhosted.org/packages/a8/cc/c5ad39d8a2207c360d5b68708788984651f8e975e3bebfe8ddd38592d02f/zope.interface-5.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604cdba8f1983d0ab78edc29aa71c8df0ada06fb147cea436dc37093a0100a4e"},
-    {url = "https://files.pythonhosted.org/packages/b1/71/5fe40e244f8dae4f06608778a92027cf154a9a4242aca088326da508103e/zope.interface-5.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e1574980b48c8c74f83578d1e77e701f8439a5d93f36a5a0af31337467c08fcf"},
-    {url = "https://files.pythonhosted.org/packages/b4/d5/1af2b1af567f428a09864c965778f00d8a550a86eabf6b1cd3b22e31a251/zope.interface-5.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:655796a906fa3ca67273011c9805c1e1baa047781fca80feeb710328cdbed87f"},
-    {url = "https://files.pythonhosted.org/packages/bf/0e/fc2272397b7fd6a907c9c8c8ed310749db0b451ce5ca5cf90de1ac01c166/zope.interface-5.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9d783213fab61832dbb10d385a319cb0e45451088abd45f95b5bb88ed0acca1a"},
-    {url = "https://files.pythonhosted.org/packages/c1/d6/b1bec1e7f059f87905d78882821800340d74d58e190e38390f808a6ecd3e/zope.interface-5.5.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6e972493cdfe4ad0411fd9abfab7d4d800a7317a93928217f1a5de2bb0f0d87a"},
-    {url = "https://files.pythonhosted.org/packages/c9/85/fd79a02eb965734bc4f5fd03b99b23bf34b42bc5b3665a42abbc0256248a/zope.interface-5.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:311196634bb9333aa06f00fc94f59d3a9fddd2305c2c425d86e406ddc6f2260d"},
-    {url = "https://files.pythonhosted.org/packages/d0/75/c80af74b361cedd35bdfb45ade9c22320d3431722186ff8ab1614df80059/zope.interface-5.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d169ccd0756c15bbb2f1acc012f5aab279dffc334d733ca0d9362c5beaebe88e"},
-    {url = "https://files.pythonhosted.org/packages/d2/0a/87528a07ce42929cb655398b2df292d2bc7183447b89967cff54b3db89a1/zope.interface-5.5.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:0217a9615531c83aeedb12e126611b1b1a3175013bbafe57c702ce40000eb9a0"},
-    {url = "https://files.pythonhosted.org/packages/ed/c9/265a39c9933aef7cea402c25fb80f6455407d74ed761816496166f55d05a/zope.interface-5.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc26c8d44472e035d59d6f1177eb712888447f5799743da9c398b0339ed90b1b"},
-    {url = "https://files.pythonhosted.org/packages/f4/3d/81815c8ad716c8a87dc4913472a1fe6fd51e185cb3812f556686049a3497/zope.interface-5.5.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6373d7eb813a143cb7795d3e42bd8ed857c82a90571567e681e1b3841a390d16"},
-    {url = "https://files.pythonhosted.org/packages/f5/06/e5db832d195b33853227dfb8e89ccbc707afed7a097f4b1829387a953ad2/zope.interface-5.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:696f3d5493eae7359887da55c2afa05acc3db5fc625c49529e84bd9992313296"},
-    {url = "https://files.pythonhosted.org/packages/f5/34/68ae976ee0f0accfc8845107210dcc4c7636f71998e056757c41ac5e5f55/zope.interface-5.5.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3218ab1a7748327e08ef83cca63eea7cf20ea7e2ebcb2522072896e5e2fceedf"},
-    {url = "https://files.pythonhosted.org/packages/fb/a7/4e2a58146d909115e102ce4038e3e8672f566174c55d8fa75325151b11fb/zope.interface-5.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e945de62917acbf853ab968d8916290548df18dd62c739d862f359ecd25842a6"},
-    {url = "https://files.pythonhosted.org/packages/ff/a1/788d02d891a5abdf842eec244b432891f56f5c23cb30504c35f70b588b85/zope.interface-5.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:40f4065745e2c2fa0dff0e7ccd7c166a8ac9748974f960cd39f63d2c19f9231f"},
-]
--- a/pyproject.toml	Fri Nov 25 20:58:08 2022 -0800
+++ b/pyproject.toml	Sat Nov 26 14:13:51 2022 -0800
@@ -6,18 +6,16 @@
     {name = "", email = ""},
 ]
 dependencies = [
-    "docopt>=0.6.2",
     "prometheus-client>=0.14.1",
     "rdflib>=6.1.1",
     "rdflib-jsonld>=0.6.2",
-    "service-identity>=21.1.0",
-    "twisted>=19.2.1",
-    "cyclone>=1.3",
-    "pyopenssl>=22.0.0",
-    "twisted-sse>=0.3.0",
-    "standardservice>=0.6.0",
     "patchablegraph>=1.5.0",
     "rdfdb>=0.24.0",
+    "starlette>=0.22.0",
+    "starlette-exporter>=0.14.0",
+    "uvicorn>=0.20.0",
+    "sse-starlette>=1.2.1",
+    "aiohttp-sse-client>=0.2.1",
 ]
 requires-python = ">=3.10"
 license = {text = "MIT"}
--- a/src/CollectorState.ts	Fri Nov 25 20:58:08 2022 -0800
+++ b/src/CollectorState.ts	Sat Nov 26 14:13:51 2022 -0800
@@ -44,7 +44,7 @@
   render() {
     const sourcesTable = (clients: Array<any>) => {
       const clientRow = (client: any) => {
-        const d = client.reconnectedPatchSource;
+        const d = client;
         const now = Date.now() / 1000;
         const dispSec = (sec: number) =>
           Math.abs(sec) > now - 1
--- a/vite.config.ts	Fri Nov 25 20:58:08 2022 -0800
+++ b/vite.config.ts	Sat Nov 26 14:13:51 2022 -0800
@@ -2,7 +2,6 @@
 
 export default defineConfig({
   base: "https://bigasterisk.com/collector/",
-  logLevel: "warn",
   server: {
     host: "0.0.0.0",
     strictPort: true,