Repository: rfverbruggen/rachiopy

Issue:
notification.add and notification.update json errors
Calling` notification.add` and `notification.update` methods producing errors as shown below. 

`({'date': 'Tue, 06 Oct 2020 15:41:09 GMT', 'content-type': 'application/json;charset=utf-8', 'content-length': '226', 'connection': 'keep-alive', 'server': 'nginx/1.12.1', 'cache-control': 'no-cache, no-store, max-age=0, must-revalidate', 'pragma': 'no-cache', 'expires': '0', 'x-xss-protection': '1; mode=block', 'x-frame-options': 'DENY', 'x-content-type-options': 'nosniff', 'x-ratelimit-limit': '1700', 'x-ratelimit-remaining': '1584', 'x-ratelimit-reset': '2020-10-07T00:00:00Z', 'status': 500}, {'code': '400', 'error': "Unrecognized token 'device': was expecting ('true', 'false' or 'null')\n at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream@1a01b38c; line: 1, column: 8]"})`

Also see comment: https://github.com/rfverbruggen/rachiopy/pull/30#issuecomment-704447783

Discussion:

Patch:
diff --git a/.hound.yml b/.hound.yml
index 9d60897..e21dcf5 100644
--- a/.hound.yml
+++ b/.hound.yml
@@ -1,4 +1,4 @@
-fail_on_violations: true
+fail_on_violations: false
 
 python:
   enabled: true
\ No newline at end of file
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..77c6b36
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,2 @@
+[MESSAGES CONTROL]
+disable=R0801
\ No newline at end of file
diff --git a/rachiopy/rachioobject.py b/rachiopy/rachioobject.py
index b38cc70..bb3bc22 100644
--- a/rachiopy/rachioobject.py
+++ b/rachiopy/rachioobject.py
@@ -1,4 +1,5 @@
 """RachioObject module containing a helper class for all API calls."""
+import json
 
 from requests import Session
 
@@ -33,6 +34,9 @@ class RachioObject:
             object if it contains JSON).
         :rtype: tuple
         """
+        if body is not None:
+            body = json.dumps(body)
+
         url = f"{_API_URL}/{path}"
         response = self._http_session.request(
             method, url, headers=self._headers, data=body
diff --git a/setup.py b/setup.py
index 426450c..4884b10 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,10 @@
 """Rachiopy setup script."""
 from setuptools import find_packages, setup
+from datetime import datetime
 
-VERSION = "1.0.2"
+NOW = datetime.now().strftime("%m%d%Y%H%M%S")
+
+VERSION = f"1.0.2-dev{NOW}"
 
 GITHUB_USERNAME = "rfverbruggen"
 GITHUB_REPOSITORY = "rachiopy"


Test patch:
diff --git a/tests/test_device.py b/tests/test_device.py
index 2a46743..53205c9 100644
--- a/tests/test_device.py
+++ b/tests/test_device.py
@@ -2,6 +2,7 @@
 
 import unittest
 import uuid
+import json
 from unittest.mock import patch
 from random import randrange
 
@@ -24,7 +25,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the get method works as expected."""
         mock.return_value = RESPONSE200
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.get(deviceid)
 
@@ -42,7 +43,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the current schedule method works as expected."""
         mock.return_value = RESPONSE200
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.current_schedule(deviceid)
 
@@ -60,7 +61,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the event method works as expected."""
         mock.return_value = RESPONSE200
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
         starttime = 1414818000000
         endtime = 1415739608103
 
@@ -84,7 +85,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the forecast method works as expected."""
         mock.return_value = RESPONSE200
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.forecast(deviceid)
 
@@ -128,7 +129,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the stop water method works as expected."""
         mock.return_value = RESPONSE204
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.stop_water(deviceid)
 
@@ -139,14 +140,14 @@ class TestDeviceMethods(unittest.TestCase):
             args[1], f"{BASE_API_URL}/device/stop_water",
         )
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": deviceid})
+        self.assertEqual(kwargs["data"], json.dumps({"id": deviceid}))
 
     @patch("requests.Session.request")
     def test_rain_delay(self, mock):
         """Test if the rain delay method works as expected."""
         mock.return_value = RESPONSE204
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
         duration = randrange(604800)
 
         self.device.rain_delay(deviceid, duration)
@@ -159,7 +160,7 @@ class TestDeviceMethods(unittest.TestCase):
         )
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
-            kwargs["data"], {"id": deviceid, "duration": duration}
+            kwargs["data"], json.dumps({"id": deviceid, "duration": duration})
         )
 
         # Check that values should be within range.
