Repository: simonsobs/ocs

Issue:
booleans published to influxdb get malformed by publisher
Seen at Penn recently:
```
2022-07-29T19:23:54+0000 InfluxDB Client Error: 400: {"error":"unable to parse 'observatory.pysmurf-monitor-so6,feed=ufm_mv17_meta AMCcSmurfProcessorFileWriterIsOpen=Falsei 1659122634599413504': invalid boolean"}
2022-07-29T19:23:58+0000 InfluxDB Client Error: 400: {"error":"unable to parse 'observatory.pysmurf-monitor-so6,feed=ufm_mv4_meta AMCcSmurfProcessorFileWriterIsOpen=Falsei 1659122637660398848': invalid boolean"}
2022-07-29T19:23:58+0000 InfluxDB Client Error: 400: {"error":"unable to parse 'observatory.pysmurf-monitor-so6,feed=ufm_mv11_meta AMCcSmurfProcessorFileWriterIsOpen=Falsei 1659122637663661824': invalid boolean"}
```

This is caused by these couple lines: https://github.com/simonsobs/ocs/blob/971bac7f8133744184a46c1312800cbcf44ea11e/ocs/agent/influxdb_publisher.py#L174-L175

Since `bool` is a subclass of `int` this results in `True` for any `bool` in `mv`, which appends an "i" to the bool. This fails to insert into InfluxDB, hence the error. We saw this same thing in #229.

Discussion:

Patch:
diff --git a/ocs/agents/influxdb_publisher/drivers.py b/ocs/agents/influxdb_publisher/drivers.py
index 018657a..116fdef 100644
--- a/ocs/agents/influxdb_publisher/drivers.py
+++ b/ocs/agents/influxdb_publisher/drivers.py
@@ -125,6 +125,19 @@ class Publisher:
             except InfluxDBServerError as err:
                 LOG.error("InfluxDB Server Error: {e}", e=err)
 
+    @staticmethod
+    def _format_field_line(field_key, field_value):
+        """Format key-value pair for InfluxDB line protocol."""
+        # Strings must be in quotes for line protocol
+        if isinstance(field_value, str):
+            line = f'{field_key}="{field_value}"'
+        else:
+            line = f"{field_key}={field_value}"
+        # Don't append 'i' to bool, which is a subclass of int
+        if isinstance(field_value, int) and not isinstance(field_value, bool):
+            line += "i"
+        return line
+
     @staticmethod
     def format_data(data, feed, protocol):
         """Format the data from an OCS feed into a dict for pushing to InfluxDB.
@@ -167,13 +180,7 @@ class Publisher:
                 if protocol == 'line':
                     fields_line = []
                     for mk, mv in fields.items():
-                        # Strings must be in quotes for line protocol
-                        if isinstance(mv, str):
-                            f_line = f'{mk}="{mv}"'
-                        else:
-                            f_line = f"{mk}={mv}"
-                        if isinstance(mv, int):
-                            f_line += "i"
+                        f_line = Publisher._format_field_line(mk, mv)
                         fields_line.append(f_line)
 
                     measurement_line = ','.join(fields_line)


Test patch:
diff --git a/tests/agents/test_influxdb_publisher_drivers.py b/tests/agents/test_influxdb_publisher_drivers.py
new file mode 100644
index 0000000..4e3bca5
--- /dev/null
+++ b/tests/agents/test_influxdb_publisher_drivers.py
@@ -0,0 +1,13 @@
+from ocs.agents.influxdb_publisher.drivers import Publisher
+
+import pytest
+
+
+@pytest.mark.parametrize("key,value,result", [('fieldname', False, 'fieldname=False'),
+                                              ('fieldname', 1, 'fieldname=1i'),
+                                              ('fieldname', 4.2, 'fieldname=4.2'),
+                                              ('fieldname', 'string', 'fieldname="string"')])
+def test_publisher_format_field_line(key, value, result):
+    f_line = Publisher._format_field_line(key, value)
+
+    assert f_line == result