diff --git a/.all-contributorsrc b/.all-contributorsrc
new file mode 100644
index 0000000..58ca321
--- /dev/null
+++ b/.all-contributorsrc
@@ -0,0 +1,28 @@
+{
+ "files": [
+ "README.md"
+ ],
+ "imageSize": 100,
+ "commit": false,
+ "commitType": "docs",
+ "commitConvention": "angular",
+ "contributors": [
+ {
+ "login": "brg468",
+ "name": "Brian Rogers",
+ "avatar_url": "https://avatars.githubusercontent.com/u/19143191?v=4",
+ "profile": "https://github.com/brg468",
+ "contributions": [
+ "code",
+ "doc",
+ "test"
+ ]
+ }
+ ],
+ "contributorsPerLine": 7,
+ "skipCi": true,
+ "repoType": "github",
+ "repoHost": "https://github.com",
+ "projectName": "rachiopy",
+ "projectOwner": "rfverbruggen"
+}
diff --git a/.all-contributorsrc.json b/.all-contributorsrc.json
new file mode 100644
index 0000000..f382d3f
--- /dev/null
+++ b/.all-contributorsrc.json
@@ -0,0 +1,4 @@
+{
+ "projectName": "rachiopy",
+ "projectOwner": "rfverbruggen"
+}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..72c6089
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ dev, master ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ dev ]
+ schedule:
+ - cron: '28 6 * * 6'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'python' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ https://git.io/JvXDl
+
+ # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 002dfd5..d17fa56 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -6,18 +6,14 @@ jobs:
# The type of runner that the job will run on
runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: [3.6, 3.7, 3.8]
-
# 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
- uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
+ - name: Set up Python 3.x
uses: actions/setup-python@v2
with:
- python-version: ${{ matrix.python-version }}
+ python-version: 3.x
- name: Install dependencies
run: |
python -m pip install --upgrade pip
@@ -28,4 +24,4 @@ jobs:
- name: Run flake8
run: flake8
- name: Run pydocstyle
- run: pydocstyle {posargs:rachiopy tests}
\ No newline at end of file
+ run: pydocstyle {posargs:rachiopy tests}
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 5dd5a33..6836e35 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -7,13 +7,18 @@ on:
jobs:
build-n-publish:
name: Build and publish Python distributions to PyPI
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://pypi.org/project/RachioPy
+ permissions:
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@master
- - name: Set up Python 3.8
- uses: actions/setup-python@v1
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
with:
- python-version: 3.8
+ python-version: 3.x
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
@@ -25,12 +30,9 @@ jobs:
run: python setup.py sdist bdist_wheel
- name: Publish distribution to Test PyPI
if: startsWith(github.ref, 'refs/tags')
- uses: pypa/gh-action-pypi-publish@master
+ uses: pypa/gh-action-pypi-publish@release/v1
with:
- password: ${{ secrets.test_pypi_password }}
- repository_url: https://test.pypi.org/legacy/
+ repository-url: https://test.pypi.org/legacy/
- name: Publish distribution to PyPI
if: startsWith(github.ref, 'refs/tags')
- uses: pypa/gh-action-pypi-publish@master
- with:
- password: ${{ secrets.pypi_password }}
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/publish_test.yml b/.github/workflows/publish_test.yml
index 5cb070a..f3275c7 100644
--- a/.github/workflows/publish_test.yml
+++ b/.github/workflows/publish_test.yml
@@ -8,13 +8,18 @@ on:
jobs:
build-n-publish:
name: Build and publish Python distributions to Test PyPI
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-latest
+ environment:
+ name: pypi
+ url: https://test.pypi.org/project/RachioPy
+ permissions:
+ id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@master
- - name: Set up Python 3.8
- uses: actions/setup-python@v1
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
with:
- python-version: 3.8
+ python-version: 3.x
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
@@ -24,8 +29,7 @@ jobs:
run: python -m unittest discover -v tests
- name: Build distribution
run: python setup.py sdist bdist_wheel
- - name: Publish distribution to Test PyPI
- uses: pypa/gh-action-pypi-publish@master
+ - name: Publish package distributions to TestPyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
with:
- password: ${{ secrets.test_pypi_password }}
- repository_url: https://test.pypi.org/legacy/
\ No newline at end of file
+ repository-url: https://test.pypi.org/legacy/
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9961237..bd440c5 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]
+ 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:
@@ -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
diff --git a/README.md b/README.md
index ddcf089..e68e8e6 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,12 @@
-Rachiopy
-========
+# Rachiopy
+
+[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
+
+
This python package provides a interface to the Rachio public API.
-Usage
------
+## Usage
+
```python
from rachiopy import Rachio
@@ -13,3 +16,28 @@ r.person.info()
```
For the complete documentation visit [read the docs](https://rachiopy.readthedocs.io/en/latest/).
+
+## Contributors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/index.rst b/docs/index.rst
index bc1d1dd..ec30503 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -19,7 +19,7 @@ Getting Started
from rachiopy import Rachio
r = Rachio("8e600a4c-0027-4a9a-9bda-dc8d5c90350d")
- resp, content = r.person.getInfo()
+ resp, content = r.person.info()
print (resp["status"])
print (content["id"])
diff --git a/rachiopy/__init__.py b/rachiopy/__init__.py
index e5fc760..21d0910 100644
--- a/rachiopy/__init__.py
+++ b/rachiopy/__init__.py
@@ -7,11 +7,15 @@
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):
"""Object representing the Rachio API."""
+ # pylint: disable=too-many-instance-attributes
def __init__(self, authtoken: str):
"""Initialze the Rachio API wrapper.
@@ -25,3 +29,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..6505195
--- /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..4eec922 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:
@@ -32,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
@@ -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 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
+ 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/setup.cfg b/setup.cfg
deleted file mode 100644
index b88034e..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[metadata]
-description-file = README.md
diff --git a/setup.py b/setup.py
index 6674fd2..b60ed23 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,11 @@
"""Rachiopy setup script."""
from setuptools import find_packages, setup
+from datetime import datetime
+from pathlib import Path
-VERSION = "1.0.3"
+NOW = datetime.now().strftime("%m%d%Y%H%M%S")
+
+VERSION = "1.1.0"
GITHUB_USERNAME = "rfverbruggen"
GITHUB_REPOSITORY = "rachiopy"
@@ -14,6 +18,10 @@
PACKAGES = find_packages(exclude=["tests", "tests.*"])
+# read the contents of your README file
+this_directory = Path(__file__).parent
+long_description = (this_directory / "README.md").read_text()
+
setup(
name="RachioPy",
version=VERSION,
@@ -26,6 +34,8 @@
project_urls=PROJECT_URLS,
license="MIT",
description="A Python module for the Rachio API.",
+ long_description=long_description,
+ long_description_content_type='text/markdown',
platforms="Cross Platform",
classifiers=[
"Development Status :: 5 - Production/Stable",
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_device.py b/tests/test_device.py
index 53205c9..5f7b072 100644
--- a/tests/test_device.py
+++ b/tests/test_device.py
@@ -33,7 +33,7 @@ def test_get(self, mock):
# Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/device/" f"{deviceid}",
+ args[1], f"{BASE_API_URL}/device/{deviceid}",
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
@@ -49,9 +49,9 @@ def test_current_schedule(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/device/" f"{deviceid}/current_schedule",
+ args[1], f"{BASE_API_URL}/device/{deviceid}/current_schedule",
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
@@ -69,7 +69,7 @@ def test_event(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1],
f"{BASE_API_URL}/device/"
@@ -91,9 +91,9 @@ def test_forecast(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/device/" f"{deviceid}/forecast?units=US",
+ args[1], f"{BASE_API_URL}/device/{deviceid}/forecast?units=US",
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
@@ -102,9 +102,9 @@ def test_forecast(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/device/" f"{deviceid}/forecast?units=US",
+ args[1], f"{BASE_API_URL}/device/{deviceid}/forecast?units=US",
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
@@ -113,10 +113,10 @@ def test_forecast(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1],
- f"{BASE_API_URL}/device/" f"{deviceid}/forecast?units=METRIC",
+ f"{BASE_API_URL}/device/{deviceid}/forecast?units=METRIC",
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
@@ -135,7 +135,7 @@ def test_stop_water(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1], f"{BASE_API_URL}/device/stop_water",
)
@@ -154,7 +154,7 @@ def test_rain_delay(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1], f"{BASE_API_URL}/device/rain_delay",
)
@@ -180,7 +180,7 @@ def test_turn_on(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1], f"{BASE_API_URL}/device/on",
)
@@ -198,7 +198,7 @@ def test_turn_off(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1], f"{BASE_API_URL}/device/off",
)
@@ -217,7 +217,7 @@ def test_pause_zone_run(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1], f"{BASE_API_URL}/device/pause_zone_run",
)
@@ -245,7 +245,7 @@ def test_resume_zone_run(self, mock):
args, kwargs = mock.call_args
- # Check that the mock funciton is called with the rights args.
+ # Check that the mock function is called with the rights args.
self.assertEqual(
args[1], f"{BASE_API_URL}/device/resume_zone_run",
)
diff --git a/tests/test_flexschedulerule.py b/tests/test_flexschedulerule.py
index ed1379e..71ec46f 100644
--- a/tests/test_flexschedulerule.py
+++ b/tests/test_flexschedulerule.py
@@ -32,7 +32,7 @@ def test_get(self, mock):
# Check that the mock function is called with the rights args.
self.assertEqual(
args[1],
- f"{BASE_API_URL}/flexschedulerule/" f"{flexscheduleruleid}",
+ f"{BASE_API_URL}/flexschedulerule/{flexscheduleruleid}",
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
diff --git a/tests/test_notification.py b/tests/test_notification.py
index d212aca..966d3f5 100644
--- a/tests/test_notification.py
+++ b/tests/test_notification.py
@@ -48,7 +48,7 @@ def test_get_device_webhook(self, mock):
# Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/notification/" f"{deviceid}/webhook"
+ args[1], f"{BASE_API_URL}/notification/{deviceid}/webhook"
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
@@ -124,7 +124,7 @@ def test_delete(self, mock):
# Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/notification/webhook/" f"{hookid}"
+ args[1], f"{BASE_API_URL}/notification/webhook/{hookid}"
)
self.assertEqual(args[0], "DELETE")
self.assertEqual(kwargs["data"], None)
@@ -142,7 +142,7 @@ def test_get(self, mock):
# Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/notification/webhook/" f"{hookid}"
+ args[1], f"{BASE_API_URL}/notification/webhook/{hookid}"
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
diff --git a/tests/test_program.py b/tests/test_program.py
new file mode 100644
index 0000000..185e79d
--- /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_schedulerule.py b/tests/test_schedulerule.py
index b56a727..264d9a9 100644
--- a/tests/test_schedulerule.py
+++ b/tests/test_schedulerule.py
@@ -32,7 +32,7 @@ def test_get(self, mock):
# Check that the mock function is called with the rights args.
self.assertEqual(
- args[1], f"{BASE_API_URL}/schedulerule/" f"{scheduleruleid}"
+ args[1], f"{BASE_API_URL}/schedulerule/{scheduleruleid}"
)
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
diff --git a/tests/test_summary.py b/tests/test_summary.py
new file mode 100644
index 0000000..651a0e7
--- /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..d9ab8e7
--- /dev/null
+++ b/tests/test_valve.py
@@ -0,0 +1,181 @@
+"""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}
+ ),
+ )
+
+ @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, duration1
+ )
+ self.assertRaises(
+ AssertionError, self.valve.start_watering, valveid, duration2
+ )
+
+ @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}),
+ )
+
+ @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, duration1
+ )
+ self.assertRaises(
+ AssertionError, self.valve.start_watering, valveid, duration2
+ )
+
+ @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}))
diff --git a/tests/test_zone.py b/tests/test_zone.py
index a634fa0..1ed8a0d 100644
--- a/tests/test_zone.py
+++ b/tests/test_zone.py
@@ -46,7 +46,7 @@ def test_get(self, mock):
args, kwargs = mock.call_args
# Check that the mock function is called with the rights args.
- self.assertEqual(args[1], f"{BASE_API_URL}/zone/" f"{zoneid}")
+ self.assertEqual(args[1], f"{BASE_API_URL}/zone/{zoneid}")
self.assertEqual(args[0], "GET")
self.assertEqual(kwargs["data"], None)
diff --git a/tox.ini b/tox.ini
index c724886..6980c70 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = clean, py36, py37, py38, pylint, flake8, pydocstyle, stats
+envlist = clean, py{38,39,310,311,312}, pylint, flake8, pydocstyle, stats
ignore_basepython_conflict = true
[testenv:clean]