Repository: kangasta/fdbk

Issue:
Reporter data source should support template topics
In addition to topic property, reporter should read template property.

Discussion:

Patch:
diff --git a/examples/net_status/net_status.py b/examples/net_status/net_status.py
index 6221aaf..8844052 100644
--- a/examples/net_status/net_status.py
+++ b/examples/net_status/net_status.py
@@ -18,13 +18,11 @@ class NetStatus(object):
         self._target = target
         self._timeout = timeout
 
-    # TODO #62: add netstatus template
-
     @property
-    def topic(self):
+    def template(self):
         return {
-            "name": self._target.get('name'),
-            "type_str": "topic",
+            "name": "netstatus",
+            "type_str": "template",
             "description": "Network connection status monitor.",
             "fields": ['elapsed', 'status_code'],
             "units": [{
@@ -40,6 +38,15 @@ class NetStatus(object):
             }],
         }
 
+    @property
+    def topic(self):
+        return {
+            "name": self._target.get('name'),
+            "template": "netstatus",
+            "type_str": "topic",
+            "metadata": {"url": self._target.get('url')},
+        }
+
     @property
     def data(self):
         data = {}
diff --git a/examples/sys_status/sys_status.py b/examples/sys_status/sys_status.py
index 03b3792..79c7a4a 100644
--- a/examples/sys_status/sys_status.py
+++ b/examples/sys_status/sys_status.py
@@ -9,18 +9,16 @@ class SysStatus(object):
     def __init__(self, topic_name='System status'):
         self.__topic_name = topic_name
 
-    # TODO #62: add sysstatus template
-
     @property
-    def topic(self):
+    def template(self):
         fields = ['CPU_usage', 'memory_usage', 'disk_usage']
         units = (
             list(map(lambda field: {'field': field, 'unit': 'percent'}, fields))
         )
 
         return {
-            "name": self.__topic_name,
-            "type_str": "topic",
+            "name": "sysstatus",
+            "type_str": "template",
             "description": "System status monitor.",
             "fields": fields,
             "units": units,
@@ -33,6 +31,14 @@ class SysStatus(object):
             }, fields))
         }
 
+    @property
+    def topic(self):
+        return {
+            "name": self.__topic_name,
+            "template": "sysstatus",
+            "type_str": "topic",
+        }
+
     @property
     def data(self):
         data = {
diff --git a/fdbk/reporter.py b/fdbk/reporter.py
index 0e4fe3e..d55fc2d 100644
--- a/fdbk/reporter.py
+++ b/fdbk/reporter.py
@@ -160,11 +160,17 @@ class Reporter:
         if not self._data_source:
             raise ValueError('Cannot create new topic without data source')
 
-        topic_d = self._data_source.topic
+        try:
+            template_d = self._data_source.template
+            template_id = self._client.add_topic(**template_d)
+            self._print(created_topic(template_d, template_id))
+        except (AttributeError, KeyError, RuntimeError):
+            pass
 
-        self._print(f"Creating topic '{topic_d['name']}' to fdbk")
+        topic_d = self._data_source.topic
 
         self._topic_id = self._client.add_topic(**topic_d)
+        self._print(created_topic(topic_d, self._topic_id))
 
     def _print(self, *args, **kwargs):
         if self._verbose:
diff --git a/fdbk/utils/messages.py b/fdbk/utils/messages.py
index 4699ceb..c756b1d 100644
--- a/fdbk/utils/messages.py
+++ b/fdbk/utils/messages.py
@@ -1,11 +1,18 @@
 def _topic_str(topic_d):
-    return f'{topic_d["name"]} ({topic_d["id"]})'
+    type_ = topic_d.get("type") or topic_d.get("type_str", "topic")
+    type_str = f', {type_}' if type_ != 'topic' else ''
+    return f'{topic_d["name"]} ({topic_d["id"]}{type_str})'
 
 
 def created_connection(plugin, parameters):
     return (
-        f"Created fdbk DB connection of type '{plugin}' with parameters"
-        f"{str(parameters)}")
+        f"Created fdbk DB connection of type '{plugin}' with parameters "
+        f"{str(parameters)}.")
+
+
+def created_topic(topic_d, id_):
+    topic_d = {**topic_d, "id": id_}
+    return (f"Created topic '{_topic_str(topic_d)}' to the database.")
 
 
 def collection_name_is_undefined(method, field):


Test patch:
diff --git a/tst/test_reporter.py b/tst/test_reporter.py
index 1d94b12..7386ef9 100644
--- a/tst/test_reporter.py
+++ b/tst/test_reporter.py
@@ -1,4 +1,5 @@
 from datetime import datetime
+from random import randrange
 from unittest import TestCase
 from unittest.mock import Mock, patch
 
@@ -7,7 +8,7 @@ from freezegun import freeze_time
 from fdbk import Reporter, DictConnection
 from fdbk.reporter import _add, _div, _obj_add, _Data
 
-class TestDataSource(object):
+class TestDataSource:
     def __init__(self, pattern, n):
         self._pattern = pattern
         self._i = 0
@@ -37,6 +38,29 @@ class TestDataSource(object):
             "number": num
         }
 
+class TestDataSourceWithTemplate:
+    @property
+    def template(self):
+        return {
+            "name": "random",
+            "description": "Random numbers from randrange(10)",
+            "type_str": "template",
+            "fields": [ "number" ]
+        }
+
+    @property
+    def topic(self):
+        return {
+            "name": "topic",
+            "template": "random",
+        }
+
+    @property
+    def data(self):
+        return {
+            "number": randrange(10)
+        }
+
 class ReporterUtilsTest(TestCase):
     def test_add_and_div_returns_none_on_error(self):
         self.assertIsNone(_add(1, None))
@@ -86,6 +110,16 @@ class ReporterTest(TestCase):
         self.assertEqual(C.get_topic(R.topic_id)["name"], "topic")
         self.assertEqual(C.get_topic(R.topic_id)["fields"], ["number"])
 
+    def test_creates_template_on_init(self):
+        DS = TestDataSourceWithTemplate()
+        R = Reporter(DS, db_plugin='DictConnection')
+        C = R.connection
+        self.assertEqual(len(C.get_topics()), 2)
+        self.assertEqual(C.get_topics()[0]["type"], "template")
+
+        R = Reporter(DS, db_connection=C)
+        self.assertEqual(len(C.get_topics()), 3)
+
     def test_can_use_existing_connection(self):
         DS = TestDataSource([1,2,3], 3)
         C = DictConnection()