From e37f62422cf53886d1fc5c8de3323aa5d8739c39 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Wed, 22 Nov 2023 11:34:06 -0500 Subject: [PATCH 1/9] Update tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6dd1052..0bb7914 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean, py36, py37, py38, py39, pylint, flake8, pydocstyle, stats +envlist = clean, py37, py38, py39, pylint, flake8, pydocstyle, stats ignore_basepython_conflict = true [testenv:clean] From 6aa84b3f76ae895947d2046acef56632a8d1f132 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Wed, 22 Nov 2023 11:36:33 -0500 Subject: [PATCH 2/9] Update test.yml --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9d06c5..7e46ba7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -24,4 +24,4 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements_test.txt ]; then pip install -r requirements_test.txt; fi - name: Run unittests - run: python -m unittest discover -v tests \ No newline at end of file + run: python -m unittest discover -v tests From 39b98c970eef18739af51bdaae4171a006314e76 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sun, 10 Dec 2023 18:36:38 +0000 Subject: [PATCH 3/9] add valve support --- rachiopy/__init__.py | 6 ++ rachiopy/program.py | 86 ++++++++++++++++++++ rachiopy/rachioobject.py | 72 +++++++++++++++++ rachiopy/summary.py | 34 ++++++++ rachiopy/valve.py | 140 +++++++++++++++++++++++++++++++++ tests/constants.py | 1 + tests/test_program.py | 100 ++++++++++++++++++++++++ tests/test_rachio.py | 3 + tests/test_summary.py | 47 +++++++++++ tests/test_valve.py | 165 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 654 insertions(+) create mode 100644 rachiopy/program.py create mode 100644 rachiopy/summary.py create mode 100644 rachiopy/valve.py create mode 100644 tests/test_program.py create mode 100644 tests/test_summary.py create mode 100644 tests/test_valve.py diff --git a/rachiopy/__init__.py b/rachiopy/__init__.py index e5fc760..2e3f311 100644 --- a/rachiopy/__init__.py +++ b/rachiopy/__init__.py @@ -7,6 +7,9 @@ from rachiopy.notification import Notification from rachiopy.schedulerule import Schedulerule from rachiopy.zone import Zone +from rachiopy.valve import Valve +from rachiopy.summary import SummaryServce +from rachiopy.program import Program class Rachio(RachioObject): @@ -25,3 +28,6 @@ def __init__(self, authtoken: str): self.notification = Notification(authtoken) self.schedulerule = Schedulerule(authtoken) self.zone = Zone(authtoken) + self.valve = Valve(authtoken) + self.summary = SummaryServce(authtoken) + self.program = Program(authtoken) diff --git a/rachiopy/program.py b/rachiopy/program.py new file mode 100644 index 0000000..ecdac26 --- /dev/null +++ b/rachiopy/program.py @@ -0,0 +1,86 @@ +"""Program module for the smart hose timer.""" + +from rachiopy.rachioobject import RachioObject + + +class Program(RachioObject): + """Program class for the smart hose timer.""" + + def list_programs(self, valve_id: str): + """Retreive the list of programs (schedules) for a valve.""" + + """For more info of the content in the response see: + https://rachio.readme.io/docs/programservice_listprograms + + :param valve_id: Valve's unique id + :type valve_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body. + :rtype: tuple + """ + path = f"program/listPrograms/{valve_id}" + return self.valve_get_request(path) + + def get_program(self, program_id: str): + """Retreive the information for a specific program. + + For more info of the content in the response see: + https://rachio.readme.io/docs/programservice_getprogram + + :param program_id: Program's unique id + :type program_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + path = f"program/getProgram/{program_id}" + return self.valve_get_request(path) + + def create_skip_overrides(self, program_id: str, timestamp: str): + """Create manual skips for the specific program run time. + You can retrieve the runtimes from SummaryService.getValveDayViews + + For more info of the content in the response see: + https://rachio.readme.io/docs/programservice_createskipoverrides + + :param program_id: Program's unique id + :type program_id: str + + :param timestamp: Timestamp of the run to skip + :type timestamp: timestamp + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + payload = {"programId": program_id, "timestamp": timestamp} + return self.valve_post_request("program/createSkipOverrides", payload) + + def delete_skip_overrides(self, program_id: str, timestamp: str): + """Cancel program skips for the specified program run time. + You can retrieve upcoming skips from SummaryService.getValveDayViews + + For more info of the content in the response see: + https://rachio.readme.io/docs/programservice_deleteskipoverrides + + :param program_id: Program's unique id + :type program_id: str + + :param timestamp: Timestamp of the run skip to delete + :type timestamp: timestamp + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + payload = {"programId": program_id, "timestamp": timestamp} + return self.valve_post_request("program/deleteSkipOverrides", payload) diff --git a/rachiopy/rachioobject.py b/rachiopy/rachioobject.py index f519908..11e4122 100644 --- a/rachiopy/rachioobject.py +++ b/rachiopy/rachioobject.py @@ -4,6 +4,7 @@ from requests import Session _API_URL = "https://api.rach.io/1/public" +_VALVE_URL = "https://cloud-rest.rach.io" class RachioObject: @@ -100,3 +101,74 @@ def delete_request(self, path: str, body=None): :rtype: tuple """ return self._request(path, "DELETE", body) + + def _valve_request(self, path: str, method: str, body=None): + """Make a request from the API. + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + + if body is not None: + body = json.dumps(body) + + url = f"{_VALVE_URL}/{path}" + response = self._http_session.request( + method, url, headers=self._headers, data=body, timeout=self.timeout + ) + + content_type = response.headers.get("content-type") + headers = {k.lower(): v for k, v in response.headers.items()} + headers["status"] = response.status_code + + if content_type and content_type.startswith("application/json"): + return headers, response.json() + + return headers, response.text + + def valve_get_request(self, path: str, body=None): + """Make a GET request to the valve API. + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + return self._valve_request(path, "GET", body) + + def valve_put_request(self, path: str, body=None): + """Make a PUT request to the valve API. + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + return self._valve_request(path, "PUT", body) + + def valve_post_request(self, path: str, body=None): + """Make a POST request to the valve API. + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + return self._valve_request(path, "POST", body) + + def valve_delete_request(self, path: str, body=None): + """Make a DELETE request to the valve API. + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + return self._valve_request(path, "DELETE", body) diff --git a/rachiopy/summary.py b/rachiopy/summary.py new file mode 100644 index 0000000..66805a6 --- /dev/null +++ b/rachiopy/summary.py @@ -0,0 +1,34 @@ +"""Smart Hose Timer scheudle summary calls.""" + +from rachiopy.rachioobject import RachioObject + + +class SummaryServce(RachioObject): + """Scheudle summary class.""" + + def get_valve_day_views(self, base_id: str, start, end): + """List historical and upcoming valve runs and skips. + + For more info of the content in the response see: + https://rachio.readme.io/docs/summaryservice_getvalvedayviews + + :param base_id: Base's unique id + :type dev_id: str + + :param start: Start date + :type start: Object[] + + :param end: End date + :type end: Object[] + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body. + :rtype: tuple + """ + payload = { + "resourceId": {"baseStationId": base_id}, + "start": start, + "end": end, + } + return self.valve_post_request("summary/getValveDayViews", payload) diff --git a/rachiopy/valve.py b/rachiopy/valve.py new file mode 100644 index 0000000..383999c --- /dev/null +++ b/rachiopy/valve.py @@ -0,0 +1,140 @@ +"""Valve Service.""" + +from rachiopy.rachioobject import RachioObject + + +class Valve(RachioObject): + """Valve class for smart hose timer.""" + + def get_base_station(self, base_id: str): + """Retreive the information for a specific base station. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_getbasestation + + :param base_id: Base station's unique id + :type user_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + path = f"valve/getBaseStation/{base_id}" + return self.valve_get_request(path) + + def get_valve(self, valve_id: str): + """Retrieve the information for a specific smart valve. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_getvalve + + :param valve_id: Valve's unique id + :type user_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + path = f"valve/getValve/{valve_id}" + return self.valve_get_request(path) + + def list_base_stations(self, user_id: str): + """Retrieve all base stations for a given user ID. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_listbasestations + + :param user_id: Person's unique id + :type user_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + path = f"valve/listBaseStations/{user_id}" + return self.valve_get_request(path) + + def list_valves(self, base_id: str): + """Retreive all valves on a given base station. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_listvalves + + :param base_id: Base station's unique id + :type user_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + path = f"valve/listValves/{base_id}" + return self.valve_get_request(path) + + def set_default_runtime(self, valve_id: str, duration: int): + """Set the runtime for a valve when the button is pressed. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_setdefaultruntime + + :param valve_id: Valve's unique id + :type user_id: str + + :param duration: Duration in seconds + :type duration: int + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + payload = {"valveId": valve_id, "defaultRuntimeSeconds": duration} + return self.valve_put_request("valve/setDefaultRuntime", payload) + + def start_watering(self, valve_id: str, duration: int): + """Start a valve. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_startwatering + + :param valve_id: Valve's unique id + :type user_id: str + + :param duration: Duration in seconds + :type duration: int + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + assert 0 <= duration <= 86400, "duration must be in range 0-86400" + payload = {"valveId": valve_id, "durationSeconds": duration} + return self.valve_put_request("valve/startWatering", payload) + + def stop_watering(self, valve_id: str): + """Stop a valve. + + For more info of the content in the response see: + https://rachio.readme.io/docs/valveservice_stopwatering + + :param valve_id: Valve's unique id + :type user_id: str + + :return: The return value is a tuple of (response, content), the first + being and instance of the httplib2.Response class, the second + being a string that contains the response entity body (Python + object if it contains JSON). + :rtype: tuple + """ + payload = {"valveId": valve_id} + return self.valve_put_request("valve/stopWatering", payload) diff --git a/tests/constants.py b/tests/constants.py index eec3442..c7b2a9c 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -5,6 +5,7 @@ from requests import Response BASE_API_URL = "https://api.rach.io/1/public" +VALVE_API_URL = "https://cloud-rest.rach.io" AUTHTOKEN = "1c1d9f3d-39c9-42b1-abc0-066f5a05cdef" diff --git a/tests/test_program.py b/tests/test_program.py new file mode 100644 index 0000000..7728689 --- /dev/null +++ b/tests/test_program.py @@ -0,0 +1,100 @@ +"""Program object test module""" + +import unittest +import uuid +import json +from unittest.mock import patch + +from rachiopy import Program +from tests.constants import VALVE_API_URL, AUTHTOKEN, RESPONSE200 + + +class TestProgramMethods(unittest.TestCase): + """Class containing the Program object tests.""" + + def setUp(self): + self.program = Program(AUTHTOKEN) + + def test_init(self): + """Test if the constructor works as expected.""" + self.assertEqual(self.program.authtoken, AUTHTOKEN) + + @patch("requests.Session.request") + def test_list_programs(self, mock): + """Test if the list programs method works as expected.""" + mock.return_value = RESPONSE200 + + valveid = str(uuid.uuid4()) + + self.program.list_programs(valveid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual( + args[1], f"{VALVE_API_URL}/program/listPrograms/{valveid}" + ) + self.assertEqual(args[0], "GET") + self.assertEqual(kwargs["data"], None) + + @patch("requests.Session.request") + def test_get_program(self, mock): + """Test if the get program method works as expected.""" + mock.return_value = RESPONSE200 + + programid = str(uuid.uuid4()) + + self.program.get_program(programid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual( + args[1], f"{VALVE_API_URL}/program/getProgram/{programid}" + ) + self.assertEqual(args[0], "GET") + self.assertEqual(kwargs["data"], None) + + @patch("requests.Session.request") + def test_create_skip_overrides(self, mock): + """Test if the create skip overrides method works as expected.""" + mock.return_value = RESPONSE200 + + programid = str(uuid.uuid4()) + timestamp = 1414818000000 + + self.program.create_skip_overrides(programid, timestamp) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual( + args[1], f"{VALVE_API_URL}/program/createSkipOverrides" + ) + self.assertEqual(args[0], "POST") + self.assertEqual( + kwargs["data"], + json.dumps({"programId": programid, "timestamp": timestamp}) + ) + + @patch("requests.Session.request") + def test_delete_skip_overrides(self, mock): + """Test if the delete skip overrides method works as expected.""" + mock.return_value = RESPONSE200 + + programid = str(uuid.uuid4()) + timestamp = 1414818000000 + + self.program.delete_skip_overrides(programid, timestamp) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual( + args[1], f"{VALVE_API_URL}/program/deleteSkipOverrides" + ) + self.assertEqual(args[0], "POST") + self.assertEqual( + kwargs["data"], + json.dumps({"programId": programid, "timestamp": timestamp}) + ) diff --git a/tests/test_rachio.py b/tests/test_rachio.py index cb6fac8..7140614 100644 --- a/tests/test_rachio.py +++ b/tests/test_rachio.py @@ -19,3 +19,6 @@ def test_init(self): self.assertEqual(rachio.schedulerule.authtoken, AUTHTOKEN) self.assertEqual(rachio.flexschedulerule.authtoken, AUTHTOKEN) self.assertEqual(rachio.notification.authtoken, AUTHTOKEN) + self.assertEqual(rachio.valve.authtoken, AUTHTOKEN) + self.assertEqual(rachio.summary.authtoken, AUTHTOKEN) + self.assertEqual(rachio.program.authtoken, AUTHTOKEN) diff --git a/tests/test_summary.py b/tests/test_summary.py new file mode 100644 index 0000000..979c359 --- /dev/null +++ b/tests/test_summary.py @@ -0,0 +1,47 @@ +"""Summary object test module""" + +import unittest +import uuid +import json +from unittest.mock import patch + +from rachiopy import SummaryServce +from tests.constants import VALVE_API_URL, AUTHTOKEN, RESPONSE200 + + +class TestSummaryMethod(unittest.TestCase): + """Class containing the Summary object test.""" + + def setUp(self): + self.summary = SummaryServce(AUTHTOKEN) + + def test_init(self): + """Test if the constructor works as expected.""" + self.assertEqual(self.summary.authtoken, AUTHTOKEN) + + @patch("requests.Session.request") + def test_get_valve_day_views(self, mock): + """Test if the get day views method works as expected.""" + mock.return_value = RESPONSE200 + + deviceid = str(uuid.uuid4()) + start = {"year": 2023, "month": 1, "day": 1} + end = {"year": 2023, "month": 1, "day": 30} + + self.summary.get_valve_day_views(deviceid, start, end) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual(args[1], f"{VALVE_API_URL}/summary/getValveDayViews") + self.assertEqual(args[0], "POST") + self.assertEqual( + kwargs["data"], + json.dumps( + { + "resourceId": {"baseStationId": deviceid}, + "start": start, + "end": end + } + ) + ) diff --git a/tests/test_valve.py b/tests/test_valve.py new file mode 100644 index 0000000..8f4a6c2 --- /dev/null +++ b/tests/test_valve.py @@ -0,0 +1,165 @@ +"""Valve object test module""" + +import unittest +from unittest.mock import patch +import uuid +import json + +from random import randrange +from rachiopy import Valve +from tests.constants import VALVE_API_URL, AUTHTOKEN, RESPONSE200, RESPONSE204 + + +class TestValveMethods(unittest.TestCase): + """Class containing the Valve object test cases.""" + + def setUp(self): + self.valve = Valve(AUTHTOKEN) + + def test_init(self): + """Test if the constructor works as expected.""" + self.assertEqual(self.valve.authtoken, AUTHTOKEN) + + @patch("requests.Session.request") + def test_get_valve(self, mock): + """Test if the get_valve method works as expected.""" + mock.return_value = RESPONSE200 + + valveid = uuid.uuid4() + + self.valve.get_valve(valveid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual(args[1], f"{VALVE_API_URL}/valve/getValve/{valveid}") + self.assertEqual(args[0], "GET") + self.assertEqual(kwargs["data"], None) + + @patch("requests.Session.request") + def test_get_base_station(self, mock): + """Test if the get_base_station method works as expected.""" + mock.return_value = RESPONSE200 + + baseid = str(uuid.uuid4()) + + self.valve.get_base_station(baseid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual( + args[1], + f"{VALVE_API_URL}/valve/getBaseStation/{baseid}" + ) + self.assertEqual(args[0], "GET") + self.assertEqual(kwargs["data"], None) + + @patch("requests.Session.request") + def test_list_base_stations(self, mock): + """Test if the list_base_stations method works as expected.""" + mock.return_value = RESPONSE200 + + userid = str(uuid.uuid4()) + + self.valve.list_base_stations(userid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual( + args[1], + f"{VALVE_API_URL}/valve/listBaseStations/{userid}" + ) + self.assertEqual(args[0], "GET") + self.assertEqual(kwargs["data"], None) + + @patch("requests.Session.request") + def test_list_valves(self, mock): + """Test if the list_valves method works as expected.""" + mock.return_value = RESPONSE200 + + baseid = str(uuid.uuid4()) + + self.valve.list_valves(baseid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual(args[1], f"{VALVE_API_URL}/valve/listValves/{baseid}") + self.assertEqual(args[0], "GET") + self.assertEqual(kwargs["data"], None) + + @patch("requests.Session.request") + def test_set_default_runtime(self, mock): + """Test if the set_default_runtime method works as expected.""" + mock.return_value = RESPONSE200 + + valveid = str(uuid.uuid4()) + duration = randrange(86400) + + self.valve.set_default_runtime(valveid, duration) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual(args[1], f"{VALVE_API_URL}/valve/setDefaultRuntime") + self.assertEqual(args[0], "PUT") + self.assertEqual( + kwargs["data"], + json.dumps({"valveId": valveid, "defaultRuntimeSeconds": duration}) + ) + + # Check that values should be within range. + self.assertRaises( + AssertionError, self.valve.start_watering, valveid, -1 + ) + self.assertRaises( + AssertionError, self.valve.start_watering, valveid, 86401 + ) + + @patch("requests.Session.request") + def test_start_watering(self, mock): + """Test if the start_watering method works as expected.""" + mock.return_value = RESPONSE204 + + valveid = str(uuid.uuid4()) + duration = randrange(86400) + + self.valve.start_watering(valveid, duration) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual(args[1], f"{VALVE_API_URL}/valve/startWatering") + self.assertEqual(args[0], "PUT") + self.assertEqual( + kwargs["data"], + json.dumps({"valveId": valveid, "durationSeconds": duration}) + ) + + # Check that values should be within range. + self.assertRaises( + AssertionError, self.valve.start_watering, valveid, -1 + ) + self.assertRaises( + AssertionError, self.valve.start_watering, valveid, 86401 + ) + + @patch("requests.Session.request") + def test_stop_watering(self, mock): + """Test if the stop_watering method works as expected.""" + mock.return_value = RESPONSE204 + + valveid = str(uuid.uuid4()) + + self.valve.stop_watering(valveid) + + args, kwargs = mock.call_args + + # Check that the mock function is called with the rights args. + self.assertEqual(args[1], f"{VALVE_API_URL}/valve/stopWatering") + self.assertEqual(args[0], "PUT") + self.assertEqual( + kwargs["data"], json.dumps({"valveId": valveid}) + ) From 5a5cb347797f586de0133cb308c707bab4bcd24f Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sun, 10 Dec 2023 18:42:37 +0000 Subject: [PATCH 4/9] fix program string --- rachiopy/program.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rachiopy/program.py b/rachiopy/program.py index ecdac26..6505195 100644 --- a/rachiopy/program.py +++ b/rachiopy/program.py @@ -7,9 +7,9 @@ class Program(RachioObject): """Program class for the smart hose timer.""" def list_programs(self, valve_id: str): - """Retreive the list of programs (schedules) for a valve.""" + """Retreive the list of programs (schedules) for a valve. - """For more info of the content in the response see: + For more info of the content in the response see: https://rachio.readme.io/docs/programservice_listprograms :param valve_id: Valve's unique id From b89cd3bccddb4565ec8eabfde6acdee4d6178508 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sun, 10 Dec 2023 18:48:37 +0000 Subject: [PATCH 5/9] roll back to what worked try readd python 3.6 Revert "update formatting" This reverts commit 5b4ec911032d96a18590c60d2b75c6b444ad9fdb. update formatting --- .github/workflows/test.yml | 2 +- rachiopy/__init__.py | 1 + tox.ini | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e46ba7..e6e71b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/rachiopy/__init__.py b/rachiopy/__init__.py index 2e3f311..21d0910 100644 --- a/rachiopy/__init__.py +++ b/rachiopy/__init__.py @@ -15,6 +15,7 @@ class Rachio(RachioObject): """Object representing the Rachio API.""" + # pylint: disable=too-many-instance-attributes def __init__(self, authtoken: str): """Initialze the Rachio API wrapper. diff --git a/tox.ini b/tox.ini index 0bb7914..6dd1052 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean, py37, py38, py39, pylint, flake8, pydocstyle, stats +envlist = clean, py36, py37, py38, py39, pylint, flake8, pydocstyle, stats ignore_basepython_conflict = true [testenv:clean] From 02234d1a7817df6628fc3d177388815a847bc0dd Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Fri, 15 Dec 2023 15:44:38 +0000 Subject: [PATCH 6/9] remove python 3.6 --- .github/workflows/test.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6e71b0..7e46ba7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [3.7, 3.8, 3.9] # Steps represent a sequence of tasks that will be executed as part of the job steps: diff --git a/tox.ini b/tox.ini index 6dd1052..0bb7914 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean, py36, py37, py38, py39, pylint, flake8, pydocstyle, stats +envlist = clean, py37, py38, py39, pylint, flake8, pydocstyle, stats ignore_basepython_conflict = true [testenv:clean] From ef1429224e74960f777074f970b03f5645e96a0d Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sun, 17 Dec 2023 14:55:37 +0000 Subject: [PATCH 7/9] try to make black happy --- tests/test_program.py | 12 ++++++------ tests/test_summary.py | 6 +++--- tests/test_valve.py | 28 +++++++++++++--------------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/tests/test_program.py b/tests/test_program.py index 7728689..185e79d 100644 --- a/tests/test_program.py +++ b/tests/test_program.py @@ -33,7 +33,7 @@ def test_list_programs(self, mock): # Check that the mock function is called with the rights args. self.assertEqual( args[1], f"{VALVE_API_URL}/program/listPrograms/{valveid}" - ) + ) self.assertEqual(args[0], "GET") self.assertEqual(kwargs["data"], None) @@ -51,7 +51,7 @@ def test_get_program(self, mock): # Check that the mock function is called with the rights args. self.assertEqual( args[1], f"{VALVE_API_URL}/program/getProgram/{programid}" - ) + ) self.assertEqual(args[0], "GET") self.assertEqual(kwargs["data"], None) @@ -70,11 +70,11 @@ def test_create_skip_overrides(self, mock): # Check that the mock function is called with the rights args. self.assertEqual( args[1], f"{VALVE_API_URL}/program/createSkipOverrides" - ) + ) self.assertEqual(args[0], "POST") self.assertEqual( kwargs["data"], - json.dumps({"programId": programid, "timestamp": timestamp}) + json.dumps({"programId": programid, "timestamp": timestamp}), ) @patch("requests.Session.request") @@ -92,9 +92,9 @@ def test_delete_skip_overrides(self, mock): # Check that the mock function is called with the rights args. self.assertEqual( args[1], f"{VALVE_API_URL}/program/deleteSkipOverrides" - ) + ) self.assertEqual(args[0], "POST") self.assertEqual( kwargs["data"], - json.dumps({"programId": programid, "timestamp": timestamp}) + json.dumps({"programId": programid, "timestamp": timestamp}), ) diff --git a/tests/test_summary.py b/tests/test_summary.py index 979c359..651a0e7 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -41,7 +41,7 @@ def test_get_valve_day_views(self, mock): { "resourceId": {"baseStationId": deviceid}, "start": start, - "end": end + "end": end, } - ) - ) + ), + ) diff --git a/tests/test_valve.py b/tests/test_valve.py index 8f4a6c2..a4e5b65 100644 --- a/tests/test_valve.py +++ b/tests/test_valve.py @@ -49,9 +49,8 @@ def test_get_base_station(self, mock): # Check that the mock function is called with the rights args. self.assertEqual( - args[1], - f"{VALVE_API_URL}/valve/getBaseStation/{baseid}" - ) + args[1], f"{VALVE_API_URL}/valve/getBaseStation/{baseid}" + ) self.assertEqual(args[0], "GET") self.assertEqual(kwargs["data"], None) @@ -68,9 +67,8 @@ def test_list_base_stations(self, mock): # Check that the mock function is called with the rights args. self.assertEqual( - args[1], - f"{VALVE_API_URL}/valve/listBaseStations/{userid}" - ) + args[1], f"{VALVE_API_URL}/valve/listBaseStations/{userid}" + ) self.assertEqual(args[0], "GET") self.assertEqual(kwargs["data"], None) @@ -107,16 +105,18 @@ def test_set_default_runtime(self, mock): self.assertEqual(args[0], "PUT") self.assertEqual( kwargs["data"], - json.dumps({"valveId": valveid, "defaultRuntimeSeconds": duration}) + json.dumps( + {"valveId": valveid, "defaultRuntimeSeconds": duration} + ), ) # Check that values should be within range. self.assertRaises( AssertionError, self.valve.start_watering, valveid, -1 - ) + ) self.assertRaises( AssertionError, self.valve.start_watering, valveid, 86401 - ) + ) @patch("requests.Session.request") def test_start_watering(self, mock): @@ -135,16 +135,16 @@ def test_start_watering(self, mock): self.assertEqual(args[0], "PUT") self.assertEqual( kwargs["data"], - json.dumps({"valveId": valveid, "durationSeconds": duration}) + json.dumps({"valveId": valveid, "durationSeconds": duration}), ) # Check that values should be within range. self.assertRaises( AssertionError, self.valve.start_watering, valveid, -1 - ) + ) self.assertRaises( AssertionError, self.valve.start_watering, valveid, 86401 - ) + ) @patch("requests.Session.request") def test_stop_watering(self, mock): @@ -160,6 +160,4 @@ def test_stop_watering(self, mock): # Check that the mock function is called with the rights args. self.assertEqual(args[1], f"{VALVE_API_URL}/valve/stopWatering") self.assertEqual(args[0], "PUT") - self.assertEqual( - kwargs["data"], json.dumps({"valveId": valveid}) - ) + self.assertEqual(kwargs["data"], json.dumps({"valveId": valveid})) From cf018590e33392c680fe43ef101aef84b965e6fb Mon Sep 17 00:00:00 2001 From: Robbert Verbruggen Date: Sat, 30 Dec 2023 09:32:53 +0100 Subject: [PATCH 8/9] chore: readd removed blank line --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6de657..bd440c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,7 @@ jobs: strategy: matrix: python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it From 04562d325a16664311cbd3b64988d212a803ef45 Mon Sep 17 00:00:00 2001 From: Brian Rogers Date: Sat, 30 Dec 2023 20:33:55 +0000 Subject: [PATCH 9/9] update tests --- rachiopy/rachioobject.py | 4 ++-- tests/test_valve.py | 26 ++++++++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/rachiopy/rachioobject.py b/rachiopy/rachioobject.py index 11e4122..4eec922 100644 --- a/rachiopy/rachioobject.py +++ b/rachiopy/rachioobject.py @@ -33,7 +33,7 @@ def __init__(self, authtoken: str, http_session=None, timeout=25): self.timeout = timeout def _request(self, path: str, method: str, body=None): - """Make a request from the API. + """Make a request to the API. :return: The return value is a tuple of (response, content), the first being and instance of the httplib2.Response class, the second @@ -103,7 +103,7 @@ def delete_request(self, path: str, body=None): return self._request(path, "DELETE", body) def _valve_request(self, path: str, method: str, body=None): - """Make a request from the API. + """Make a request to the API. :return: The return value is a tuple of (response, content), the first being and instance of the httplib2.Response class, the second diff --git a/tests/test_valve.py b/tests/test_valve.py index a4e5b65..d9ab8e7 100644 --- a/tests/test_valve.py +++ b/tests/test_valve.py @@ -110,12 +110,21 @@ def test_set_default_runtime(self, mock): ), ) + @patch("requests.Session.request") + def test_set_default_runtime_exception(self, mock): + """Test if the set_default_runtime method catches incorrect values.""" + mock.return_value = RESPONSE200 + + valveid = str(uuid.uuid4()) + duration1 = randrange(-50, -1) + duration2 = randrange(86401, 86500) + # Check that values should be within range. self.assertRaises( - AssertionError, self.valve.start_watering, valveid, -1 + AssertionError, self.valve.start_watering, valveid, duration1 ) self.assertRaises( - AssertionError, self.valve.start_watering, valveid, 86401 + AssertionError, self.valve.start_watering, valveid, duration2 ) @patch("requests.Session.request") @@ -138,12 +147,21 @@ def test_start_watering(self, mock): json.dumps({"valveId": valveid, "durationSeconds": duration}), ) + @patch("requests.Session.request") + def test_start_watering_exception(self, mock): + """Test if the start_watering method catches incorrect values.""" + mock.return_value = RESPONSE204 + + valveid = str(uuid.uuid4()) + duration1 = randrange(-50, -1) + duration2 = randrange(86401, 86500) + # Check that values should be within range. self.assertRaises( - AssertionError, self.valve.start_watering, valveid, -1 + AssertionError, self.valve.start_watering, valveid, duration1 ) self.assertRaises( - AssertionError, self.valve.start_watering, valveid, 86401 + AssertionError, self.valve.start_watering, valveid, duration2 ) @patch("requests.Session.request")