changeset 1214:c1792115b1d3

new 'store' service for user inputs Ignore-this: 7b14beca506dd7f1e38b5214aae1833a darcs-hash:9a1fcc5c46b304442940cfd956bf8ac4e5f6007b
author drewp <drewp@bigasterisk.com>
date Sat, 16 Mar 2019 18:22:57 -0700
parents 462c4fd08d2e
children b48f1ecbc078
files service/store/Dockerfile service/store/db.nquads service/store/index.html service/store/makefile service/store/requirements.txt service/store/store.py
diffstat 6 files changed, 144 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/Dockerfile	Sat Mar 16 18:22:57 2019 -0700
@@ -0,0 +1,12 @@
+FROM bang6:5000/base_x86
+
+WORKDIR /opt
+
+COPY requirements.txt ./
+RUN pip install -r requirements.txt
+
+COPY *.py *.html ./
+
+EXPOSE 11014
+
+CMD [ "python", "./store.py" ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/db.nquads	Sat Mar 16 18:22:57 2019 -0700
@@ -0,0 +1,2 @@
+<http://e/1> <http://e/2> "3.5"^^<http://float> <http://projects.bigasterisk.com/room/stored> .
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/index.html	Sat Mar 16 18:22:57 2019 -0700
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+  <head>
+    <title>store</title>
+    <meta charset="utf-8" />
+  </head>
+  <body>
+
+    table of statements, creation times, creators, which UI they used
+    
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/makefile	Sat Mar 16 18:22:57 2019 -0700
@@ -0,0 +1,22 @@
+JOB=store
+PORT=10014
+
+TAG=bang6:5000/${JOB}_x86:latest
+
+push_x86:
+	docker push ${TAG}
+
+build_x86:
+	rm -rf tmp_ctx
+	mkdir -p tmp_ctx
+	cp -a Dockerfile ../../lib/*.py *.py *.txt *.html tmp_ctx
+	docker build --network=host -t ${TAG} tmp_ctx
+	rm -rf tmp_ctx
+
+build_image: build_x86 push_x86
+
+shell:
+	docker run --rm -it --cap-add SYS_PTRACE --net=host $(TAG) /bin/sh
+
+local_run: build_x86
+	docker run --rm -it --net=host -v `pwd`/index.html:/opt/index.html -v `pwd`:/opt/homeauto_store bang6:5000/store_x86:latest python ./store.py -v
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/requirements.txt	Sat Mar 16 18:22:57 2019 -0700
@@ -0,0 +1,4 @@
+rdflib==4.2.2
+cyclone
+https://projects.bigasterisk.com/rdfdb/rdfdb-0.6.0.tar.gz
+rdflib-jsonld==0.3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/store/store.py	Sat Mar 16 18:22:57 2019 -0700
@@ -0,0 +1,92 @@
+"""
+persistent store of rdf statements, meant for stmts from users.
+
+API is not typical rdf: putting statments replaces existing (s,o)
+matches so there can be only one object at a time. Putting the special
+object :unset clears the statement.
+"""
+
+import sys, logging
+from docopt import docopt
+from patchablegraph import PatchableGraph, CycloneGraphHandler, CycloneGraphEventsHandler
+from rdfdb.patch import Patch
+from rdflib import Namespace, URIRef, Literal, Graph
+from rdflib.parser import StringInputSource
+from twisted.internet import reactor
+from twisted.python.filepath import FilePath
+import cyclone.web
+
+ROOM = Namespace('http://projects.bigasterisk.com/room/')
+
+logging.basicConfig()
+log = logging.getLogger()
+
+CTX = ROOM['stored']
+
+class OutputPage(cyclone.web.RequestHandler):
+    def put(self):
+        arg = self.request.arguments
+        if arg.get('s') and arg.get('p'):
+            self._onQueryStringStatement(arg['s'][-1], arg['p'][-1], self.request.body)
+        else:
+            self._onGraphBodyStatements(self.request.body, self.request.headers)
+            
+    def _onQueryStringStatement(self, s, p, body):
+        subj = URIRef(arg['s'][-1])
+        pred = URIRef(arg['p'][-1])
+        turtleLiteral = self.request.body
+        try:
+            obj = Literal(float(turtleLiteral))
+        except ValueError:
+            obj = Literal(turtleLiteral)
+        self._onStatements([(subj, pred, obj)])
+        
+    def _onGraphBodyStatements(self, body, headers):
+        # maybe quads only so we can track who made the input and from what interface?
+        # Or your input of triples gets wrapped in a new quad in here?
+        g = Graph()
+        g.parse(StringInputSource(body), format='nt')
+        if not g:
+            raise ValueError("expected graph body")
+        self._onStatements(list(g.triples((None, None, None))))
+        
+    def _onStatements(self, stmts):
+        g = self.settings.masterGraph
+        for s, p, o in stmts:
+            patch = g.getObjectPatch(CTX, s, p, o)
+            if o == ROOM['unset']:
+                patch = Patch(delQuads=patch.delQuads)
+            g.patch(patch)
+        nquads = g.serialize(None, format='nquads')
+        self.settings.dbFile.setContent(nquads)
+    
+if __name__ == '__main__':
+    arg = docopt("""
+    Usage: store.py [options]
+
+    -v   Verbose
+    """)
+    log.setLevel(logging.WARN)
+    if arg['-v']:
+        from twisted.python import log as twlog
+        twlog.startLogging(sys.stdout)
+        log.setLevel(logging.DEBUG)
+
+    masterGraph = PatchableGraph()
+    dbFile = FilePath('/opt/homeauto_store/db.nquads')
+    if dbFile.exists():
+        masterGraph._graph.parse(dbFile.open(), format='nquads')
+    
+    port = 10014
+    reactor.listenTCP(port, cyclone.web.Application([
+        (r"/()", cyclone.web.StaticFileHandler,
+         {"path": ".", "default_filename": "index.html"}),
+        (r"/graph", CycloneGraphHandler, {'masterGraph': masterGraph}),
+        (r"/graph/events", CycloneGraphEventsHandler,
+         {'masterGraph': masterGraph}),
+        (r'/output', OutputPage),
+    ], masterGraph=masterGraph, dbFile=dbFile, debug=arg['-v']),
+                      interface='::')
+    log.warn('serving on %s', port)
+
+    reactor.run()