@@ -173,7 +174,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the turn on method works as expected."""
         mock.return_value = RESPONSE204
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.turn_on(deviceid)
 
@@ -184,14 +185,14 @@ class TestDeviceMethods(unittest.TestCase):
             args[1], f"{BASE_API_URL}/device/on",
         )
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": deviceid})
+        self.assertEqual(kwargs["data"], json.dumps({"id": deviceid}))
 
     @patch("requests.Session.request")
     def test_turn_off(self, mock):
         """Test if the turn off method works as expected."""
         mock.return_value = RESPONSE204
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.turn_off(deviceid)
 
@@ -202,14 +203,14 @@ class TestDeviceMethods(unittest.TestCase):
             args[1], f"{BASE_API_URL}/device/off",
         )
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": deviceid})
+        self.assertEqual(kwargs["data"], json.dumps({"id": deviceid}))
 
     @patch("requests.Session.request")
     def test_pause_zone_run(self, mock):
         """Test if the pause zone run method works as expected."""
         mock.return_value = RESPONSE204
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
         duration = randrange(3600)
 
         self.device.pause_zone_run(deviceid, duration)
@@ -222,7 +223,7 @@ class TestDeviceMethods(unittest.TestCase):
         )
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
-            kwargs["data"], {"id": deviceid, "duration": duration}
+            kwargs["data"], json.dumps({"id": deviceid, "duration": duration})
         )
 
         # Check that values should be within range.
@@ -238,7 +239,7 @@ class TestDeviceMethods(unittest.TestCase):
         """Test if the resume zone run method works as expected."""
         mock.return_value = RESPONSE204
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.device.resume_zone_run(deviceid)
 
@@ -249,4 +250,4 @@ class TestDeviceMethods(unittest.TestCase):
             args[1], f"{BASE_API_URL}/device/resume_zone_run",
         )
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": deviceid})
+        self.assertEqual(kwargs["data"], json.dumps({"id": deviceid}))
diff --git a/tests/test_notification.py b/tests/test_notification.py
index 4a1dfb3..d212aca 100644
--- a/tests/test_notification.py
+++ b/tests/test_notification.py
@@ -3,6 +3,7 @@
 import unittest
 from unittest.mock import patch
 import uuid
+import json
 
 from rachiopy import Notification
 from tests.constants import BASE_API_URL, AUTHTOKEN, RESPONSE200, RESPONSE204
@@ -39,7 +40,7 @@ class TestNotificationMethods(unittest.TestCase):
         """Test if the get device webhook method works as expected."""
         mock.return_value = RESPONSE200
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
 
         self.notification.get_device_webhook(deviceid)
 
@@ -57,7 +58,7 @@ class TestNotificationMethods(unittest.TestCase):
         """Test if the add method works as expected."""
         mock.return_value = RESPONSE200
 
-        deviceid = uuid.uuid4()
+        deviceid = str(uuid.uuid4())
         externalid = "Test ID"
         url = "https://www.mydomain.com/another_webhook_new_url"
         eventtypes = [{"id": "1"}, {"id": "2"}]
@@ -71,12 +72,14 @@ class TestNotificationMethods(unittest.TestCase):
         self.assertEqual(args[0], "POST")
         self.assertEqual(
             kwargs["data"],
-            {
-                "device": {"id": deviceid},
-                "externalId": externalid,
-                "url": url,
-                "eventTypes": eventtypes,
-            },
+            json.dumps(
+                {
+                    "device": {"id": deviceid},
+                    "externalId": externalid,
+                    "url": url,
+                    "eventTypes": eventtypes,
+                }
+            ),
         )
 
     @patch("requests.Session.request")
@@ -84,7 +87,7 @@ class TestNotificationMethods(unittest.TestCase):
         """Test if the update method works as expected."""
         mock.return_value = RESPONSE200
 
-        hookid = uuid.uuid4()
+        hookid = str(uuid.uuid4())
         externalid = "Test ID"
         url = "https://www.mydomain.com/another_webhook_new_url"
         eventtypes = [{"id": "1"}, {"id": "2"}]
@@ -98,12 +101,14 @@ class TestNotificationMethods(unittest.TestCase):
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
             kwargs["data"],
-            {
-                "id": hookid,
-                "externalId": externalid,
-                "url": url,
-                "eventTypes": eventtypes,
-            },
+            json.dumps(
+                {
+                    "id": hookid,
+                    "externalId": externalid,
+                    "url": url,
+                    "eventTypes": eventtypes,
+                }
+            ),
         )
 
     @patch("requests.Session.request")
@@ -111,7 +116,7 @@ class TestNotificationMethods(unittest.TestCase):
         """Test if the delete method works as expected."""
         mock.return_value = RESPONSE204
 
-        hookid = uuid.uuid4()
+        hookid = str(uuid.uuid4())
 
         self.notification.delete(hookid)
 
@@ -129,7 +134,7 @@ class TestNotificationMethods(unittest.TestCase):
         """Test if the get method works as expected."""
         mock.return_value = RESPONSE200
 
-        hookid = uuid.uuid4()
+        hookid = str(uuid.uuid4())
 
         self.notification.get(hookid)
 
diff --git a/tests/test_schedulerule.py b/tests/test_schedulerule.py
index 84a82fa..b56a727 100644
--- a/tests/test_schedulerule.py
+++ b/tests/test_schedulerule.py
@@ -3,6 +3,7 @@
 import unittest
 from unittest.mock import patch
 import uuid
+import json
 
 from rachiopy import Schedulerule
 from tests.constants import BASE_API_URL, AUTHTOKEN, RESPONSE200, RESPONSE204
@@ -23,7 +24,7 @@ class TestScheduleRuleMethods(unittest.TestCase):
         """Test if the get method works as expected."""
         mock.return_value = RESPONSE200
 
-        scheduleruleid = uuid.uuid4()
+        scheduleruleid = str(uuid.uuid4())
 
         self.schedulerule.get(scheduleruleid)
 
@@ -41,7 +42,7 @@ class TestScheduleRuleMethods(unittest.TestCase):
         """Test if the skip method works as expected."""
         mock.return_value = RESPONSE204
 
-        scheduleruleid = uuid.uuid4()
+        scheduleruleid = str(uuid.uuid4())
 
         self.schedulerule.skip(scheduleruleid)
 
@@ -50,14 +51,14 @@ class TestScheduleRuleMethods(unittest.TestCase):
         # Check that the mock function is called with the rights args.
         self.assertEqual(args[1], f"{BASE_API_URL}/schedulerule/skip")
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": scheduleruleid})
+        self.assertEqual(kwargs["data"], json.dumps({"id": scheduleruleid}))
 
     @patch("requests.Session.request")
     def test_start(self, mock):
         """Test if the start method works as expected."""
         mock.return_value = RESPONSE204
 
-        scheduleruleid = uuid.uuid4()
+        scheduleruleid = str(uuid.uuid4())
 
         self.schedulerule.start(scheduleruleid)
 
@@ -66,14 +67,14 @@ class TestScheduleRuleMethods(unittest.TestCase):
         # Check that the mock function is called with the rights args.
         self.assertEqual(args[1], f"{BASE_API_URL}/schedulerule/start")
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": scheduleruleid})
+        self.assertEqual(kwargs["data"], json.dumps({"id": scheduleruleid}))
 
     @patch("requests.Session.request")
     def test_seasonal_adjustment(self, mock):
         """Test if the seasonal adjustment method works as expected."""
         mock.return_value = RESPONSE200
 
-        scheduleruleid = uuid.uuid4()
+        scheduleruleid = str(uuid.uuid4())
         adjustment = 0.2
 
         self.schedulerule.seasonal_adjustment(scheduleruleid, adjustment)
@@ -86,5 +87,6 @@ class TestScheduleRuleMethods(unittest.TestCase):
         )
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
-            kwargs["data"], {"id": scheduleruleid, "adjustment": adjustment}
+            kwargs["data"],
+            json.dumps({"id": scheduleruleid, "adjustment": adjustment}),
         )
diff --git a/tests/test_zone.py b/tests/test_zone.py
index b024db4..a634fa0 100644
--- a/tests/test_zone.py
+++ b/tests/test_zone.py
@@ -4,6 +4,7 @@ import unittest
 from unittest.mock import patch
 import uuid
 import random
+import json
 
 from random import randrange
 from rachiopy import Zone
@@ -17,9 +18,9 @@ class TestZoneMethods(unittest.TestCase):
     def setUp(self):
         self.zone = Zone(AUTHTOKEN)
 
-        zone1id = uuid.uuid4()
-        zone2id = uuid.uuid4()
-        zone3id = uuid.uuid4()
+        zone1id = str(uuid.uuid4())
+        zone2id = str(uuid.uuid4())
+        zone3id = str(uuid.uuid4())
         duration1 = randrange(10800)
         duration2 = randrange(10800)
         duration3 = randrange(10800)
@@ -54,7 +55,7 @@ class TestZoneMethods(unittest.TestCase):
         """Test if the start method works as expected."""
         mock.return_value = RESPONSE204
 
-        zoneid = uuid.uuid4()
+        zoneid = str(uuid.uuid4())
         duration = randrange(10800)
 
         self.zone.start(zoneid, duration)
@@ -64,7 +65,9 @@ class TestZoneMethods(unittest.TestCase):
         # Check that the mock function is called with the rights args.
         self.assertEqual(args[1], f"{BASE_API_URL}/zone/start")
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": zoneid, "duration": duration})
+        self.assertEqual(
+            kwargs["data"], json.dumps({"id": zoneid, "duration": duration})
+        )
 
         # Check that values should be within range.
         self.assertRaises(AssertionError, self.zone.start, zoneid, -1)
@@ -87,12 +90,18 @@ class TestZoneMethods(unittest.TestCase):
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
             kwargs["data"],
-            {
-                "zones": [
-                    {"id": data[0], "duration": data[1], "sortOrder": count}
-                    for (count, data) in enumerate(self.zones, 1)
-                ]
-            },
+            json.dumps(
+                {
+                    "zones": [
+                        {
+                            "id": data[0],
+                            "duration": data[1],
+                            "sortOrder": count,
+                        }
+                        for (count, data) in enumerate(self.zones, 1)
+                    ]
+                }
+            ),
         )
 
     @patch("requests.Session.request")
@@ -100,7 +109,7 @@ class TestZoneMethods(unittest.TestCase):
         """Test if the set moisture percent method works as expected."""
         mock.return_value = RESPONSE204
 
-        zoneid = uuid.uuid4()
+        zoneid = str(uuid.uuid4())
         percent = round(random.random(), 1)
 
         self.zone.set_moisture_percent(zoneid, percent)
@@ -110,7 +119,9 @@ class TestZoneMethods(unittest.TestCase):
         # Check that the mock function is called with the rights args.
         self.assertEqual(args[1], f"{BASE_API_URL}/zone/setMoisturePercent")
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": zoneid, "percent": percent})
+        self.assertEqual(
+            kwargs["data"], json.dumps({"id": zoneid, "percent": percent})
+        )
 
         # Check that values should be within range.
         self.assertRaises(
@@ -125,7 +136,7 @@ class TestZoneMethods(unittest.TestCase):
         """Test if the set moisture level method works as expected."""
         mock.return_value = RESPONSE204
 
-        zoneid = uuid.uuid4()
+        zoneid = str(uuid.uuid4())
         level = round(random.uniform(0.0, 100.0), 2)
 
         self.zone.set_moisture_level(zoneid, level)
@@ -135,7 +146,9 @@ class TestZoneMethods(unittest.TestCase):
         # Check that the mock function is called with the rights args.
         self.assertEqual(args[1], f"{BASE_API_URL}/zone/setMoistureLevel")
         self.assertEqual(args[0], "PUT")
-        self.assertEqual(kwargs["data"], {"id": zoneid, "level": level})
+        self.assertEqual(
+            kwargs["data"], json.dumps({"id": zoneid, "level": level})
+        )
 
     @patch("requests.Session.request")
     def test_zoneschedule(self, mock):
@@ -154,12 +167,18 @@ class TestZoneMethods(unittest.TestCase):
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
             kwargs["data"],
-            {
-                "zones": [
-                    {"id": data[0], "duration": data[1], "sortOrder": count}
-                    for (count, data) in enumerate(self.zones, 1)
-                ]
-            },
+            json.dumps(
+                {
+                    "zones": [
+                        {
+                            "id": data[0],
+                            "duration": data[1],
+                            "sortOrder": count,
+                        }
+                        for (count, data) in enumerate(self.zones, 1)
+                    ]
+                }
+            ),
         )
 
     @patch("requests.Session.request")
@@ -178,10 +197,16 @@ class TestZoneMethods(unittest.TestCase):
         self.assertEqual(args[0], "PUT")
         self.assertEqual(
             kwargs["data"],
-            {
-                "zones": [
-                    {"id": data[0], "duration": data[1], "sortOrder": count}
-                    for (count, data) in enumerate(self.zones, 1)
-                ]
-            },
+            json.dumps(
+                {
+                    "zones": [
+                        {
+                            "id": data[0],
+                            "duration": data[1],
+                            "sortOrder": count,
+                        }
+                        for (count, data) in enumerate(self.zones, 1)
+                    ]
+                }
+            ),
         )