changeset 1732:3f4b447d65f5

port to starlette/asyncio
author drewp@bigasterisk.com
date Mon, 10 Jul 2023 17:37:58 -0700
parents 35abc7656f0f
children 6279b1ac2b5e
files service/rdf_to_mqtt/Dockerfile service/rdf_to_mqtt/deploy.yaml service/rdf_to_mqtt/ingress.yaml service/rdf_to_mqtt/pdm.lock service/rdf_to_mqtt/pyproject.toml service/rdf_to_mqtt/rdf_over_http.py service/rdf_to_mqtt/rdf_to_mqtt.py service/rdf_to_mqtt/requirements.txt service/rdf_to_mqtt/serv.n3 service/rdf_to_mqtt/skaffold.yaml
diffstat 10 files changed, 481 insertions(+), 153 deletions(-) [+]
line wrap: on
line diff
--- a/service/rdf_to_mqtt/Dockerfile	Fri Jun 30 22:11:06 2023 -0700
+++ b/service/rdf_to_mqtt/Dockerfile	Mon Jul 10 17:37:58 2023 -0700
@@ -1,14 +1,9 @@
-FROM bang5:5000/base_x86
+FROM bang5:5000/base_basic
 
 WORKDIR /opt
 
-COPY requirements.txt ./
-RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt
-RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/python3.zip?v3'
-RUN pip3 install -U attrs
+COPY pdm.lock pyproject.toml ./
+RUN pdm sync
 
 COPY *.py *.html *.js ./
 
-EXPOSE 10011:10011
-
-CMD [ "python3", "./rdf_to_mqtt.py", "-v" ]
--- a/service/rdf_to_mqtt/deploy.yaml	Fri Jun 30 22:11:06 2023 -0700
+++ b/service/rdf_to_mqtt/deploy.yaml	Mon Jul 10 17:37:58 2023 -0700
@@ -15,17 +15,17 @@
       containers:
         - name: rdf-to-mqtt
           image: bang5:5000/rdf_to_mqtt_image
-          imagePullPolicy: "Always"
+          args:
+            - pdm
+            - run
+            - uvicorn
+            - --access-log
+            - --host=0.0.0.0
+            - --log-level=trace
+            - --port=8000
+            - rdf_to_mqtt:app
           ports:
-          - containerPort: 10008
-      affinity:
-        nodeAffinity:
-          requiredDuringSchedulingIgnoredDuringExecution:
-            nodeSelectorTerms:
-            - matchExpressions:
-              - key: "kubernetes.io/hostname"
-                operator: In
-                values: ["bang"]
+            - containerPort: 8000
 ---
 apiVersion: v1
 kind: Service
@@ -33,6 +33,6 @@
   name: rdf-to-mqtt
 spec:
   ports:
-  - {port: 10008, targetPort: 10008}
+    - { port: 80, targetPort: 8000 }
   selector:
     app: rdf-to-mqtt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/ingress.yaml	Mon Jul 10 17:37:58 2023 -0700
@@ -0,0 +1,27 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  name: rdf-to-mqtt
+  annotations:
+    cert-manager.io/cluster-issuer: letsencrypt-prod
+    ingress.pomerium.io/allow_public_unauthenticated_access: "false"
+    ingress.pomerium.io/pass_identity_headers: "true"
+    ingress.pomerium.io/preserve_host_header: "true"
+    ingress.pomerium.io/policy: |
+      allow:
+        or: 
+          - { email: { is: "drewpca@gmail.com" }}
+          - { email: { is: "kelsimp@gmail.com" }}
+    ingress.pomerium.io/prefix_rewrite: "/"
+spec:
+  ingressClassName: pomerium
+  rules:
+    - host: "bigasterisk.com"
+      http:
+        paths:
+          - pathType: Prefix
+            path: /rdf_to_mqtt/
+            backend: { service: { name: rdf-to-mqtt, port: { number: 80 } } }
+  tls:
+    - hosts: [bigasterisk.com]
+      secretName: bigasterisk.com-tls
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/pdm.lock	Mon Jul 10 17:37:58 2023 -0700
@@ -0,0 +1,297 @@
+# This file is @generated by PDM.
+# It is not intended for manual editing.
+
+[[package]]
+name = "aiomqtt"
+version = "1.0.0"
+requires_python = ">=3.7,<4.0"
+summary = "The idiomatic asyncio MQTT client, wrapped around paho-mqtt"
+dependencies = [
+    "paho-mqtt<2.0.0,>=1.6.0",
+]
+
+[[package]]
+name = "anyio"
+version = "3.7.1"
+requires_python = ">=3.7"
+summary = "High level compatibility layer for multiple asynchronous event loop implementations"
+dependencies = [
+    "exceptiongroup; python_version < \"3.11\"",
+    "idna>=2.8",
+    "sniffio>=1.1",
+]
+
+[[package]]
+name = "asyncio-mqtt"
+version = "0.16.2"
+requires_python = ">=3.7"
+summary = "Idiomatic asyncio wrapper around paho-mqtt"
+dependencies = [
+    "paho-mqtt>=1.6.0",
+]
+
+[[package]]
+name = "certifi"
+version = "2023.5.7"
+requires_python = ">=3.6"
+summary = "Python package for providing Mozilla's CA Bundle."
+
+[[package]]
+name = "click"
+version = "8.1.4"
+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 = "elasticsearch"
+version = "7.17.9"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
+summary = "Python client for Elasticsearch"
+dependencies = [
+    "certifi",
+    "urllib3<2,>=1.21.1",
+]
+
+[[package]]
+name = "elasticsearch-logging-handler"
+version = "1.0.1"
+requires_python = ">=3.7"
+summary = "Minimalistic Elasticsearch logging handler"
+dependencies = [
+    "elasticsearch<8.0.0",
+    "pytz",
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.2"
+requires_python = ">=3.7"
+summary = "Backport of PEP 654 (exception groups)"
+
+[[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 = "idna"
+version = "3.4"
+requires_python = ">=3.5"
+summary = "Internationalized Domain Names in Applications (IDNA)"
+
+[[package]]
+name = "isodate"
+version = "0.6.1"
+summary = "An ISO 8601 date/time/duration parser and formatter"
+dependencies = [
+    "six",
+]
+
+[[package]]
+name = "logging-json"
+version = "0.4.0"
+requires_python = ">=3.7"
+summary = "JSON formatter for python logging"
+
+[[package]]
+name = "paho-mqtt"
+version = "1.6.1"
+summary = "MQTT version 5.0/3.1.1 client class"
+
+[[package]]
+name = "prometheus-client"
+version = "0.17.1"
+requires_python = ">=3.6"
+summary = "Python client for the Prometheus monitoring system."
+
+[[package]]
+name = "pyparsing"
+version = "3.1.0"
+requires_python = ">=3.6.8"
+summary = "pyparsing module - Classes and methods to define and execute parsing grammars"
+
+[[package]]
+name = "pytz"
+version = "2023.3"
+summary = "World timezone definitions, modern and historical"
+
+[[package]]
+name = "rdflib"
+version = "6.3.2"
+requires_python = ">=3.7,<4.0"
+summary = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information."
+dependencies = [
+    "isodate<0.7.0,>=0.6.0",
+    "pyparsing<4,>=2.1.0",
+]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+summary = "Python 2 and 3 compatibility utilities"
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+requires_python = ">=3.7"
+summary = "Sniff out which async library your code is running under"
+
+[[package]]
+name = "starlette"
+version = "0.28.0"
+requires_python = ">=3.7"
+summary = "The little ASGI library that shines."
+dependencies = [
+    "anyio<5,>=3.4.0",
+]
+
+[[package]]
+name = "starlette-exporter"
+version = "0.16.0"
+summary = "Prometheus metrics exporter for Starlette applications."
+dependencies = [
+    "prometheus-client>=0.12",
+    "starlette",
+]
+
+[[package]]
+name = "urllib3"
+version = "1.26.16"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+summary = "HTTP library with thread-safe connection pooling, file post, and more."
+
+[[package]]
+name = "uvicorn"
+version = "0.22.0"
+requires_python = ">=3.7"
+summary = "The lightning-fast ASGI server."
+dependencies = [
+    "click>=7.0",
+    "h11>=0.8",
+]
+
+[[package]]
+name = "victorialogger"
+version = "0.4.0"
+requires_python = ">=3.10"
+summary = ""
+dependencies = [
+    "elasticsearch-logging-handler>=1.0.1",
+    "logging-json>=0.4.0",
+]
+
+[metadata]
+lock_version = "4.2"
+cross_platform = true
+groups = ["default"]
+content_hash = "sha256:2e897ffd2914d6d86f5f5488ca6416c633b96eae1d855914f686b9b96d27137f"
+
+[metadata.files]
+"aiomqtt 1.0.0" = [
+    {url = "https://files.pythonhosted.org/packages/8c/d9/5e4859b847897d364cefb29e25d142d6031949499796e6808c24a36a1035/aiomqtt-1.0.0.tar.gz", hash = "sha256:a96c4af50f54ded0c07d4dfc14aa3e212265cbebf659e2ab64950cf14ba8dca2"},
+    {url = "https://files.pythonhosted.org/packages/cf/7b/c842643452c12c02a1cbc03c9cef24188c81d7b557bd319ebd2c065dbf75/aiomqtt-1.0.0-py3-none-any.whl", hash = "sha256:dd6fe629c10ac24a3d2abc2bcc09afe4f66bc06b6457b63cf764cc81f3b980fe"},
+]
+"anyio 3.7.1" = [
+    {url = "https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"},
+    {url = "https://files.pythonhosted.org/packages/28/99/2dfd53fd55ce9838e6ff2d4dac20ce58263798bd1a0dbe18b3a9af3fcfce/anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"},
+]
+"asyncio-mqtt 0.16.2" = [
+    {url = "https://files.pythonhosted.org/packages/2a/64/c8a8c2ed51f3c1f4b8d2f244424d3bad3fbd4333eb01589c6b00a6dd2c04/asyncio_mqtt-0.16.2-py3-none-any.whl", hash = "sha256:fe70ea2c648b248779a7ff3d9218262cdd739083743dfaa7c0d52ba458a8ad71"},
+]
+"certifi 2023.5.7" = [
+    {url = "https://files.pythonhosted.org/packages/93/71/752f7a4dd4c20d6b12341ed1732368546bc0ca9866139fe812f6009d9ac7/certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
+    {url = "https://files.pythonhosted.org/packages/9d/19/59961b522e6757f0c9097e4493fa906031b95b3ebe9360b2c3083561a6b4/certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
+]
+"click 8.1.4" = [
+    {url = "https://files.pythonhosted.org/packages/77/88/b0cc5fe95c31c301e9823ea9b028f669c0dcfa205ff71111037a5ed4892c/click-8.1.4.tar.gz", hash = "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37"},
+    {url = "https://files.pythonhosted.org/packages/f9/a6/dc327484918f1656cc9fcebebe77efcfc0ef0d447fa925a8760ee55abe0e/click-8.1.4-py3-none-any.whl", hash = "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3"},
+]
+"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"},
+]
+"elasticsearch 7.17.9" = [
+    {url = "https://files.pythonhosted.org/packages/19/3e/91d8970b17a898bbe01c1833e246cc231abddaf70ab987c3f2fd81142964/elasticsearch-7.17.9.tar.gz", hash = "sha256:66c4ece2adfe7cc120e2b6a6798a1fd5c777aecf82eec39bb95cef7cfc7ea2b3"},
+    {url = "https://files.pythonhosted.org/packages/9b/0a/5ad75e5379bf01c044aa00e0ea893eab630cb5339db87b84436f2b762ffd/elasticsearch-7.17.9-py2.py3-none-any.whl", hash = "sha256:0e2454645dc00517dee4c6de3863411a9c5f1955d013c5fefa29123dadc92f98"},
+]
+"elasticsearch-logging-handler 1.0.1" = [
+    {url = "https://files.pythonhosted.org/packages/0f/62/acbf0fd217a44eaec7535ec2b0e5a94d7ed3266d6c07f39268afd1155f27/elasticsearch_logging_handler-1.0.1-py3-none-any.whl", hash = "sha256:b99c2ab7d64d756f7ef4d631d55e887b354ebd64d6577ba4b348cfba63666065"},
+    {url = "https://files.pythonhosted.org/packages/9e/3a/c03e8df9ef1d6fa6ed2bd9cde95f4f350e9346414f8a949e43260a83493b/elasticsearch-logging-handler-1.0.1.tar.gz", hash = "sha256:9bb96f665b35aa46c33a7204adbcbd43a37895478ba11a8565c61d4bbe105a31"},
+]
+"exceptiongroup 1.1.2" = [
+    {url = "https://files.pythonhosted.org/packages/55/09/5d2079ecab0ca483e527a1707a483562bdc17abf829d3e73f0c1a73b61c7/exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"},
+    {url = "https://files.pythonhosted.org/packages/fe/17/f43b7c9ccf399d72038042ee72785c305f6c6fdc6231942f8ab99d995742/exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"},
+]
+"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"},
+]
+"idna 3.4" = [
+    {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"},
+]
+"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"},
+]
+"logging-json 0.4.0" = [
+    {url = "https://files.pythonhosted.org/packages/1d/6d/42d547fd5c5e38a079d8d174da88864b1cfb8a0400c4464ee1af1d0451ac/logging_json-0.4.0.tar.gz", hash = "sha256:ab550d0a16c17a3de7740b1552738ff4f478093b1688902fa397d08ed1952678"},
+    {url = "https://files.pythonhosted.org/packages/6f/a0/85b77c3b4daa9bc884d8c4f65e71cc11cb33b46da8d6b75ad8364cc5f793/logging_json-0.4.0-py3-none-any.whl", hash = "sha256:e56c0e79fd18573007ccef50fa98689f73c4640c5a2117e7375af0ac43ac95d7"},
+]
+"paho-mqtt 1.6.1" = [
+    {url = "https://files.pythonhosted.org/packages/f8/dd/4b75dcba025f8647bc9862ac17299e0d7d12d3beadbf026d8c8d74215c12/paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"},
+]
+"prometheus-client 0.17.1" = [
+    {url = "https://files.pythonhosted.org/packages/ad/b3/6e18c89bf6bd120590ea538a62cae16dc763ff2745b18377c4be5495c4aa/prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"},
+    {url = "https://files.pythonhosted.org/packages/f5/05/aee33352594522c56eb4a4382b5acd9a706a030db9ba2fc3dc38a283e75c/prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"},
+]
+"pyparsing 3.1.0" = [
+    {url = "https://files.pythonhosted.org/packages/4f/13/28e88033cab976721512e7741000fb0635fa078045e530a91abb25aea0c0/pyparsing-3.1.0.tar.gz", hash = "sha256:edb662d6fe322d6e990b1594b5feaeadf806803359e3d4d42f11e295e588f0ea"},
+    {url = "https://files.pythonhosted.org/packages/a4/24/6ae4c9c45cf99d96b06b5d99e25526c060303171fb0aea9da2bfd7dbde93/pyparsing-3.1.0-py3-none-any.whl", hash = "sha256:d554a96d1a7d3ddaf7183104485bc19fd80543ad6ac5bdb6426719d766fb06c1"},
+]
+"pytz 2023.3" = [
+    {url = "https://files.pythonhosted.org/packages/5e/32/12032aa8c673ee16707a9b6cdda2b09c0089131f35af55d443b6a9c69c1d/pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"},
+    {url = "https://files.pythonhosted.org/packages/7f/99/ad6bd37e748257dd70d6f85d916cafe79c0b0f5e2e95b11f7fbc82bf3110/pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"},
+]
+"rdflib 6.3.2" = [
+    {url = "https://files.pythonhosted.org/packages/af/92/d7fb1d7fb70c9f7003fa50b7a3880ebcb311cc3f8552b3595e7c8f75aeeb/rdflib-6.3.2-py3-none-any.whl", hash = "sha256:36b4e74a32aa1e4fa7b8719876fb192f19ecd45ff932ea5ebbd2e417a0247e63"},
+    {url = "https://files.pythonhosted.org/packages/c8/28/4d1f27c5d73f58e567ca1a14a4eab7d7978a09c4e117687f9f6c216d3366/rdflib-6.3.2.tar.gz", hash = "sha256:72af591ff704f4caacea7ecc0c5a9056b8553e0489dd4f35a9bc52dbd41522e0"},
+]
+"six 1.16.0" = [
+    {url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+    {url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+]
+"sniffio 1.3.0" = [
+    {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+    {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+"starlette 0.28.0" = [
+    {url = "https://files.pythonhosted.org/packages/ba/f3/3cca6acd03eb7e2da1dcf35148815d94e588bf7c1e555a002c888d8a23ee/starlette-0.28.0-py3-none-any.whl", hash = "sha256:e58b9fc402c579950260fbb6d57173395c4e62804c40d3ede7e9ef1074f0c579"},
+    {url = "https://files.pythonhosted.org/packages/f2/7b/05e2ddc8d0da28c3c916d637cfe509d16e7a2e2cf7faa7cb888446326a30/starlette-0.28.0.tar.gz", hash = "sha256:7bf3da5e997e796cc202cef2bd3f96a7d9b1e1943203c2fe2b42e020bc658482"},
+]
+"starlette-exporter 0.16.0" = [
+    {url = "https://files.pythonhosted.org/packages/91/7a/1edb5c8de27ab3e28a4e6b1f96f5a9de1fffc2a645a095bc5368a0776dcb/starlette_exporter-0.16.0.tar.gz", hash = "sha256:728cccf975c85d3cf2844b0110b51e1fa2dce628ef68bc38da58ad691f9b5d68"},
+    {url = "https://files.pythonhosted.org/packages/e6/bc/ca5f04ac95634ecba7199ca0912558bec91ca69449edbd2920915432f3c6/starlette_exporter-0.16.0-py3-none-any.whl", hash = "sha256:9dbe8dc647acbeb8680d53cedbbb8042ca75ca1b6987f609c5601ea96ddb7422"},
+]
+"urllib3 1.26.16" = [
+    {url = "https://files.pythonhosted.org/packages/c5/05/c214b32d21c0b465506f95c4f28ccbcba15022e000b043b72b3df7728471/urllib3-1.26.16-py2.py3-none-any.whl", hash = "sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f"},
+    {url = "https://files.pythonhosted.org/packages/e2/7d/539e6f0cf9f0b95b71dd701a56dae89f768cd39fd8ce0096af3546aeb5a3/urllib3-1.26.16.tar.gz", hash = "sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14"},
+]
+"uvicorn 0.22.0" = [
+    {url = "https://files.pythonhosted.org/packages/ad/bd/d47ee02312640fcf26c7e1c807402d5c5eab468571153a94ec8f7ada0e46/uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"},
+    {url = "https://files.pythonhosted.org/packages/c6/dd/0d3bab50ab4ef8bec849f89fec2adc2fabcc397018c30e57d9f0d4009c5e/uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"},
+]
+"victorialogger 0.4.0" = [
+    {url = "https://projects.bigasterisk.com/victorialogger/victorialogger-0.4.0.tar.gz", hash = "sha256:9fae42f114b3808079b2c18764773d074077678720a52c598e2852e26eb0abac"},
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/rdf_to_mqtt/pyproject.toml	Mon Jul 10 17:37:58 2023 -0700
@@ -0,0 +1,32 @@
+[project]
+name = ""
+version = ""
+description = ""
+authors = [{ name = "Drew Perttula", email = "drewp@bigasterisk.com" }]
+dependencies = [
+    "victorialogger>=0.4.0",
+    "starlette>=0.28.0",
+    "starlette-exporter>=0.16.0",
+    "uvicorn>=0.22.0",
+    "asyncio-mqtt>=0.16.2",
+    "aiomqtt>=1.0.0",
+    "rdflib>=6.3.2",
+]
+requires-python = ">=3.10"
+license = { text = "MIT" }
+
+[build-system]
+requires = ["pdm-pep517>=1.0.0"]
+build-backend = "pdm.pep517.api"
+
+[tool]
+
+[tool.pdm]
+
+[tool.pdm.overrides]
+pyopenssl = ">=22.0.0"
+
+[[tool.pdm.source]]
+url = "https://projects.bigasterisk.com/"
+verify_ssl = true
+name = "home"
--- a/service/rdf_to_mqtt/rdf_over_http.py	Fri Jun 30 22:11:06 2023 -0700
+++ b/service/rdf_to_mqtt/rdf_over_http.py	Mon Jul 10 17:37:58 2023 -0700
@@ -19,9 +19,9 @@
 
 def rdfStatementsFromRequest(arg, body, headers):
     if arg.get('s') and arg.get('p'):
-        subj = expandQueryParamUri(arg['s'][-1])
-        pred = expandQueryParamUri(arg['p'][-1])
-        turtleLiteral = body
+        subj = expandQueryParamUri(arg['s'])
+        pred = expandQueryParamUri(arg['p'])
+        turtleLiteral = body.decode('utf8')
         try:
             obj = Literal(float(turtleLiteral))
         except ValueError:
--- a/service/rdf_to_mqtt/rdf_to_mqtt.py	Fri Jun 30 22:11:06 2023 -0700
+++ b/service/rdf_to_mqtt/rdf_to_mqtt.py	Mon Jul 10 17:37:58 2023 -0700
@@ -4,39 +4,49 @@
 
 This is like light9/bin/collector.
 """
+import asyncio
 import json
+import os
+import time
 
-import cyclone.web
-from cycloneerr import PrettyErrorHandler
-from docopt import docopt
-from greplin import scales
-from greplin.scales.cyclonehandler import StatsHandler
-from mqtt_client import MqttClient
+from prometheus_client import Counter, Gauge, Summary
 from rdflib import Namespace
-from standardservice.logsetup import log, verboseLogging
-from twisted.internet import reactor
+from starlette_exporter import PrometheusMiddleware, handle_metrics
+from starlette.applications import Starlette
+from starlette.requests import Request
+from starlette.responses import PlainTextResponse
+from starlette.routing import Route
+from starlette.staticfiles import StaticFiles
+import aiomqtt
+
 from devs import devs
 import rdf_over_http
 
+# from victorialogger import log
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger(__name__)
+
 ROOM = Namespace('http://projects.bigasterisk.com/room/')
 
-STATS = scales.collection(
-    '/root',
-    scales.PmfStat('putRequests'),
-    scales.PmfStat('statement'),
-    scales.PmfStat('mqttPublish'),
-)
+PUT_REQUESTS = Summary('put_requests', 'calls')
+STATEMENT = Summary('on_statement', 'calls')
+MQTT_PUBLISH = Summary('mqtt_publish', 'calls')
+
+mqtt: aiomqtt.Client | None = None
 
 
-class OutputPage(PrettyErrorHandler, cyclone.web.RequestHandler):
+class OutputPage:
 
-    @STATS.putRequests.time()
-    def put(self):
-        for stmt in rdf_over_http.rdfStatementsFromRequest(self.request.arguments, self.request.body, self.request.headers):
-            self._onStatement(stmt)
+    async def put(self, request: Request) -> PlainTextResponse:
+        with PUT_REQUESTS.time():
+            for stmt in rdf_over_http.rdfStatementsFromRequest(request.query_params, await request.body(), request.headers):
+                await self._onStatement(stmt)
+        return PlainTextResponse("ok")
 
-    @STATS.statement.time()
-    def _onStatement(self, stmt):
+    @STATEMENT.time()
+    async def _onStatement(self, stmt):
         log.info(f'incoming statement: {stmt}')
         ignored = True
         for dev, attrs in devs.items():
@@ -47,100 +57,105 @@
                 brightness = stmt[2].toPython()
 
                 if attrs.get('values', '') == 'binary':
-                    self._publishOnOff(attrs, brightness)
+                    await self._publishOnOff(attrs, brightness)
                 else:
-                    self._publishRgbw(attrs, brightness)
+                    await self._publishRgbw(attrs, brightness)
                 ignored = False
             if stmt[0:2] == (dev, ROOM['inputSelector']):
-                choice = stmt[2].toPython().decode('utf8')
-                self._publish(topic=attrs['root'], message=f'input_{choice}')
+                choice = stmt[2].toPython()
+                await self._publish(topic=attrs['root'], message=f'input_{choice}')
                 ignored = False
             if stmt[0:2] == (dev, ROOM['volumeChange']):
                 delta = int(stmt[2].toPython())
                 which = 'up' if delta > 0 else 'down'
-                self._publish(topic=f'theater_blaster/ir_out/volume_{which}', message=json.dumps({'timed': abs(delta)}))
+                await self._publish(topic=f'theater_blaster/ir_out/volume_{which}', message=json.dumps({'timed': abs(delta)}))
                 ignored = False
             if stmt[0:2] == (dev, ROOM['color']):
-                h = stmt[2].toPython()
-                msg = {}
-                if h.endswith(b'K'):  # accept "0.7*2200K" (brightness 0.7)
-                    # see https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html#zigbee2mqttfriendly_nameset
-                    bright, kelvin = map(float, h[:-1].split(b'*'))
-                    msg['state'] = 'ON'
-                    msg["color_temp"] = round(1000000 / kelvin, 2)
-                    msg['brightness'] = int(bright * 255)  # 1..20 look about the same
-                else:
-                    r, g, b = int(h[1:3], 16), int(h[3:5], 16), int(h[5:7], 16)
-                    msg = {
-                        'state': 'ON' if r or g or b else 'OFF',
-                        'color': {
-                            'r': r,
-                            'g': g,
-                            'b': b
-                        },
-                        'brightness': max(r, g, b),
-                    }
-
-                    if attrs.get('hasWhite', False):
-                        msg['white_value'] = max(r, g, b)
-                msg.update(attrs.get('defaults', {}))
-                self._publish(topic=attrs['root'], message=json.dumps(msg))
+                msg = self._onColor(stmt[2].toPython(), attrs)
+                await self._publish(topic=attrs['root'], message=json.dumps(msg))
                 ignored = False
 
         if ignored:
             log.warn("ignoring %s", stmt)
 
-    def _publishOnOff(self, attrs, brightness):
+    def _onColor(self, h, attrs):
+        if isinstance(h, bytes):
+            h = h.decode('utf8')
+        msg = {}
+        if h.endswith('K'):  # accept "0.7*2200K" (brightness 0.7)
+            # see https://www.zigbee2mqtt.io/information/mqtt_topics_and_message_structure.html#zigbee2mqttfriendly_nameset
+            bright, kelvin = map(float, h[:-1].split('*'))
+            msg['state'] = 'ON'
+            msg["color_temp"] = round(1000000 / kelvin, 2)
+            msg['brightness'] = int(bright * 255)  # 1..20 look about the same
+        else:
+            r, g, b = int(h[1:3], 16), int(h[3:5], 16), int(h[5:7], 16)
+            msg = {
+                'state': 'ON' if r or g or b else 'OFF',
+                'color': {
+                    'r': r,
+                    'g': g,
+                    'b': b
+                },
+                'brightness': max(r, g, b),
+            }
+
+            if attrs.get('hasWhite', False):
+                msg['white_value'] = max(r, g, b)
+        msg.update(attrs.get('defaults', {}))
+        return msg
+
+    async def _publishOnOff(self, attrs, brightness):
         msg = 'OFF'
         if brightness > 0:
             msg = 'ON'
-        self._publish(topic=attrs['root'], message=msg)
+        await self._publish(topic=attrs['root'], message=msg)
 
-    def _publishRgbw(self, attrs, brightness):
+    async def _publishRgbw(self, attrs, brightness):
         for chan, scale in [('w1', 1), ('r', 1), ('g', .8), ('b', .8)]:
-            self._publish(topic=f"{attrs['root']}/light/kit_{chan}/command", messageJson={'state': 'ON', 'brightness': int(brightness * 255)})
+            await self._publish(topic=f"{attrs['root']}/light/kit_{chan}/command", messageJson={'state': 'ON', 'brightness': int(brightness * 255)})
 
-    def _publishFrontScreenText(self, stmt):
+    async def _publishFrontScreenText(self, stmt):
         ignored = True
         for line in ['line1', 'line2', 'line3', 'line4']:
             if stmt[1] == ROOM[line]:
                 ignored = False
-                self.settings.mqtt.publish(b'frontwindow/%s' % line.encode('ascii'), stmt[2].toPython())
+                assert mqtt is not None
+                await mqtt.publish('frontwindow/%s' % line, stmt[2].toPython())
         return ignored
 
-    @STATS.mqttPublish.time()
-    def _publish(self, topic: str, messageJson: object = None, message: str = None):
+    @MQTT_PUBLISH.time()
+    async def _publish(self, topic: str, messageJson: object = None, message: str | None = None):
         log.debug(f'mqtt.publish {topic} {message} {messageJson}')
         if messageJson is not None:
             message = json.dumps(messageJson)
-        self.settings.mqtt.publish(topic.encode('ascii'), message.encode('ascii'))
+        assert mqtt is not None
+        await mqtt.publish(topic, message)
 
 
-if __name__ == '__main__':
-    arg = docopt("""
-    Usage: rdf_to_mqtt.py [options]
+def main():
 
-    -v   Verbose
-    """)
-    verboseLogging(arg['-v'])
+    async def start2():
+        global mqtt
+        async with aiomqtt.Client(os.environ.get('MOSQUITTO', "mosquitto-ext"), 1883, client_id="rdf_to_mqtt-%s" % time.time(), keepalive=6) as mqtt:
+            log.info(f'connected to mqtt {mqtt}')
+            while True:
+                await asyncio.sleep(5)
 
-    mqtt = MqttClient(clientId='rdf_to_mqtt', brokerHost='mosquitto-ext.default.svc.cluster.local', brokerPort=1883)
+    def start():
+        asyncio.create_task(start2())
 
-    port = 10008
-    reactor.listenTCP(port,
-                      cyclone.web.Application([
-                          (r"/()", cyclone.web.StaticFileHandler, {
-                              "path": ".",
-                              "default_filename": "index.html"
-                          }),
-                          (r'/output', OutputPage),
-                          (r'/stats/(.*)', StatsHandler, {
-                              'serverName': 'rdf_to_mqtt'
-                          }),
-                      ],
-                                              mqtt=mqtt,
-                                              debug=arg['-v']),
-                      interface='::')
-    log.warn('serving on %s', port)
+    log.info('make app')
+    app = Starlette(debug=True,
+                    on_startup=[start],
+                    routes=[
+                        Route('/', StaticFiles(directory='.', html=True)),
+                        Route("/output", OutputPage().put, methods=["PUT"]),
+                    ])
+    app.add_middleware(PrometheusMiddleware, app_name='environment')
+    app.add_route("/metrics", handle_metrics)
+    log.info('return app')
+    return app
 
-    reactor.run()
+
+app = main()
\ No newline at end of file
--- a/service/rdf_to_mqtt/requirements.txt	Fri Jun 30 22:11:06 2023 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-cyclone
-rdflib-jsonld==0.4.0
-rdflib==4.2.2
-twisted-mqtt==0.3.6
-rx==1.6.1
-git+http://github.com/drewp/scales.git@448d59fb491b7631877528e7695a93553bfaaa93#egg=scales
-docopt
-
-cycloneerr
-patchablegraph==0.11.0
-rdfdb==0.21.0
-standardservice==0.6.0
-mqtt_client==0.7.0
--- a/service/rdf_to_mqtt/serv.n3	Fri Jun 30 22:11:06 2023 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-@prefix : <http://bigasterisk.com/ns/serv#> .
-@prefix auth: <http://bigasterisk.com/ns/serv/auth#> .
-@prefix serv: <http://bigasterisk.com/services/> .
-
-
-serv:rdf_to_mqtt_image a :DockerImage;
-      :dockerFile "Dockerfile";
-      :internalPort 10008
-      .
-      
-
-
-serv:rdf_to_mqtt a :Service;
-      :image serv:rdf_to_mqtt_image;
-      :path "/rdf_to_mqtt/";
-      :openid auth:admin;
-      :serverHost "bang";
-      :port 10008;
-      :prodDockerFlags (
-      );
-      :localRunDockerFlags (
-        "-v" "`pwd`:/opt"
-      );
-      :prodCmdline ("python3" "rdf_to_mqtt.py");
-      :localRunCmdline ( "python3" "rdf_to_mqtt.py" "-v" );
-.
-
--- a/service/rdf_to_mqtt/skaffold.yaml	Fri Jun 30 22:11:06 2023 -0700
+++ b/service/rdf_to_mqtt/skaffold.yaml	Mon Jul 10 17:37:58 2023 -0700
@@ -1,15 +1,17 @@
-apiVersion: skaffold/v2beta5
+apiVersion: skaffold/v4beta6
 kind: Config
 metadata:
   name: rdf-to-mqtt
 build:
+  artifacts:
+    - image: bang5:5000/rdf_to_mqtt_image
   tagPolicy:
     dateTime:
-      format: "2006-01-02_15-04-05"
-      timezone: "Local"
-  artifacts:
-  - image: bang5:5000/rdf_to_mqtt_image
+      format: 2006-01-02_15-04-05
+      timezone: Local
+manifests:
+  rawYaml:
+    - deploy.yaml
+    - ingress.yaml
 deploy:
-  kubectl:
-    manifests:
-    - deploy.yaml
+  kubectl: {}